1 | /* |
2 | * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com> |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright notice, |
9 | * this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * * Neither the name of Redis nor the names of its contributors may be used |
14 | * to endorse or promote products derived from this software without |
15 | * specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
27 | * POSSIBILITY OF SUCH DAMAGE. |
28 | */ |
29 | |
30 | #include "server.h" |
31 | #include "sha256.h" |
32 | #include <fcntl.h> |
33 | #include <ctype.h> |
34 | |
35 | /* ============================================================================= |
36 | * Global state for ACLs |
37 | * ==========================================================================*/ |
38 | |
39 | rax *Users; /* Table mapping usernames to user structures. */ |
40 | |
41 | user *DefaultUser; /* Global reference to the default user. |
42 | Every new connection is associated to it, if no |
43 | AUTH or HELLO is used to authenticate with a |
44 | different user. */ |
45 | |
46 | list *UsersToLoad; /* This is a list of users found in the configuration file |
47 | that we'll need to load in the final stage of Redis |
48 | initialization, after all the modules are already |
49 | loaded. Every list element is a NULL terminated |
50 | array of SDS pointers: the first is the user name, |
51 | all the remaining pointers are ACL rules in the same |
52 | format as ACLSetUser(). */ |
53 | list *ACLLog; /* Our security log, the user is able to inspect that |
54 | using the ACL LOG command .*/ |
55 | |
56 | static rax *commandId = NULL; /* Command name to id mapping */ |
57 | |
58 | static unsigned long nextid = 0; /* Next command id that has not been assigned */ |
59 | |
60 | struct ACLCategoryItem { |
61 | const char *name; |
62 | uint64_t flag; |
63 | } ACLCommandCategories[] = { /* See redis.conf for details on each category. */ |
64 | {"keyspace" , ACL_CATEGORY_KEYSPACE}, |
65 | {"read" , ACL_CATEGORY_READ}, |
66 | {"write" , ACL_CATEGORY_WRITE}, |
67 | {"set" , ACL_CATEGORY_SET}, |
68 | {"sortedset" , ACL_CATEGORY_SORTEDSET}, |
69 | {"list" , ACL_CATEGORY_LIST}, |
70 | {"hash" , ACL_CATEGORY_HASH}, |
71 | {"string" , ACL_CATEGORY_STRING}, |
72 | {"bitmap" , ACL_CATEGORY_BITMAP}, |
73 | {"hyperloglog" , ACL_CATEGORY_HYPERLOGLOG}, |
74 | {"geo" , ACL_CATEGORY_GEO}, |
75 | {"stream" , ACL_CATEGORY_STREAM}, |
76 | {"pubsub" , ACL_CATEGORY_PUBSUB}, |
77 | {"admin" , ACL_CATEGORY_ADMIN}, |
78 | {"fast" , ACL_CATEGORY_FAST}, |
79 | {"slow" , ACL_CATEGORY_SLOW}, |
80 | {"blocking" , ACL_CATEGORY_BLOCKING}, |
81 | {"dangerous" , ACL_CATEGORY_DANGEROUS}, |
82 | {"connection" , ACL_CATEGORY_CONNECTION}, |
83 | {"transaction" , ACL_CATEGORY_TRANSACTION}, |
84 | {"scripting" , ACL_CATEGORY_SCRIPTING}, |
85 | {NULL,0} /* Terminator. */ |
86 | }; |
87 | |
88 | struct ACLUserFlag { |
89 | const char *name; |
90 | uint64_t flag; |
91 | } ACLUserFlags[] = { |
92 | /* Note: the order here dictates the emitted order at ACLDescribeUser */ |
93 | {"on" , USER_FLAG_ENABLED}, |
94 | {"off" , USER_FLAG_DISABLED}, |
95 | {"nopass" , USER_FLAG_NOPASS}, |
96 | {"skip-sanitize-payload" , USER_FLAG_SANITIZE_PAYLOAD_SKIP}, |
97 | {"sanitize-payload" , USER_FLAG_SANITIZE_PAYLOAD}, |
98 | {NULL,0} /* Terminator. */ |
99 | }; |
100 | |
101 | struct ACLSelectorFlags { |
102 | const char *name; |
103 | uint64_t flag; |
104 | } ACLSelectorFlags[] = { |
105 | /* Note: the order here dictates the emitted order at ACLDescribeUser */ |
106 | {"allkeys" , SELECTOR_FLAG_ALLKEYS}, |
107 | {"allchannels" , SELECTOR_FLAG_ALLCHANNELS}, |
108 | {"allcommands" , SELECTOR_FLAG_ALLCOMMANDS}, |
109 | {NULL,0} /* Terminator. */ |
110 | }; |
111 | |
112 | /* ACL selectors are private and not exposed outside of acl.c. */ |
113 | typedef struct { |
114 | uint32_t flags; /* See SELECTOR_FLAG_* */ |
115 | /* The bit in allowed_commands is set if this user has the right to |
116 | * execute this command. |
117 | * |
118 | * If the bit for a given command is NOT set and the command has |
119 | * allowed first-args, Redis will also check allowed_firstargs in order to |
120 | * understand if the command can be executed. */ |
121 | uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64]; |
122 | /* allowed_firstargs is used by ACL rules to block access to a command unless a |
123 | * specific argv[1] is given. |
124 | * |
125 | * For each command ID (corresponding to the command bit set in allowed_commands), |
126 | * This array points to an array of SDS strings, terminated by a NULL pointer, |
127 | * with all the first-args that are allowed for this command. When no first-arg |
128 | * matching is used, the field is just set to NULL to avoid allocating |
129 | * USER_COMMAND_BITS_COUNT pointers. */ |
130 | sds **allowed_firstargs; |
131 | list *patterns; /* A list of allowed key patterns. If this field is NULL |
132 | the user cannot mention any key in a command, unless |
133 | the flag ALLKEYS is set in the user. */ |
134 | list *channels; /* A list of allowed Pub/Sub channel patterns. If this |
135 | field is NULL the user cannot mention any channel in a |
136 | `PUBLISH` or [P][UNSUBSCRIBE] command, unless the flag |
137 | ALLCHANNELS is set in the user. */ |
138 | } aclSelector; |
139 | |
140 | void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id); |
141 | void ACLResetFirstArgs(aclSelector *selector); |
142 | void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub); |
143 | void ACLFreeLogEntry(void *le); |
144 | int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen); |
145 | |
146 | /* The length of the string representation of a hashed password. */ |
147 | #define HASH_PASSWORD_LEN (SHA256_BLOCK_SIZE*2) |
148 | |
149 | /* ============================================================================= |
150 | * Helper functions for the rest of the ACL implementation |
151 | * ==========================================================================*/ |
152 | |
153 | /* Return zero if strings are the same, non-zero if they are not. |
154 | * The comparison is performed in a way that prevents an attacker to obtain |
155 | * information about the nature of the strings just monitoring the execution |
156 | * time of the function. Note: The two strings must be the same length. |
157 | */ |
158 | int time_independent_strcmp(char *a, char *b, int len) { |
159 | int diff = 0; |
160 | for (int j = 0; j < len; j++) { |
161 | diff |= (a[j] ^ b[j]); |
162 | } |
163 | return diff; /* If zero strings are the same. */ |
164 | } |
165 | |
166 | /* Given an SDS string, returns the SHA256 hex representation as a |
167 | * new SDS string. */ |
168 | sds ACLHashPassword(unsigned char *cleartext, size_t len) { |
169 | SHA256_CTX ctx; |
170 | unsigned char hash[SHA256_BLOCK_SIZE]; |
171 | char hex[HASH_PASSWORD_LEN]; |
172 | char *cset = "0123456789abcdef" ; |
173 | |
174 | sha256_init(&ctx); |
175 | sha256_update(&ctx,(unsigned char*)cleartext,len); |
176 | sha256_final(&ctx,hash); |
177 | |
178 | for (int j = 0; j < SHA256_BLOCK_SIZE; j++) { |
179 | hex[j*2] = cset[((hash[j]&0xF0)>>4)]; |
180 | hex[j*2+1] = cset[(hash[j]&0xF)]; |
181 | } |
182 | return sdsnewlen(hex,HASH_PASSWORD_LEN); |
183 | } |
184 | |
185 | /* Given a hash and the hash length, returns C_OK if it is a valid password |
186 | * hash, or C_ERR otherwise. */ |
187 | int ACLCheckPasswordHash(unsigned char *hash, int hashlen) { |
188 | if (hashlen != HASH_PASSWORD_LEN) { |
189 | return C_ERR; |
190 | } |
191 | |
192 | /* Password hashes can only be characters that represent |
193 | * hexadecimal values, which are numbers and lowercase |
194 | * characters 'a' through 'f'. */ |
195 | for(int i = 0; i < HASH_PASSWORD_LEN; i++) { |
196 | char c = hash[i]; |
197 | if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) { |
198 | return C_ERR; |
199 | } |
200 | } |
201 | return C_OK; |
202 | } |
203 | |
204 | /* ============================================================================= |
205 | * Low level ACL API |
206 | * ==========================================================================*/ |
207 | |
208 | /* Return 1 if the specified string contains spaces or null characters. |
209 | * We do this for usernames and key patterns for simpler rewriting of |
210 | * ACL rules, presentation on ACL list, and to avoid subtle security bugs |
211 | * that may arise from parsing the rules in presence of escapes. |
212 | * The function returns 0 if the string has no spaces. */ |
213 | int ACLStringHasSpaces(const char *s, size_t len) { |
214 | for (size_t i = 0; i < len; i++) { |
215 | if (isspace(s[i]) || s[i] == 0) return 1; |
216 | } |
217 | return 0; |
218 | } |
219 | |
220 | /* Given the category name the command returns the corresponding flag, or |
221 | * zero if there is no match. */ |
222 | uint64_t ACLGetCommandCategoryFlagByName(const char *name) { |
223 | for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { |
224 | if (!strcasecmp(name,ACLCommandCategories[j].name)) { |
225 | return ACLCommandCategories[j].flag; |
226 | } |
227 | } |
228 | return 0; /* No match. */ |
229 | } |
230 | |
231 | /* Method for searching for a user within a list of user definitions. The |
232 | * list contains an array of user arguments, and we are only |
233 | * searching the first argument, the username, for a match. */ |
234 | int ACLListMatchLoadedUser(void *definition, void *user) { |
235 | sds *user_definition = definition; |
236 | return sdscmp(user_definition[0], user) == 0; |
237 | } |
238 | |
239 | /* Method for passwords/pattern comparison used for the user->passwords list |
240 | * so that we can search for items with listSearchKey(). */ |
241 | int ACLListMatchSds(void *a, void *b) { |
242 | return sdscmp(a,b) == 0; |
243 | } |
244 | |
245 | /* Method to free list elements from ACL users password/patterns lists. */ |
246 | void ACLListFreeSds(void *item) { |
247 | sdsfree(item); |
248 | } |
249 | |
250 | /* Method to duplicate list elements from ACL users password/patterns lists. */ |
251 | void *ACLListDupSds(void *item) { |
252 | return sdsdup(item); |
253 | } |
254 | |
255 | /* Structure used for handling key patterns with different key |
256 | * based permissions. */ |
257 | typedef struct { |
258 | int flags; /* The CMD_KEYS_* flags for this key pattern */ |
259 | sds pattern; /* The pattern to match keys against */ |
260 | } keyPattern; |
261 | |
262 | /* Create a new key pattern. */ |
263 | keyPattern *ACLKeyPatternCreate(sds pattern, int flags) { |
264 | keyPattern *new = (keyPattern *) zmalloc(sizeof(keyPattern)); |
265 | new->pattern = pattern; |
266 | new->flags = flags; |
267 | return new; |
268 | } |
269 | |
270 | /* Free a key pattern and internal structures. */ |
271 | void ACLKeyPatternFree(keyPattern *pattern) { |
272 | sdsfree(pattern->pattern); |
273 | zfree(pattern); |
274 | } |
275 | |
276 | /* Method for passwords/pattern comparison used for the user->passwords list |
277 | * so that we can search for items with listSearchKey(). */ |
278 | int ACLListMatchKeyPattern(void *a, void *b) { |
279 | return sdscmp(((keyPattern *) a)->pattern,((keyPattern *) b)->pattern) == 0; |
280 | } |
281 | |
282 | /* Method to free list elements from ACL users password/patterns lists. */ |
283 | void ACLListFreeKeyPattern(void *item) { |
284 | ACLKeyPatternFree(item); |
285 | } |
286 | |
287 | /* Method to duplicate list elements from ACL users password/patterns lists. */ |
288 | void *ACLListDupKeyPattern(void *item) { |
289 | keyPattern *old = (keyPattern *) item; |
290 | return ACLKeyPatternCreate(sdsdup(old->pattern), old->flags); |
291 | } |
292 | |
293 | /* Append the string representation of a key pattern onto the |
294 | * provided base string. */ |
295 | sds sdsCatPatternString(sds base, keyPattern *pat) { |
296 | if (pat->flags == ACL_ALL_PERMISSION) { |
297 | base = sdscatlen(base,"~" ,1); |
298 | } else if (pat->flags == ACL_READ_PERMISSION) { |
299 | base = sdscatlen(base,"%R~" ,3); |
300 | } else if (pat->flags == ACL_WRITE_PERMISSION) { |
301 | base = sdscatlen(base,"%W~" ,3); |
302 | } else { |
303 | serverPanic("Invalid key pattern flag detected" ); |
304 | } |
305 | return sdscatsds(base, pat->pattern); |
306 | } |
307 | |
308 | /* Create an empty selector with the provided set of initial |
309 | * flags. The selector will be default have no permissions. */ |
310 | aclSelector *ACLCreateSelector(int flags) { |
311 | aclSelector *selector = zmalloc(sizeof(aclSelector)); |
312 | selector->flags = flags | server.acl_pubsub_default; |
313 | selector->patterns = listCreate(); |
314 | selector->channels = listCreate(); |
315 | selector->allowed_firstargs = NULL; |
316 | |
317 | listSetMatchMethod(selector->patterns,ACLListMatchKeyPattern); |
318 | listSetFreeMethod(selector->patterns,ACLListFreeKeyPattern); |
319 | listSetDupMethod(selector->patterns,ACLListDupKeyPattern); |
320 | listSetMatchMethod(selector->channels,ACLListMatchSds); |
321 | listSetFreeMethod(selector->channels,ACLListFreeSds); |
322 | listSetDupMethod(selector->channels,ACLListDupSds); |
323 | memset(selector->allowed_commands,0,sizeof(selector->allowed_commands)); |
324 | |
325 | return selector; |
326 | } |
327 | |
328 | /* Cleanup the provided selector, including all interior structures. */ |
329 | void ACLFreeSelector(aclSelector *selector) { |
330 | listRelease(selector->patterns); |
331 | listRelease(selector->channels); |
332 | ACLResetFirstArgs(selector); |
333 | zfree(selector); |
334 | } |
335 | |
336 | /* Create an exact copy of the provided selector. */ |
337 | aclSelector *ACLCopySelector(aclSelector *src) { |
338 | aclSelector *dst = zmalloc(sizeof(aclSelector)); |
339 | dst->flags = src->flags; |
340 | dst->patterns = listDup(src->patterns); |
341 | dst->channels = listDup(src->channels); |
342 | memcpy(dst->allowed_commands,src->allowed_commands, |
343 | sizeof(dst->allowed_commands)); |
344 | dst->allowed_firstargs = NULL; |
345 | /* Copy the allowed first-args array of array of SDS strings. */ |
346 | if (src->allowed_firstargs) { |
347 | for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) { |
348 | if (!(src->allowed_firstargs[j])) continue; |
349 | for (int i = 0; src->allowed_firstargs[j][i]; i++) { |
350 | ACLAddAllowedFirstArg(dst, j, src->allowed_firstargs[j][i]); |
351 | } |
352 | } |
353 | } |
354 | return dst; |
355 | } |
356 | |
357 | /* List method for freeing a selector */ |
358 | void ACLListFreeSelector(void *a) { |
359 | ACLFreeSelector((aclSelector *) a); |
360 | } |
361 | |
362 | /* List method for duplicating a selector */ |
363 | void *ACLListDuplicateSelector(void *src) { |
364 | return ACLCopySelector((aclSelector *)src); |
365 | } |
366 | |
367 | /* All users have an implicit root selector which |
368 | * provides backwards compatibility to the old ACLs- |
369 | * permissions. */ |
370 | aclSelector *ACLUserGetRootSelector(user *u) { |
371 | serverAssert(listLength(u->selectors)); |
372 | aclSelector *s = (aclSelector *) listNodeValue(listFirst(u->selectors)); |
373 | serverAssert(s->flags & SELECTOR_FLAG_ROOT); |
374 | return s; |
375 | } |
376 | |
377 | /* Create a new user with the specified name, store it in the list |
378 | * of users (the Users global radix tree), and returns a reference to |
379 | * the structure representing the user. |
380 | * |
381 | * If the user with such name already exists NULL is returned. */ |
382 | user *ACLCreateUser(const char *name, size_t namelen) { |
383 | if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL; |
384 | user *u = zmalloc(sizeof(*u)); |
385 | u->name = sdsnewlen(name,namelen); |
386 | u->flags = USER_FLAG_DISABLED; |
387 | u->passwords = listCreate(); |
388 | listSetMatchMethod(u->passwords,ACLListMatchSds); |
389 | listSetFreeMethod(u->passwords,ACLListFreeSds); |
390 | listSetDupMethod(u->passwords,ACLListDupSds); |
391 | |
392 | u->selectors = listCreate(); |
393 | listSetFreeMethod(u->selectors,ACLListFreeSelector); |
394 | listSetDupMethod(u->selectors,ACLListDuplicateSelector); |
395 | |
396 | /* Add the initial root selector */ |
397 | aclSelector *s = ACLCreateSelector(SELECTOR_FLAG_ROOT); |
398 | listAddNodeHead(u->selectors, s); |
399 | |
400 | raxInsert(Users,(unsigned char*)name,namelen,u,NULL); |
401 | return u; |
402 | } |
403 | |
404 | /* This function should be called when we need an unlinked "fake" user |
405 | * we can use in order to validate ACL rules or for other similar reasons. |
406 | * The user will not get linked to the Users radix tree. The returned |
407 | * user should be released with ACLFreeUser() as usually. */ |
408 | user *ACLCreateUnlinkedUser(void) { |
409 | char username[64]; |
410 | for (int j = 0; ; j++) { |
411 | snprintf(username,sizeof(username),"__fakeuser:%d__" ,j); |
412 | user *fakeuser = ACLCreateUser(username,strlen(username)); |
413 | if (fakeuser == NULL) continue; |
414 | int retval = raxRemove(Users,(unsigned char*) username, |
415 | strlen(username),NULL); |
416 | serverAssert(retval != 0); |
417 | return fakeuser; |
418 | } |
419 | } |
420 | |
421 | /* Release the memory used by the user structure. Note that this function |
422 | * will not remove the user from the Users global radix tree. */ |
423 | void ACLFreeUser(user *u) { |
424 | sdsfree(u->name); |
425 | listRelease(u->passwords); |
426 | listRelease(u->selectors); |
427 | zfree(u); |
428 | } |
429 | |
430 | /* When a user is deleted we need to cycle the active |
431 | * connections in order to kill all the pending ones that |
432 | * are authenticated with such user. */ |
433 | void ACLFreeUserAndKillClients(user *u) { |
434 | listIter li; |
435 | listNode *ln; |
436 | listRewind(server.clients,&li); |
437 | while ((ln = listNext(&li)) != NULL) { |
438 | client *c = listNodeValue(ln); |
439 | if (c->user == u) { |
440 | /* We'll free the connection asynchronously, so |
441 | * in theory to set a different user is not needed. |
442 | * However if there are bugs in Redis, soon or later |
443 | * this may result in some security hole: it's much |
444 | * more defensive to set the default user and put |
445 | * it in non authenticated mode. */ |
446 | c->user = DefaultUser; |
447 | c->authenticated = 0; |
448 | /* We will write replies to this client later, so we can't |
449 | * close it directly even if async. */ |
450 | if (c == server.current_client) { |
451 | c->flags |= CLIENT_CLOSE_AFTER_COMMAND; |
452 | } else { |
453 | freeClientAsync(c); |
454 | } |
455 | } |
456 | } |
457 | ACLFreeUser(u); |
458 | } |
459 | |
460 | /* Copy the user ACL rules from the source user 'src' to the destination |
461 | * user 'dst' so that at the end of the process they'll have exactly the |
462 | * same rules (but the names will continue to be the original ones). */ |
463 | void ACLCopyUser(user *dst, user *src) { |
464 | listRelease(dst->passwords); |
465 | listRelease(dst->selectors); |
466 | dst->passwords = listDup(src->passwords); |
467 | dst->selectors = listDup(src->selectors); |
468 | dst->flags = src->flags; |
469 | } |
470 | |
471 | /* Free all the users registered in the radix tree 'users' and free the |
472 | * radix tree itself. */ |
473 | void (rax *users) { |
474 | raxFreeWithCallback(users,(void(*)(void*))ACLFreeUserAndKillClients); |
475 | } |
476 | |
477 | /* Given a command ID, this function set by reference 'word' and 'bit' |
478 | * so that user->allowed_commands[word] will address the right word |
479 | * where the corresponding bit for the provided ID is stored, and |
480 | * so that user->allowed_commands[word]&bit will identify that specific |
481 | * bit. The function returns C_ERR in case the specified ID overflows |
482 | * the bitmap in the user representation. */ |
483 | int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) { |
484 | if (id >= USER_COMMAND_BITS_COUNT) return C_ERR; |
485 | *word = id / sizeof(uint64_t) / 8; |
486 | *bit = 1ULL << (id % (sizeof(uint64_t) * 8)); |
487 | return C_OK; |
488 | } |
489 | |
490 | /* Check if the specified command bit is set for the specified user. |
491 | * The function returns 1 is the bit is set or 0 if it is not. |
492 | * Note that this function does not check the ALLCOMMANDS flag of the user |
493 | * but just the lowlevel bitmask. |
494 | * |
495 | * If the bit overflows the user internal representation, zero is returned |
496 | * in order to disallow the execution of the command in such edge case. */ |
497 | int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) { |
498 | uint64_t word, bit; |
499 | if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return 0; |
500 | return (selector->allowed_commands[word] & bit) != 0; |
501 | } |
502 | |
503 | /* When +@all or allcommands is given, we set a reserved bit as well that we |
504 | * can later test, to see if the user has the right to execute "future commands", |
505 | * that is, commands loaded later via modules. */ |
506 | int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) { |
507 | return ACLGetSelectorCommandBit(selector,USER_COMMAND_BITS_COUNT-1); |
508 | } |
509 | |
510 | /* Set the specified command bit for the specified user to 'value' (0 or 1). |
511 | * If the bit overflows the user internal representation, no operation |
512 | * is performed. As a side effect of calling this function with a value of |
513 | * zero, the user flag ALLCOMMANDS is cleared since it is no longer possible |
514 | * to skip the command bit explicit test. */ |
515 | void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value) { |
516 | uint64_t word, bit; |
517 | if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return; |
518 | if (value) { |
519 | selector->allowed_commands[word] |= bit; |
520 | } else { |
521 | selector->allowed_commands[word] &= ~bit; |
522 | selector->flags &= ~SELECTOR_FLAG_ALLCOMMANDS; |
523 | } |
524 | } |
525 | |
526 | /* This function is used to allow/block a specific command. |
527 | * Allowing/blocking a container command also applies for its subcommands */ |
528 | void ACLChangeSelectorPerm(aclSelector *selector, struct redisCommand *cmd, int allow) { |
529 | unsigned long id = cmd->id; |
530 | ACLSetSelectorCommandBit(selector,id,allow); |
531 | ACLResetFirstArgsForCommand(selector,id); |
532 | if (cmd->subcommands_dict) { |
533 | dictEntry *de; |
534 | dictIterator *di = dictGetSafeIterator(cmd->subcommands_dict); |
535 | while((de = dictNext(di)) != NULL) { |
536 | struct redisCommand *sub = (struct redisCommand *)dictGetVal(de); |
537 | ACLSetSelectorCommandBit(selector,sub->id,allow); |
538 | } |
539 | dictReleaseIterator(di); |
540 | } |
541 | } |
542 | |
543 | void ACLSetSelectorCommandBitsForCategoryLogic(dict *commands, aclSelector *selector, uint64_t cflag, int value) { |
544 | dictIterator *di = dictGetIterator(commands); |
545 | dictEntry *de; |
546 | while ((de = dictNext(di)) != NULL) { |
547 | struct redisCommand *cmd = dictGetVal(de); |
548 | if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */ |
549 | if (cmd->acl_categories & cflag) { |
550 | ACLChangeSelectorPerm(selector,cmd,value); |
551 | } |
552 | if (cmd->subcommands_dict) { |
553 | ACLSetSelectorCommandBitsForCategoryLogic(cmd->subcommands_dict, selector, cflag, value); |
554 | } |
555 | } |
556 | dictReleaseIterator(di); |
557 | } |
558 | |
559 | /* This is like ACLSetSelectorCommandBit(), but instead of setting the specified |
560 | * ID, it will check all the commands in the category specified as argument, |
561 | * and will set all the bits corresponding to such commands to the specified |
562 | * value. Since the category passed by the user may be non existing, the |
563 | * function returns C_ERR if the category was not found, or C_OK if it was |
564 | * found and the operation was performed. */ |
565 | int ACLSetSelectorCommandBitsForCategory(aclSelector *selector, const char *category, int value) { |
566 | uint64_t cflag = ACLGetCommandCategoryFlagByName(category); |
567 | if (!cflag) return C_ERR; |
568 | ACLSetSelectorCommandBitsForCategoryLogic(server.orig_commands, selector, cflag, value); |
569 | return C_OK; |
570 | } |
571 | |
572 | void ACLCountCategoryBitsForCommands(dict *commands, aclSelector *selector, unsigned long *on, unsigned long *off, uint64_t cflag) { |
573 | dictIterator *di = dictGetIterator(commands); |
574 | dictEntry *de; |
575 | while ((de = dictNext(di)) != NULL) { |
576 | struct redisCommand *cmd = dictGetVal(de); |
577 | if (cmd->acl_categories & cflag) { |
578 | if (ACLGetSelectorCommandBit(selector,cmd->id)) |
579 | (*on)++; |
580 | else |
581 | (*off)++; |
582 | } |
583 | if (cmd->subcommands_dict) { |
584 | ACLCountCategoryBitsForCommands(cmd->subcommands_dict, selector, on, off, cflag); |
585 | } |
586 | } |
587 | dictReleaseIterator(di); |
588 | } |
589 | |
590 | /* Return the number of commands allowed (on) and denied (off) for the user 'u' |
591 | * in the subset of commands flagged with the specified category name. |
592 | * If the category name is not valid, C_ERR is returned, otherwise C_OK is |
593 | * returned and on and off are populated by reference. */ |
594 | int ACLCountCategoryBitsForSelector(aclSelector *selector, unsigned long *on, unsigned long *off, |
595 | const char *category) |
596 | { |
597 | uint64_t cflag = ACLGetCommandCategoryFlagByName(category); |
598 | if (!cflag) return C_ERR; |
599 | |
600 | *on = *off = 0; |
601 | ACLCountCategoryBitsForCommands(server.orig_commands, selector, on, off, cflag); |
602 | return C_OK; |
603 | } |
604 | |
605 | sds ACLDescribeSelectorCommandRulesSingleCommands(aclSelector *selector, aclSelector *fake_selector, |
606 | sds rules, dict *commands) { |
607 | dictIterator *di = dictGetIterator(commands); |
608 | dictEntry *de; |
609 | while ((de = dictNext(di)) != NULL) { |
610 | struct redisCommand *cmd = dictGetVal(de); |
611 | int userbit = ACLGetSelectorCommandBit(selector,cmd->id); |
612 | int fakebit = ACLGetSelectorCommandBit(fake_selector,cmd->id); |
613 | if (userbit != fakebit) { |
614 | rules = sdscatlen(rules, userbit ? "+" : "-" , 1); |
615 | rules = sdscatsds(rules,cmd->fullname); |
616 | rules = sdscatlen(rules," " ,1); |
617 | ACLChangeSelectorPerm(fake_selector,cmd,userbit); |
618 | } |
619 | |
620 | if (cmd->subcommands_dict) |
621 | rules = ACLDescribeSelectorCommandRulesSingleCommands(selector,fake_selector,rules,cmd->subcommands_dict); |
622 | |
623 | /* Emit the first-args if there are any. */ |
624 | if (userbit == 0 && selector->allowed_firstargs && |
625 | selector->allowed_firstargs[cmd->id]) |
626 | { |
627 | for (int j = 0; selector->allowed_firstargs[cmd->id][j]; j++) { |
628 | rules = sdscatlen(rules,"+" ,1); |
629 | rules = sdscatsds(rules,cmd->fullname); |
630 | rules = sdscatlen(rules,"|" ,1); |
631 | rules = sdscatsds(rules,selector->allowed_firstargs[cmd->id][j]); |
632 | rules = sdscatlen(rules," " ,1); |
633 | } |
634 | } |
635 | } |
636 | dictReleaseIterator(di); |
637 | return rules; |
638 | } |
639 | |
640 | /* This function returns an SDS string representing the specified selector ACL |
641 | * rules related to command execution, in the same format you could set them |
642 | * back using ACL SETUSER. The function will return just the set of rules needed |
643 | * to recreate the user commands bitmap, without including other user flags such |
644 | * as on/off, passwords and so forth. The returned string always starts with |
645 | * the +@all or -@all rule, depending on the user bitmap, and is followed, if |
646 | * needed, by the other rules needed to narrow or extend what the user can do. */ |
647 | sds ACLDescribeSelectorCommandRules(aclSelector *selector) { |
648 | sds rules = sdsempty(); |
649 | int additive; /* If true we start from -@all and add, otherwise if |
650 | false we start from +@all and remove. */ |
651 | |
652 | /* This code is based on a trick: as we generate the rules, we apply |
653 | * them to a fake user, so that as we go we still know what are the |
654 | * bit differences we should try to address by emitting more rules. */ |
655 | aclSelector fs = {0}; |
656 | aclSelector *fake_selector = &fs; |
657 | |
658 | /* Here we want to understand if we should start with +@all and remove |
659 | * the commands corresponding to the bits that are not set in the user |
660 | * commands bitmap, or the contrary. Note that semantically the two are |
661 | * different. For instance starting with +@all and subtracting, the user |
662 | * will be able to execute future commands, while -@all and adding will just |
663 | * allow the user the run the selected commands and/or categories. |
664 | * How do we test for that? We use the trick of a reserved command ID bit |
665 | * that is set only by +@all (and its alias "allcommands"). */ |
666 | if (ACLSelectorCanExecuteFutureCommands(selector)) { |
667 | additive = 0; |
668 | rules = sdscat(rules,"+@all " ); |
669 | ACLSetSelector(fake_selector,"+@all" ,-1); |
670 | } else { |
671 | additive = 1; |
672 | rules = sdscat(rules,"-@all " ); |
673 | ACLSetSelector(fake_selector,"-@all" ,-1); |
674 | } |
675 | |
676 | /* Attempt to find a good approximation for categories and commands |
677 | * based on the current bits used, by looping over the category list |
678 | * and applying the best fit each time. Often a set of categories will not |
679 | * perfectly match the set of commands into it, so at the end we do a |
680 | * final pass adding/removing the single commands needed to make the bitmap |
681 | * exactly match. A temp user is maintained to keep track of categories |
682 | * already applied. */ |
683 | aclSelector ts = {0}; |
684 | aclSelector *temp_selector = &ts; |
685 | |
686 | /* Keep track of the categories that have been applied, to prevent |
687 | * applying them twice. */ |
688 | char applied[sizeof(ACLCommandCategories)/sizeof(ACLCommandCategories[0])]; |
689 | memset(applied, 0, sizeof(applied)); |
690 | |
691 | memcpy(temp_selector->allowed_commands, |
692 | selector->allowed_commands, |
693 | sizeof(selector->allowed_commands)); |
694 | while (1) { |
695 | int best = -1; |
696 | unsigned long mindiff = INT_MAX, maxsame = 0; |
697 | for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { |
698 | if (applied[j]) continue; |
699 | |
700 | unsigned long on, off, diff, same; |
701 | ACLCountCategoryBitsForSelector(temp_selector,&on,&off,ACLCommandCategories[j].name); |
702 | /* Check if the current category is the best this loop: |
703 | * * It has more commands in common with the user than commands |
704 | * that are different. |
705 | * AND EITHER |
706 | * * It has the fewest number of differences |
707 | * than the best match we have found so far. |
708 | * * OR it matches the fewest number of differences |
709 | * that we've seen but it has more in common. */ |
710 | diff = additive ? off : on; |
711 | same = additive ? on : off; |
712 | if (same > diff && |
713 | ((diff < mindiff) || (diff == mindiff && same > maxsame))) |
714 | { |
715 | best = j; |
716 | mindiff = diff; |
717 | maxsame = same; |
718 | } |
719 | } |
720 | |
721 | /* We didn't find a match */ |
722 | if (best == -1) break; |
723 | |
724 | sds op = sdsnewlen(additive ? "+@" : "-@" , 2); |
725 | op = sdscat(op,ACLCommandCategories[best].name); |
726 | ACLSetSelector(fake_selector,op,-1); |
727 | |
728 | sds invop = sdsnewlen(additive ? "-@" : "+@" , 2); |
729 | invop = sdscat(invop,ACLCommandCategories[best].name); |
730 | ACLSetSelector(temp_selector,invop,-1); |
731 | |
732 | rules = sdscatsds(rules,op); |
733 | rules = sdscatlen(rules," " ,1); |
734 | sdsfree(op); |
735 | sdsfree(invop); |
736 | |
737 | applied[best] = 1; |
738 | } |
739 | |
740 | /* Fix the final ACLs with single commands differences. */ |
741 | rules = ACLDescribeSelectorCommandRulesSingleCommands(selector,fake_selector,rules,server.orig_commands); |
742 | |
743 | /* Trim the final useless space. */ |
744 | sdsrange(rules,0,-2); |
745 | |
746 | /* This is technically not needed, but we want to verify that now the |
747 | * predicted bitmap is exactly the same as the user bitmap, and abort |
748 | * otherwise, because aborting is better than a security risk in this |
749 | * code path. */ |
750 | if (memcmp(fake_selector->allowed_commands, |
751 | selector->allowed_commands, |
752 | sizeof(selector->allowed_commands)) != 0) |
753 | { |
754 | serverLog(LL_WARNING, |
755 | "CRITICAL ERROR: User ACLs don't match final bitmap: '%s'" , |
756 | rules); |
757 | serverPanic("No bitmap match in ACLDescribeSelectorCommandRules()" ); |
758 | } |
759 | return rules; |
760 | } |
761 | |
762 | sds ACLDescribeSelector(aclSelector *selector) { |
763 | listIter li; |
764 | listNode *ln; |
765 | sds res = sdsempty(); |
766 | /* Key patterns. */ |
767 | if (selector->flags & SELECTOR_FLAG_ALLKEYS) { |
768 | res = sdscatlen(res,"~* " ,3); |
769 | } else { |
770 | listRewind(selector->patterns,&li); |
771 | while((ln = listNext(&li))) { |
772 | keyPattern *thispat = (keyPattern *)listNodeValue(ln); |
773 | res = sdsCatPatternString(res, thispat); |
774 | res = sdscatlen(res," " ,1); |
775 | } |
776 | } |
777 | |
778 | /* Pub/sub channel patterns. */ |
779 | if (selector->flags & SELECTOR_FLAG_ALLCHANNELS) { |
780 | res = sdscatlen(res,"&* " ,3); |
781 | } else { |
782 | res = sdscatlen(res,"resetchannels " ,14); |
783 | listRewind(selector->channels,&li); |
784 | while((ln = listNext(&li))) { |
785 | sds thispat = listNodeValue(ln); |
786 | res = sdscatlen(res,"&" ,1); |
787 | res = sdscatsds(res,thispat); |
788 | res = sdscatlen(res," " ,1); |
789 | } |
790 | } |
791 | |
792 | /* Command rules. */ |
793 | sds rules = ACLDescribeSelectorCommandRules(selector); |
794 | res = sdscatsds(res,rules); |
795 | sdsfree(rules); |
796 | return res; |
797 | } |
798 | |
799 | /* This is similar to ACLDescribeSelectorCommandRules(), however instead of |
800 | * describing just the user command rules, everything is described: user |
801 | * flags, keys, passwords and finally the command rules obtained via |
802 | * the ACLDescribeSelectorCommandRules() function. This is the function we call |
803 | * when we want to rewrite the configuration files describing ACLs and |
804 | * in order to show users with ACL LIST. */ |
805 | sds ACLDescribeUser(user *u) { |
806 | sds res = sdsempty(); |
807 | |
808 | /* Flags. */ |
809 | for (int j = 0; ACLUserFlags[j].flag; j++) { |
810 | if (u->flags & ACLUserFlags[j].flag) { |
811 | res = sdscat(res,ACLUserFlags[j].name); |
812 | res = sdscatlen(res," " ,1); |
813 | } |
814 | } |
815 | |
816 | /* Passwords. */ |
817 | listIter li; |
818 | listNode *ln; |
819 | listRewind(u->passwords,&li); |
820 | while((ln = listNext(&li))) { |
821 | sds thispass = listNodeValue(ln); |
822 | res = sdscatlen(res,"#" ,1); |
823 | res = sdscatsds(res,thispass); |
824 | res = sdscatlen(res," " ,1); |
825 | } |
826 | |
827 | /* Selectors (Commands and keys) */ |
828 | listRewind(u->selectors,&li); |
829 | while((ln = listNext(&li))) { |
830 | aclSelector *selector = (aclSelector *) listNodeValue(ln); |
831 | sds default_perm = ACLDescribeSelector(selector); |
832 | if (selector->flags & SELECTOR_FLAG_ROOT) { |
833 | res = sdscatfmt(res, "%s" , default_perm); |
834 | } else { |
835 | res = sdscatfmt(res, " (%s)" , default_perm); |
836 | } |
837 | sdsfree(default_perm); |
838 | } |
839 | return res; |
840 | } |
841 | |
842 | /* Get a command from the original command table, that is not affected |
843 | * by the command renaming operations: we base all the ACL work from that |
844 | * table, so that ACLs are valid regardless of command renaming. */ |
845 | struct redisCommand *ACLLookupCommand(const char *name) { |
846 | struct redisCommand *cmd; |
847 | sds sdsname = sdsnew(name); |
848 | cmd = lookupCommandBySdsLogic(server.orig_commands,sdsname); |
849 | sdsfree(sdsname); |
850 | return cmd; |
851 | } |
852 | |
853 | /* Flush the array of allowed first-args for the specified user |
854 | * and command ID. */ |
855 | void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) { |
856 | if (selector->allowed_firstargs && selector->allowed_firstargs[id]) { |
857 | for (int i = 0; selector->allowed_firstargs[id][i]; i++) |
858 | sdsfree(selector->allowed_firstargs[id][i]); |
859 | zfree(selector->allowed_firstargs[id]); |
860 | selector->allowed_firstargs[id] = NULL; |
861 | } |
862 | } |
863 | |
864 | /* Flush the entire table of first-args. This is useful on +@all, -@all |
865 | * or similar to return back to the minimal memory usage (and checks to do) |
866 | * for the user. */ |
867 | void ACLResetFirstArgs(aclSelector *selector) { |
868 | if (selector->allowed_firstargs == NULL) return; |
869 | for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) { |
870 | if (selector->allowed_firstargs[j]) { |
871 | for (int i = 0; selector->allowed_firstargs[j][i]; i++) |
872 | sdsfree(selector->allowed_firstargs[j][i]); |
873 | zfree(selector->allowed_firstargs[j]); |
874 | } |
875 | } |
876 | zfree(selector->allowed_firstargs); |
877 | selector->allowed_firstargs = NULL; |
878 | } |
879 | |
880 | /* Add a first-arh to the list of subcommands for the user 'u' and |
881 | * the command id specified. */ |
882 | void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) { |
883 | /* If this is the first first-arg to be configured for |
884 | * this user, we have to allocate the first-args array. */ |
885 | if (selector->allowed_firstargs == NULL) { |
886 | selector->allowed_firstargs = zcalloc(USER_COMMAND_BITS_COUNT * sizeof(sds*)); |
887 | } |
888 | |
889 | /* We also need to enlarge the allocation pointing to the |
890 | * null terminated SDS array, to make space for this one. |
891 | * To start check the current size, and while we are here |
892 | * make sure the first-arg is not already specified inside. */ |
893 | long items = 0; |
894 | if (selector->allowed_firstargs[id]) { |
895 | while(selector->allowed_firstargs[id][items]) { |
896 | /* If it's already here do not add it again. */ |
897 | if (!strcasecmp(selector->allowed_firstargs[id][items],sub)) |
898 | return; |
899 | items++; |
900 | } |
901 | } |
902 | |
903 | /* Now we can make space for the new item (and the null term). */ |
904 | items += 2; |
905 | selector->allowed_firstargs[id] = zrealloc(selector->allowed_firstargs[id], sizeof(sds)*items); |
906 | selector->allowed_firstargs[id][items-2] = sdsnew(sub); |
907 | selector->allowed_firstargs[id][items-1] = NULL; |
908 | } |
909 | |
910 | /* Create an ACL selector from the given ACL operations, which should be |
911 | * a list of space separate ACL operations that starts and ends |
912 | * with parentheses. |
913 | * |
914 | * If any of the operations are invalid, NULL will be returned instead |
915 | * and errno will be set corresponding to the interior error. */ |
916 | aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) { |
917 | serverAssert(opset[0] == '(' && opset[opsetlen - 1] == ')'); |
918 | aclSelector *s = ACLCreateSelector(0); |
919 | |
920 | int argc = 0; |
921 | sds trimmed = sdsnewlen(opset + 1, opsetlen - 2); |
922 | sds *argv = sdssplitargs(trimmed, &argc); |
923 | for (int i = 0; i < argc; i++) { |
924 | if (ACLSetSelector(s, argv[i], sdslen(argv[i])) == C_ERR) { |
925 | ACLFreeSelector(s); |
926 | s = NULL; |
927 | goto cleanup; |
928 | } |
929 | } |
930 | |
931 | cleanup: |
932 | sdsfreesplitres(argv, argc); |
933 | sdsfree(trimmed); |
934 | return s; |
935 | } |
936 | |
937 | /* Set a selector's properties with the provided 'op'. |
938 | * |
939 | * +<command> Allow the execution of that command. |
940 | * May be used with `|` for allowing subcommands (e.g "+config|get") |
941 | * -<command> Disallow the execution of that command. |
942 | * May be used with `|` for blocking subcommands (e.g "-config|set") |
943 | * +@<category> Allow the execution of all the commands in such category |
944 | * with valid categories are like @admin, @set, @sortedset, ... |
945 | * and so forth, see the full list in the server.c file where |
946 | * the Redis command table is described and defined. |
947 | * The special category @all means all the commands, but currently |
948 | * present in the server, and that will be loaded in the future |
949 | * via modules. |
950 | * +<command>|first-arg Allow a specific first argument of an otherwise |
951 | * disabled command. Note that this form is not |
952 | * allowed as negative like -SELECT|1, but |
953 | * only additive starting with "+". |
954 | * allcommands Alias for +@all. Note that it implies the ability to execute |
955 | * all the future commands loaded via the modules system. |
956 | * nocommands Alias for -@all. |
957 | * ~<pattern> Add a pattern of keys that can be mentioned as part of |
958 | * commands. For instance ~* allows all the keys. The pattern |
959 | * is a glob-style pattern like the one of KEYS. |
960 | * It is possible to specify multiple patterns. |
961 | * %R~<pattern> Add key read pattern that specifies which keys can be read |
962 | * from. |
963 | * %W~<pattern> Add key write pattern that specifies which keys can be |
964 | * written to. |
965 | * allkeys Alias for ~* |
966 | * resetkeys Flush the list of allowed keys patterns. |
967 | * &<pattern> Add a pattern of channels that can be mentioned as part of |
968 | * Pub/Sub commands. For instance &* allows all the channels. The |
969 | * pattern is a glob-style pattern like the one of PSUBSCRIBE. |
970 | * It is possible to specify multiple patterns. |
971 | * allchannels Alias for &* |
972 | * resetchannels Flush the list of allowed channel patterns. |
973 | */ |
974 | int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) { |
975 | if (!strcasecmp(op,"allkeys" ) || |
976 | !strcasecmp(op,"~*" )) |
977 | { |
978 | selector->flags |= SELECTOR_FLAG_ALLKEYS; |
979 | listEmpty(selector->patterns); |
980 | } else if (!strcasecmp(op,"resetkeys" )) { |
981 | selector->flags &= ~SELECTOR_FLAG_ALLKEYS; |
982 | listEmpty(selector->patterns); |
983 | } else if (!strcasecmp(op,"allchannels" ) || |
984 | !strcasecmp(op,"&*" )) |
985 | { |
986 | selector->flags |= SELECTOR_FLAG_ALLCHANNELS; |
987 | listEmpty(selector->channels); |
988 | } else if (!strcasecmp(op,"resetchannels" )) { |
989 | selector->flags &= ~SELECTOR_FLAG_ALLCHANNELS; |
990 | listEmpty(selector->channels); |
991 | } else if (!strcasecmp(op,"allcommands" ) || |
992 | !strcasecmp(op,"+@all" )) |
993 | { |
994 | memset(selector->allowed_commands,255,sizeof(selector->allowed_commands)); |
995 | selector->flags |= SELECTOR_FLAG_ALLCOMMANDS; |
996 | ACLResetFirstArgs(selector); |
997 | } else if (!strcasecmp(op,"nocommands" ) || |
998 | !strcasecmp(op,"-@all" )) |
999 | { |
1000 | memset(selector->allowed_commands,0,sizeof(selector->allowed_commands)); |
1001 | selector->flags &= ~SELECTOR_FLAG_ALLCOMMANDS; |
1002 | ACLResetFirstArgs(selector); |
1003 | } else if (op[0] == '~' || op[0] == '%') { |
1004 | if (selector->flags & SELECTOR_FLAG_ALLKEYS) { |
1005 | errno = EEXIST; |
1006 | return C_ERR; |
1007 | } |
1008 | int flags = 0; |
1009 | size_t offset = 1; |
1010 | if (op[0] == '%') { |
1011 | for (; offset < oplen; offset++) { |
1012 | if (toupper(op[offset]) == 'R' && !(flags & ACL_READ_PERMISSION)) { |
1013 | flags |= ACL_READ_PERMISSION; |
1014 | } else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) { |
1015 | flags |= ACL_WRITE_PERMISSION; |
1016 | } else if (op[offset] == '~') { |
1017 | offset++; |
1018 | break; |
1019 | } else { |
1020 | errno = EINVAL; |
1021 | return C_ERR; |
1022 | } |
1023 | } |
1024 | } else { |
1025 | flags = ACL_ALL_PERMISSION; |
1026 | } |
1027 | |
1028 | if (ACLStringHasSpaces(op+offset,oplen-offset)) { |
1029 | errno = EINVAL; |
1030 | return C_ERR; |
1031 | } |
1032 | keyPattern *newpat = ACLKeyPatternCreate(sdsnewlen(op+offset,oplen-offset), flags); |
1033 | listNode *ln = listSearchKey(selector->patterns,newpat); |
1034 | /* Avoid re-adding the same key pattern multiple times. */ |
1035 | if (ln == NULL) { |
1036 | listAddNodeTail(selector->patterns,newpat); |
1037 | } else { |
1038 | ((keyPattern *)listNodeValue(ln))->flags |= flags; |
1039 | ACLKeyPatternFree(newpat); |
1040 | } |
1041 | selector->flags &= ~SELECTOR_FLAG_ALLKEYS; |
1042 | } else if (op[0] == '&') { |
1043 | if (selector->flags & SELECTOR_FLAG_ALLCHANNELS) { |
1044 | errno = EISDIR; |
1045 | return C_ERR; |
1046 | } |
1047 | if (ACLStringHasSpaces(op+1,oplen-1)) { |
1048 | errno = EINVAL; |
1049 | return C_ERR; |
1050 | } |
1051 | sds newpat = sdsnewlen(op+1,oplen-1); |
1052 | listNode *ln = listSearchKey(selector->channels,newpat); |
1053 | /* Avoid re-adding the same channel pattern multiple times. */ |
1054 | if (ln == NULL) |
1055 | listAddNodeTail(selector->channels,newpat); |
1056 | else |
1057 | sdsfree(newpat); |
1058 | selector->flags &= ~SELECTOR_FLAG_ALLCHANNELS; |
1059 | } else if (op[0] == '+' && op[1] != '@') { |
1060 | if (strrchr(op,'|') == NULL) { |
1061 | struct redisCommand *cmd = ACLLookupCommand(op+1); |
1062 | if (cmd == NULL) { |
1063 | errno = ENOENT; |
1064 | return C_ERR; |
1065 | } |
1066 | ACLChangeSelectorPerm(selector,cmd,1); |
1067 | } else { |
1068 | /* Split the command and subcommand parts. */ |
1069 | char *copy = zstrdup(op+1); |
1070 | char *sub = strrchr(copy,'|'); |
1071 | sub[0] = '\0'; |
1072 | sub++; |
1073 | |
1074 | struct redisCommand *cmd = ACLLookupCommand(copy); |
1075 | |
1076 | /* Check if the command exists. We can't check the |
1077 | * first-arg to see if it is valid. */ |
1078 | if (cmd == NULL) { |
1079 | zfree(copy); |
1080 | errno = ENOENT; |
1081 | return C_ERR; |
1082 | } |
1083 | |
1084 | /* We do not support allowing first-arg of a subcommand */ |
1085 | if (cmd->parent) { |
1086 | zfree(copy); |
1087 | errno = ECHILD; |
1088 | return C_ERR; |
1089 | } |
1090 | |
1091 | /* The subcommand cannot be empty, so things like DEBUG| |
1092 | * are syntax errors of course. */ |
1093 | if (strlen(sub) == 0) { |
1094 | zfree(copy); |
1095 | errno = EINVAL; |
1096 | return C_ERR; |
1097 | } |
1098 | |
1099 | if (cmd->subcommands_dict) { |
1100 | /* If user is trying to allow a valid subcommand we can just add its unique ID */ |
1101 | cmd = ACLLookupCommand(op+1); |
1102 | if (cmd == NULL) { |
1103 | zfree(copy); |
1104 | errno = ENOENT; |
1105 | return C_ERR; |
1106 | } |
1107 | ACLChangeSelectorPerm(selector,cmd,1); |
1108 | } else { |
1109 | /* If user is trying to use the ACL mech to block SELECT except SELECT 0 or |
1110 | * block DEBUG except DEBUG OBJECT (DEBUG subcommands are not considered |
1111 | * subcommands for now) we use the allowed_firstargs mechanism. */ |
1112 | |
1113 | /* Add the first-arg to the list of valid ones. */ |
1114 | serverLog(LL_WARNING, "Deprecation warning: Allowing a first arg of an otherwise " |
1115 | "blocked command is a misuse of ACL and may get disabled " |
1116 | "in the future (offender: +%s)" , op+1); |
1117 | ACLAddAllowedFirstArg(selector,cmd->id,sub); |
1118 | } |
1119 | |
1120 | zfree(copy); |
1121 | } |
1122 | } else if (op[0] == '-' && op[1] != '@') { |
1123 | struct redisCommand *cmd = ACLLookupCommand(op+1); |
1124 | if (cmd == NULL) { |
1125 | errno = ENOENT; |
1126 | return C_ERR; |
1127 | } |
1128 | ACLChangeSelectorPerm(selector,cmd,0); |
1129 | } else if ((op[0] == '+' || op[0] == '-') && op[1] == '@') { |
1130 | int bitval = op[0] == '+' ? 1 : 0; |
1131 | if (ACLSetSelectorCommandBitsForCategory(selector,op+2,bitval) == C_ERR) { |
1132 | errno = ENOENT; |
1133 | return C_ERR; |
1134 | } |
1135 | } else { |
1136 | errno = EINVAL; |
1137 | return C_ERR; |
1138 | } |
1139 | return C_OK; |
1140 | } |
1141 | |
1142 | /* Set user properties according to the string "op". The following |
1143 | * is a description of what different strings will do: |
1144 | * |
1145 | * on Enable the user: it is possible to authenticate as this user. |
1146 | * off Disable the user: it's no longer possible to authenticate |
1147 | * with this user, however the already authenticated connections |
1148 | * will still work. |
1149 | * ><password> Add this password to the list of valid password for the user. |
1150 | * For example >mypass will add "mypass" to the list. |
1151 | * This directive clears the "nopass" flag (see later). |
1152 | * #<hash> Add this password hash to the list of valid hashes for |
1153 | * the user. This is useful if you have previously computed |
1154 | * the hash, and don't want to store it in plaintext. |
1155 | * This directive clears the "nopass" flag (see later). |
1156 | * <<password> Remove this password from the list of valid passwords. |
1157 | * !<hash> Remove this hashed password from the list of valid passwords. |
1158 | * This is useful when you want to remove a password just by |
1159 | * hash without knowing its plaintext version at all. |
1160 | * nopass All the set passwords of the user are removed, and the user |
1161 | * is flagged as requiring no password: it means that every |
1162 | * password will work against this user. If this directive is |
1163 | * used for the default user, every new connection will be |
1164 | * immediately authenticated with the default user without |
1165 | * any explicit AUTH command required. Note that the "resetpass" |
1166 | * directive will clear this condition. |
1167 | * resetpass Flush the list of allowed passwords. Moreover removes the |
1168 | * "nopass" status. After "resetpass" the user has no associated |
1169 | * passwords and there is no way to authenticate without adding |
1170 | * some password (or setting it as "nopass" later). |
1171 | * reset Performs the following actions: resetpass, resetkeys, off, |
1172 | * -@all. The user returns to the same state it has immediately |
1173 | * after its creation. |
1174 | * (<options>) Create a new selector with the options specified within the |
1175 | * parentheses and attach it to the user. Each option should be |
1176 | * space separated. The first character must be ( and the last |
1177 | * character must be ). |
1178 | * clearselectors Remove all of the currently attached selectors. |
1179 | * Note this does not change the "root" user permissions, |
1180 | * which are the permissions directly applied onto the |
1181 | * user (outside the parentheses). |
1182 | * |
1183 | * Selector options can also be specified by this function, in which case |
1184 | * they update the root selector for the user. |
1185 | * |
1186 | * The 'op' string must be null terminated. The 'oplen' argument should |
1187 | * specify the length of the 'op' string in case the caller requires to pass |
1188 | * binary data (for instance the >password form may use a binary password). |
1189 | * Otherwise the field can be set to -1 and the function will use strlen() |
1190 | * to determine the length. |
1191 | * |
1192 | * The function returns C_OK if the action to perform was understood because |
1193 | * the 'op' string made sense. Otherwise C_ERR is returned if the operation |
1194 | * is unknown or has some syntax error. |
1195 | * |
1196 | * When an error is returned, errno is set to the following values: |
1197 | * |
1198 | * EINVAL: The specified opcode is not understood or the key/channel pattern is |
1199 | * invalid (contains non allowed characters). |
1200 | * ENOENT: The command name or command category provided with + or - is not |
1201 | * known. |
1202 | * EEXIST: You are adding a key pattern after "*" was already added. This is |
1203 | * almost surely an error on the user side. |
1204 | * EISDIR: You are adding a channel pattern after "*" was already added. This is |
1205 | * almost surely an error on the user side. |
1206 | * ENODEV: The password you are trying to remove from the user does not exist. |
1207 | * EBADMSG: The hash you are trying to add is not a valid hash. |
1208 | * ECHILD: Attempt to allow a specific first argument of a subcommand |
1209 | */ |
1210 | int ACLSetUser(user *u, const char *op, ssize_t oplen) { |
1211 | if (oplen == -1) oplen = strlen(op); |
1212 | if (oplen == 0) return C_OK; /* Empty string is a no-operation. */ |
1213 | if (!strcasecmp(op,"on" )) { |
1214 | u->flags |= USER_FLAG_ENABLED; |
1215 | u->flags &= ~USER_FLAG_DISABLED; |
1216 | } else if (!strcasecmp(op,"off" )) { |
1217 | u->flags |= USER_FLAG_DISABLED; |
1218 | u->flags &= ~USER_FLAG_ENABLED; |
1219 | } else if (!strcasecmp(op,"skip-sanitize-payload" )) { |
1220 | u->flags |= USER_FLAG_SANITIZE_PAYLOAD_SKIP; |
1221 | u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD; |
1222 | } else if (!strcasecmp(op,"sanitize-payload" )) { |
1223 | u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD_SKIP; |
1224 | u->flags |= USER_FLAG_SANITIZE_PAYLOAD; |
1225 | } else if (!strcasecmp(op,"nopass" )) { |
1226 | u->flags |= USER_FLAG_NOPASS; |
1227 | listEmpty(u->passwords); |
1228 | } else if (!strcasecmp(op,"resetpass" )) { |
1229 | u->flags &= ~USER_FLAG_NOPASS; |
1230 | listEmpty(u->passwords); |
1231 | } else if (op[0] == '>' || op[0] == '#') { |
1232 | sds newpass; |
1233 | if (op[0] == '>') { |
1234 | newpass = ACLHashPassword((unsigned char*)op+1,oplen-1); |
1235 | } else { |
1236 | if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) { |
1237 | errno = EBADMSG; |
1238 | return C_ERR; |
1239 | } |
1240 | newpass = sdsnewlen(op+1,oplen-1); |
1241 | } |
1242 | |
1243 | listNode *ln = listSearchKey(u->passwords,newpass); |
1244 | /* Avoid re-adding the same password multiple times. */ |
1245 | if (ln == NULL) |
1246 | listAddNodeTail(u->passwords,newpass); |
1247 | else |
1248 | sdsfree(newpass); |
1249 | u->flags &= ~USER_FLAG_NOPASS; |
1250 | } else if (op[0] == '<' || op[0] == '!') { |
1251 | sds delpass; |
1252 | if (op[0] == '<') { |
1253 | delpass = ACLHashPassword((unsigned char*)op+1,oplen-1); |
1254 | } else { |
1255 | if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) { |
1256 | errno = EBADMSG; |
1257 | return C_ERR; |
1258 | } |
1259 | delpass = sdsnewlen(op+1,oplen-1); |
1260 | } |
1261 | listNode *ln = listSearchKey(u->passwords,delpass); |
1262 | sdsfree(delpass); |
1263 | if (ln) { |
1264 | listDelNode(u->passwords,ln); |
1265 | } else { |
1266 | errno = ENODEV; |
1267 | return C_ERR; |
1268 | } |
1269 | } else if (op[0] == '(' && op[oplen - 1] == ')') { |
1270 | aclSelector *selector = aclCreateSelectorFromOpSet(op, oplen); |
1271 | if (!selector) { |
1272 | /* No errorno set, propagate it from interior error. */ |
1273 | return C_ERR; |
1274 | } |
1275 | listAddNodeTail(u->selectors, selector); |
1276 | return C_OK; |
1277 | } else if (!strcasecmp(op,"clearselectors" )) { |
1278 | listIter li; |
1279 | listNode *ln; |
1280 | listRewind(u->selectors,&li); |
1281 | /* There has to be a root selector */ |
1282 | serverAssert(listNext(&li)); |
1283 | while((ln = listNext(&li))) { |
1284 | listDelNode(u->selectors, ln); |
1285 | } |
1286 | return C_OK; |
1287 | } else if (!strcasecmp(op,"reset" )) { |
1288 | serverAssert(ACLSetUser(u,"resetpass" ,-1) == C_OK); |
1289 | serverAssert(ACLSetUser(u,"resetkeys" ,-1) == C_OK); |
1290 | serverAssert(ACLSetUser(u,"resetchannels" ,-1) == C_OK); |
1291 | if (server.acl_pubsub_default & SELECTOR_FLAG_ALLCHANNELS) |
1292 | serverAssert(ACLSetUser(u,"allchannels" ,-1) == C_OK); |
1293 | serverAssert(ACLSetUser(u,"off" ,-1) == C_OK); |
1294 | serverAssert(ACLSetUser(u,"sanitize-payload" ,-1) == C_OK); |
1295 | serverAssert(ACLSetUser(u,"clearselectors" ,-1) == C_OK); |
1296 | serverAssert(ACLSetUser(u,"-@all" ,-1) == C_OK); |
1297 | } else { |
1298 | aclSelector *selector = ACLUserGetRootSelector(u); |
1299 | if (ACLSetSelector(selector, op, oplen) == C_ERR) { |
1300 | return C_ERR; |
1301 | } |
1302 | } |
1303 | return C_OK; |
1304 | } |
1305 | |
1306 | /* Return a description of the error that occurred in ACLSetUser() according to |
1307 | * the errno value set by the function on error. */ |
1308 | const char *ACLSetUserStringError(void) { |
1309 | const char *errmsg = "Wrong format" ; |
1310 | if (errno == ENOENT) |
1311 | errmsg = "Unknown command or category name in ACL" ; |
1312 | else if (errno == EINVAL) |
1313 | errmsg = "Syntax error" ; |
1314 | else if (errno == EEXIST) |
1315 | errmsg = "Adding a pattern after the * pattern (or the " |
1316 | "'allkeys' flag) is not valid and does not have any " |
1317 | "effect. Try 'resetkeys' to start with an empty " |
1318 | "list of patterns" ; |
1319 | else if (errno == EISDIR) |
1320 | errmsg = "Adding a pattern after the * pattern (or the " |
1321 | "'allchannels' flag) is not valid and does not have any " |
1322 | "effect. Try 'resetchannels' to start with an empty " |
1323 | "list of channels" ; |
1324 | else if (errno == ENODEV) |
1325 | errmsg = "The password you are trying to remove from the user does " |
1326 | "not exist" ; |
1327 | else if (errno == EBADMSG) |
1328 | errmsg = "The password hash must be exactly 64 characters and contain " |
1329 | "only lowercase hexadecimal characters" ; |
1330 | else if (errno == EALREADY) |
1331 | errmsg = "Duplicate user found. A user can only be defined once in " |
1332 | "config files" ; |
1333 | else if (errno == ECHILD) |
1334 | errmsg = "Allowing first-arg of a subcommand is not supported" ; |
1335 | return errmsg; |
1336 | } |
1337 | |
1338 | /* Create the default user, this has special permissions. */ |
1339 | user *ACLCreateDefaultUser(void) { |
1340 | user *new = ACLCreateUser("default" ,7); |
1341 | ACLSetUser(new,"+@all" ,-1); |
1342 | ACLSetUser(new,"~*" ,-1); |
1343 | ACLSetUser(new,"&*" ,-1); |
1344 | ACLSetUser(new,"on" ,-1); |
1345 | ACLSetUser(new,"nopass" ,-1); |
1346 | return new; |
1347 | } |
1348 | |
1349 | /* Initialization of the ACL subsystem. */ |
1350 | void ACLInit(void) { |
1351 | Users = raxNew(); |
1352 | UsersToLoad = listCreate(); |
1353 | listSetMatchMethod(UsersToLoad, ACLListMatchLoadedUser); |
1354 | ACLLog = listCreate(); |
1355 | DefaultUser = ACLCreateDefaultUser(); |
1356 | } |
1357 | |
1358 | /* Check the username and password pair and return C_OK if they are valid, |
1359 | * otherwise C_ERR is returned and errno is set to: |
1360 | * |
1361 | * EINVAL: if the username-password do not match. |
1362 | * ENONENT: if the specified user does not exist at all. |
1363 | */ |
1364 | int ACLCheckUserCredentials(robj *username, robj *password) { |
1365 | user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr)); |
1366 | if (u == NULL) { |
1367 | errno = ENOENT; |
1368 | return C_ERR; |
1369 | } |
1370 | |
1371 | /* Disabled users can't login. */ |
1372 | if (u->flags & USER_FLAG_DISABLED) { |
1373 | errno = EINVAL; |
1374 | return C_ERR; |
1375 | } |
1376 | |
1377 | /* If the user is configured to don't require any password, we |
1378 | * are already fine here. */ |
1379 | if (u->flags & USER_FLAG_NOPASS) return C_OK; |
1380 | |
1381 | /* Check all the user passwords for at least one to match. */ |
1382 | listIter li; |
1383 | listNode *ln; |
1384 | listRewind(u->passwords,&li); |
1385 | sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr)); |
1386 | while((ln = listNext(&li))) { |
1387 | sds thispass = listNodeValue(ln); |
1388 | if (!time_independent_strcmp(hashed, thispass, HASH_PASSWORD_LEN)) { |
1389 | sdsfree(hashed); |
1390 | return C_OK; |
1391 | } |
1392 | } |
1393 | sdsfree(hashed); |
1394 | |
1395 | /* If we reached this point, no password matched. */ |
1396 | errno = EINVAL; |
1397 | return C_ERR; |
1398 | } |
1399 | |
1400 | /* This is like ACLCheckUserCredentials(), however if the user/pass |
1401 | * are correct, the connection is put in authenticated state and the |
1402 | * connection user reference is populated. |
1403 | * |
1404 | * The return value is C_OK or C_ERR with the same meaning as |
1405 | * ACLCheckUserCredentials(). */ |
1406 | int ACLAuthenticateUser(client *c, robj *username, robj *password) { |
1407 | if (ACLCheckUserCredentials(username,password) == C_OK) { |
1408 | c->authenticated = 1; |
1409 | c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr)); |
1410 | moduleNotifyUserChanged(c); |
1411 | return C_OK; |
1412 | } else { |
1413 | addACLLogEntry(c,ACL_DENIED_AUTH,(c->flags & CLIENT_MULTI) ? ACL_LOG_CTX_MULTI : ACL_LOG_CTX_TOPLEVEL,0,username->ptr,NULL); |
1414 | return C_ERR; |
1415 | } |
1416 | } |
1417 | |
1418 | /* For ACL purposes, every user has a bitmap with the commands that such |
1419 | * user is allowed to execute. In order to populate the bitmap, every command |
1420 | * should have an assigned ID (that is used to index the bitmap). This function |
1421 | * creates such an ID: it uses sequential IDs, reusing the same ID for the same |
1422 | * command name, so that a command retains the same ID in case of modules that |
1423 | * are unloaded and later reloaded. |
1424 | * |
1425 | * The function does not take ownership of the 'cmdname' SDS string. |
1426 | * */ |
1427 | unsigned long ACLGetCommandID(sds cmdname) { |
1428 | sds lowername = sdsdup(cmdname); |
1429 | sdstolower(lowername); |
1430 | if (commandId == NULL) commandId = raxNew(); |
1431 | void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername)); |
1432 | if (id != raxNotFound) { |
1433 | sdsfree(lowername); |
1434 | return (unsigned long)id; |
1435 | } |
1436 | raxInsert(commandId,(unsigned char*)lowername,strlen(lowername), |
1437 | (void*)nextid,NULL); |
1438 | sdsfree(lowername); |
1439 | unsigned long thisid = nextid; |
1440 | nextid++; |
1441 | |
1442 | /* We never assign the last bit in the user commands bitmap structure, |
1443 | * this way we can later check if this bit is set, understanding if the |
1444 | * current ACL for the user was created starting with a +@all to add all |
1445 | * the possible commands and just subtracting other single commands or |
1446 | * categories, or if, instead, the ACL was created just adding commands |
1447 | * and command categories from scratch, not allowing future commands by |
1448 | * default (loaded via modules). This is useful when rewriting the ACLs |
1449 | * with ACL SAVE. */ |
1450 | if (nextid == USER_COMMAND_BITS_COUNT-1) nextid++; |
1451 | return thisid; |
1452 | } |
1453 | |
1454 | /* Clear command id table and reset nextid to 0. */ |
1455 | void ACLClearCommandID(void) { |
1456 | if (commandId) raxFree(commandId); |
1457 | commandId = NULL; |
1458 | nextid = 0; |
1459 | } |
1460 | |
1461 | /* Return an username by its name, or NULL if the user does not exist. */ |
1462 | user *ACLGetUserByName(const char *name, size_t namelen) { |
1463 | void *myuser = raxFind(Users,(unsigned char*)name,namelen); |
1464 | if (myuser == raxNotFound) return NULL; |
1465 | return myuser; |
1466 | } |
1467 | |
1468 | /* ============================================================================= |
1469 | * ACL permission checks |
1470 | * ==========================================================================*/ |
1471 | |
1472 | /* Check if the key can be accessed by the selector. |
1473 | * |
1474 | * If the selector can access the key, ACL_OK is returned, otherwise |
1475 | * ACL_DENIED_KEY is returned. */ |
1476 | static int ACLSelectorCheckKey(aclSelector *selector, const char *key, int keylen, int keyspec_flags) { |
1477 | /* The selector can access any key */ |
1478 | if (selector->flags & SELECTOR_FLAG_ALLKEYS) return ACL_OK; |
1479 | |
1480 | listIter li; |
1481 | listNode *ln; |
1482 | listRewind(selector->patterns,&li); |
1483 | |
1484 | int key_flags = 0; |
1485 | if (keyspec_flags & CMD_KEY_ACCESS) key_flags |= ACL_READ_PERMISSION; |
1486 | if (keyspec_flags & CMD_KEY_INSERT) key_flags |= ACL_WRITE_PERMISSION; |
1487 | if (keyspec_flags & CMD_KEY_DELETE) key_flags |= ACL_WRITE_PERMISSION; |
1488 | if (keyspec_flags & CMD_KEY_UPDATE) key_flags |= ACL_WRITE_PERMISSION; |
1489 | |
1490 | /* Test this key against every pattern. */ |
1491 | while((ln = listNext(&li))) { |
1492 | keyPattern *pattern = listNodeValue(ln); |
1493 | if ((pattern->flags & key_flags) != key_flags) |
1494 | continue; |
1495 | size_t plen = sdslen(pattern->pattern); |
1496 | if (stringmatchlen(pattern->pattern,plen,key,keylen,0)) |
1497 | return ACL_OK; |
1498 | } |
1499 | return ACL_DENIED_KEY; |
1500 | } |
1501 | |
1502 | /* Checks if the provided selector selector has access specified in flags |
1503 | * to all keys in the keyspace. For example, CMD_KEY_READ access requires either |
1504 | * '%R~*', '~*', or allkeys to be granted to the selector. Returns 1 if all |
1505 | * the access flags are satisfied with this selector or 0 otherwise. |
1506 | */ |
1507 | static int ACLSelectorHasUnrestrictedKeyAccess(aclSelector *selector, int flags) { |
1508 | /* The selector can access any key */ |
1509 | if (selector->flags & SELECTOR_FLAG_ALLKEYS) return 1; |
1510 | |
1511 | listIter li; |
1512 | listNode *ln; |
1513 | listRewind(selector->patterns,&li); |
1514 | |
1515 | int access_flags = 0; |
1516 | if (flags & CMD_KEY_ACCESS) access_flags |= ACL_READ_PERMISSION; |
1517 | if (flags & CMD_KEY_INSERT) access_flags |= ACL_WRITE_PERMISSION; |
1518 | if (flags & CMD_KEY_DELETE) access_flags |= ACL_WRITE_PERMISSION; |
1519 | if (flags & CMD_KEY_UPDATE) access_flags |= ACL_WRITE_PERMISSION; |
1520 | |
1521 | /* Test this key against every pattern. */ |
1522 | while((ln = listNext(&li))) { |
1523 | keyPattern *pattern = listNodeValue(ln); |
1524 | if ((pattern->flags & access_flags) != access_flags) |
1525 | continue; |
1526 | if (!strcmp(pattern->pattern,"*" )) { |
1527 | return 1; |
1528 | } |
1529 | } |
1530 | return 0; |
1531 | } |
1532 | |
1533 | /* Checks a channel against a provided list of channels. The is_pattern |
1534 | * argument should only be used when subscribing (not when publishing) |
1535 | * and controls whether the input channel is evaluated as a channel pattern |
1536 | * (like in PSUBSCRIBE) or a plain channel name (like in SUBSCRIBE). |
1537 | * |
1538 | * Note that a plain channel name like in PUBLISH or SUBSCRIBE can be |
1539 | * matched against ACL channel patterns, but the pattern provided in PSUBSCRIBE |
1540 | * can only be matched as a literal against an ACL pattern (using plain string compare). */ |
1541 | static int ACLCheckChannelAgainstList(list *reference, const char *channel, int channellen, int is_pattern) { |
1542 | listIter li; |
1543 | listNode *ln; |
1544 | |
1545 | listRewind(reference, &li); |
1546 | while((ln = listNext(&li))) { |
1547 | sds pattern = listNodeValue(ln); |
1548 | size_t plen = sdslen(pattern); |
1549 | /* Channel patterns are matched literally against the channels in |
1550 | * the list. Regular channels perform pattern matching. */ |
1551 | if ((is_pattern && !strcmp(pattern,channel)) || |
1552 | (!is_pattern && stringmatchlen(pattern,plen,channel,channellen,0))) |
1553 | { |
1554 | return ACL_OK; |
1555 | } |
1556 | } |
1557 | return ACL_DENIED_CHANNEL; |
1558 | } |
1559 | |
1560 | /* To prevent duplicate calls to getKeysResult, a cache is maintained |
1561 | * in between calls to the various selectors. */ |
1562 | typedef struct { |
1563 | int keys_init; |
1564 | getKeysResult keys; |
1565 | } aclKeyResultCache; |
1566 | |
1567 | void initACLKeyResultCache(aclKeyResultCache *cache) { |
1568 | cache->keys_init = 0; |
1569 | } |
1570 | |
1571 | void cleanupACLKeyResultCache(aclKeyResultCache *cache) { |
1572 | if (cache->keys_init) getKeysFreeResult(&(cache->keys)); |
1573 | } |
1574 | |
1575 | /* Check if the command is ready to be executed according to the |
1576 | * ACLs associated with the specified selector. |
1577 | * |
1578 | * If the selector can execute the command ACL_OK is returned, otherwise |
1579 | * ACL_DENIED_CMD, ACL_DENIED_KEY, or ACL_DENIED_CHANNEL is returned: the first in case the |
1580 | * command cannot be executed because the selector is not allowed to run such |
1581 | * command, the second and third if the command is denied because the selector is trying |
1582 | * to access a key or channel that are not among the specified patterns. */ |
1583 | static int ACLSelectorCheckCmd(aclSelector *selector, struct redisCommand *cmd, robj **argv, int argc, int *keyidxptr, aclKeyResultCache *cache) { |
1584 | uint64_t id = cmd->id; |
1585 | int ret; |
1586 | if (!(selector->flags & SELECTOR_FLAG_ALLCOMMANDS) && !(cmd->flags & CMD_NO_AUTH)) { |
1587 | /* If the bit is not set we have to check further, in case the |
1588 | * command is allowed just with that specific first argument. */ |
1589 | if (ACLGetSelectorCommandBit(selector,id) == 0) { |
1590 | /* Check if the first argument matches. */ |
1591 | if (argc < 2 || |
1592 | selector->allowed_firstargs == NULL || |
1593 | selector->allowed_firstargs[id] == NULL) |
1594 | { |
1595 | return ACL_DENIED_CMD; |
1596 | } |
1597 | |
1598 | long subid = 0; |
1599 | while (1) { |
1600 | if (selector->allowed_firstargs[id][subid] == NULL) |
1601 | return ACL_DENIED_CMD; |
1602 | int idx = cmd->parent ? 2 : 1; |
1603 | if (!strcasecmp(argv[idx]->ptr,selector->allowed_firstargs[id][subid])) |
1604 | break; /* First argument match found. Stop here. */ |
1605 | subid++; |
1606 | } |
1607 | } |
1608 | } |
1609 | |
1610 | /* Check if the user can execute commands explicitly touching the keys |
1611 | * mentioned in the command arguments. */ |
1612 | if (!(selector->flags & SELECTOR_FLAG_ALLKEYS) && doesCommandHaveKeys(cmd)) { |
1613 | if (!(cache->keys_init)) { |
1614 | cache->keys = (getKeysResult) GETKEYS_RESULT_INIT; |
1615 | getKeysFromCommandWithSpecs(cmd, argv, argc, GET_KEYSPEC_DEFAULT, &(cache->keys)); |
1616 | cache->keys_init = 1; |
1617 | } |
1618 | getKeysResult *result = &(cache->keys); |
1619 | keyReference *resultidx = result->keys; |
1620 | for (int j = 0; j < result->numkeys; j++) { |
1621 | int idx = resultidx[j].pos; |
1622 | ret = ACLSelectorCheckKey(selector, argv[idx]->ptr, sdslen(argv[idx]->ptr), resultidx[j].flags); |
1623 | if (ret != ACL_OK) { |
1624 | if (keyidxptr) *keyidxptr = resultidx[j].pos; |
1625 | return ret; |
1626 | } |
1627 | } |
1628 | } |
1629 | |
1630 | /* Check if the user can execute commands explicitly touching the channels |
1631 | * mentioned in the command arguments */ |
1632 | const int channel_flags = CMD_CHANNEL_PUBLISH | CMD_CHANNEL_SUBSCRIBE; |
1633 | if (!(selector->flags & SELECTOR_FLAG_ALLCHANNELS) && doesCommandHaveChannelsWithFlags(cmd, channel_flags)) { |
1634 | getKeysResult channels = (getKeysResult) GETKEYS_RESULT_INIT; |
1635 | getChannelsFromCommand(cmd, argv, argc, &channels); |
1636 | keyReference *channelref = channels.keys; |
1637 | for (int j = 0; j < channels.numkeys; j++) { |
1638 | int idx = channelref[j].pos; |
1639 | if (!(channelref[j].flags & channel_flags)) continue; |
1640 | int is_pattern = channelref[j].flags & CMD_CHANNEL_PATTERN; |
1641 | int ret = ACLCheckChannelAgainstList(selector->channels, argv[idx]->ptr, sdslen(argv[idx]->ptr), is_pattern); |
1642 | if (ret != ACL_OK) { |
1643 | if (keyidxptr) *keyidxptr = channelref[j].pos; |
1644 | getKeysFreeResult(&channels); |
1645 | return ret; |
1646 | } |
1647 | } |
1648 | getKeysFreeResult(&channels); |
1649 | } |
1650 | return ACL_OK; |
1651 | } |
1652 | |
1653 | /* Check if the key can be accessed by the client according to |
1654 | * the ACLs associated with the specified user according to the |
1655 | * keyspec access flags. |
1656 | * |
1657 | * If the user can access the key, ACL_OK is returned, otherwise |
1658 | * ACL_DENIED_KEY is returned. */ |
1659 | int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags) { |
1660 | listIter li; |
1661 | listNode *ln; |
1662 | |
1663 | /* If there is no associated user, the connection can run anything. */ |
1664 | if (u == NULL) return ACL_OK; |
1665 | |
1666 | /* Check all of the selectors */ |
1667 | listRewind(u->selectors,&li); |
1668 | while((ln = listNext(&li))) { |
1669 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1670 | if (ACLSelectorCheckKey(s, key, keylen, flags) == ACL_OK) { |
1671 | return ACL_OK; |
1672 | } |
1673 | } |
1674 | return ACL_DENIED_KEY; |
1675 | } |
1676 | |
1677 | /* Checks if the user can execute the given command with the added restriction |
1678 | * it must also have the access specified in flags to any key in the key space. |
1679 | * For example, CMD_KEY_READ access requires either '%R~*', '~*', or allkeys to be |
1680 | * granted in addition to the access required by the command. Returns 1 |
1681 | * if the user has access or 0 otherwise. |
1682 | */ |
1683 | int ACLUserCheckCmdWithUnrestrictedKeyAccess(user *u, struct redisCommand *cmd, robj **argv, int argc, int flags) { |
1684 | listIter li; |
1685 | listNode *ln; |
1686 | int local_idxptr; |
1687 | |
1688 | /* If there is no associated user, the connection can run anything. */ |
1689 | if (u == NULL) return 1; |
1690 | |
1691 | /* For multiple selectors, we cache the key result in between selector |
1692 | * calls to prevent duplicate lookups. */ |
1693 | aclKeyResultCache cache; |
1694 | initACLKeyResultCache(&cache); |
1695 | |
1696 | /* Check each selector sequentially */ |
1697 | listRewind(u->selectors,&li); |
1698 | while((ln = listNext(&li))) { |
1699 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1700 | int acl_retval = ACLSelectorCheckCmd(s, cmd, argv, argc, &local_idxptr, &cache); |
1701 | if (acl_retval == ACL_OK && ACLSelectorHasUnrestrictedKeyAccess(s, flags)) { |
1702 | cleanupACLKeyResultCache(&cache); |
1703 | return 1; |
1704 | } |
1705 | } |
1706 | cleanupACLKeyResultCache(&cache); |
1707 | return 0; |
1708 | } |
1709 | |
1710 | /* Check if the channel can be accessed by the client according to |
1711 | * the ACLs associated with the specified user. |
1712 | * |
1713 | * If the user can access the key, ACL_OK is returned, otherwise |
1714 | * ACL_DENIED_CHANNEL is returned. */ |
1715 | int ACLUserCheckChannelPerm(user *u, sds channel, int is_pattern) { |
1716 | listIter li; |
1717 | listNode *ln; |
1718 | |
1719 | /* If there is no associated user, the connection can run anything. */ |
1720 | if (u == NULL) return ACL_OK; |
1721 | |
1722 | /* Check all of the selectors */ |
1723 | listRewind(u->selectors,&li); |
1724 | while((ln = listNext(&li))) { |
1725 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1726 | /* The selector can run any keys */ |
1727 | if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return ACL_OK; |
1728 | |
1729 | /* Otherwise, loop over the selectors list and check each channel */ |
1730 | if (ACLCheckChannelAgainstList(s->channels, channel, sdslen(channel), is_pattern) == ACL_OK) { |
1731 | return ACL_OK; |
1732 | } |
1733 | } |
1734 | return ACL_DENIED_CHANNEL; |
1735 | } |
1736 | |
1737 | /* Lower level API that checks if a specified user is able to execute a given command. */ |
1738 | int ACLCheckAllUserCommandPerm(user *u, struct redisCommand *cmd, robj **argv, int argc, int *idxptr) { |
1739 | listIter li; |
1740 | listNode *ln; |
1741 | |
1742 | /* If there is no associated user, the connection can run anything. */ |
1743 | if (u == NULL) return ACL_OK; |
1744 | |
1745 | /* We have to pick a single error to log, the logic for picking is as follows: |
1746 | * 1) If no selector can execute the command, return the command. |
1747 | * 2) Return the last key or channel that no selector could match. */ |
1748 | int relevant_error = ACL_DENIED_CMD; |
1749 | int local_idxptr = 0, last_idx = 0; |
1750 | |
1751 | /* For multiple selectors, we cache the key result in between selector |
1752 | * calls to prevent duplicate lookups. */ |
1753 | aclKeyResultCache cache; |
1754 | initACLKeyResultCache(&cache); |
1755 | |
1756 | /* Check each selector sequentially */ |
1757 | listRewind(u->selectors,&li); |
1758 | while((ln = listNext(&li))) { |
1759 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1760 | int acl_retval = ACLSelectorCheckCmd(s, cmd, argv, argc, &local_idxptr, &cache); |
1761 | if (acl_retval == ACL_OK) { |
1762 | cleanupACLKeyResultCache(&cache); |
1763 | return ACL_OK; |
1764 | } |
1765 | if (acl_retval > relevant_error || |
1766 | (acl_retval == relevant_error && local_idxptr > last_idx)) |
1767 | { |
1768 | relevant_error = acl_retval; |
1769 | last_idx = local_idxptr; |
1770 | } |
1771 | } |
1772 | |
1773 | *idxptr = last_idx; |
1774 | cleanupACLKeyResultCache(&cache); |
1775 | return relevant_error; |
1776 | } |
1777 | |
1778 | /* High level API for checking if a client can execute the queued up command */ |
1779 | int ACLCheckAllPerm(client *c, int *idxptr) { |
1780 | return ACLCheckAllUserCommandPerm(c->user, c->cmd, c->argv, c->argc, idxptr); |
1781 | } |
1782 | |
1783 | /* Check if the user's existing pub/sub clients violate the ACL pub/sub |
1784 | * permissions specified via the upcoming argument, and kill them if so. */ |
1785 | void ACLKillPubsubClientsIfNeeded(user *new, user *original) { |
1786 | listIter li, lpi; |
1787 | listNode *ln, *lpn; |
1788 | robj *o; |
1789 | int kill = 0; |
1790 | |
1791 | /* First optimization is we check if any selector has all channel |
1792 | * permissions. */ |
1793 | listRewind(new->selectors,&li); |
1794 | while((ln = listNext(&li))) { |
1795 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1796 | if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return; |
1797 | } |
1798 | |
1799 | /* Second optimization is to check if the new list of channels |
1800 | * is a strict superset of the original. This is done by |
1801 | * created an "upcoming" list of all channels that are in |
1802 | * the new user and checking each of the existing channels |
1803 | * against it. */ |
1804 | list *upcoming = listCreate(); |
1805 | listRewind(new->selectors,&li); |
1806 | while((ln = listNext(&li))) { |
1807 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1808 | listRewind(s->channels, &lpi); |
1809 | while((lpn = listNext(&lpi))) { |
1810 | listAddNodeTail(upcoming, listNodeValue(lpn)); |
1811 | } |
1812 | } |
1813 | |
1814 | int match = 1; |
1815 | listRewind(original->selectors,&li); |
1816 | while((ln = listNext(&li)) && match) { |
1817 | aclSelector *s = (aclSelector *) listNodeValue(ln); |
1818 | listRewind(s->channels, &lpi); |
1819 | while((lpn = listNext(&lpi)) && match) { |
1820 | if (!listSearchKey(upcoming, listNodeValue(lpn))) { |
1821 | match = 0; |
1822 | break; |
1823 | } |
1824 | } |
1825 | } |
1826 | |
1827 | if (match) { |
1828 | /* All channels were matched, no need to kill clients. */ |
1829 | listRelease(upcoming); |
1830 | return; |
1831 | } |
1832 | |
1833 | /* Permissions have changed, so we need to iterate through all |
1834 | * the clients and disconnect those that are no longer valid. |
1835 | * Scan all connected clients to find the user's pub/subs. */ |
1836 | listRewind(server.clients,&li); |
1837 | while ((ln = listNext(&li)) != NULL) { |
1838 | client *c = listNodeValue(ln); |
1839 | kill = 0; |
1840 | |
1841 | if (c->user == original && getClientType(c) == CLIENT_TYPE_PUBSUB) { |
1842 | /* Check for pattern violations. */ |
1843 | listRewind(c->pubsub_patterns,&lpi); |
1844 | while (!kill && ((lpn = listNext(&lpi)) != NULL)) { |
1845 | |
1846 | o = lpn->value; |
1847 | int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1); |
1848 | kill = (res == ACL_DENIED_CHANNEL); |
1849 | } |
1850 | /* Check for channel violations. */ |
1851 | if (!kill) { |
1852 | /* Check for global channels violation. */ |
1853 | dictIterator *di = dictGetIterator(c->pubsub_channels); |
1854 | |
1855 | dictEntry *de; |
1856 | while (!kill && ((de = dictNext(di)) != NULL)) { |
1857 | o = dictGetKey(de); |
1858 | int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0); |
1859 | kill = (res == ACL_DENIED_CHANNEL); |
1860 | } |
1861 | dictReleaseIterator(di); |
1862 | |
1863 | /* Check for shard channels violation. */ |
1864 | di = dictGetIterator(c->pubsubshard_channels); |
1865 | while (!kill && ((de = dictNext(di)) != NULL)) { |
1866 | o = dictGetKey(de); |
1867 | int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0); |
1868 | kill = (res == ACL_DENIED_CHANNEL); |
1869 | } |
1870 | |
1871 | dictReleaseIterator(di); |
1872 | } |
1873 | |
1874 | /* Kill it. */ |
1875 | if (kill) { |
1876 | freeClient(c); |
1877 | } |
1878 | } |
1879 | } |
1880 | listRelease(upcoming); |
1881 | } |
1882 | |
1883 | /* ============================================================================= |
1884 | * ACL loading / saving functions |
1885 | * ==========================================================================*/ |
1886 | |
1887 | |
1888 | /* Selector definitions should be sent as a single argument, however |
1889 | * we will be lenient and try to find selector definitions spread |
1890 | * across multiple arguments since it makes for a simpler user experience |
1891 | * for ACL SETUSER as well as when loading from conf files. |
1892 | * |
1893 | * This function takes in an array of ACL operators, excluding the username, |
1894 | * and merges selector operations that are spread across multiple arguments. The return |
1895 | * value is a new SDS array, with length set to the passed in merged_argc. Arguments |
1896 | * that are untouched are still duplicated. If there is an unmatched parenthesis, NULL |
1897 | * is returned and invalid_idx is set to the argument with the start of the opening |
1898 | * parenthesis. */ |
1899 | sds *ACLMergeSelectorArguments(sds *argv, int argc, int *merged_argc, int *invalid_idx) { |
1900 | *merged_argc = 0; |
1901 | int open_bracket_start = -1; |
1902 | |
1903 | sds *acl_args = (sds *) zmalloc(sizeof(sds) * argc); |
1904 | |
1905 | sds selector = NULL; |
1906 | for (int j = 0; j < argc; j++) { |
1907 | char *op = argv[j]; |
1908 | |
1909 | if (op[0] == '(' && op[sdslen(op) - 1] != ')') { |
1910 | selector = sdsdup(argv[j]); |
1911 | open_bracket_start = j; |
1912 | continue; |
1913 | } |
1914 | |
1915 | if (open_bracket_start != -1) { |
1916 | selector = sdscatfmt(selector, " %s" , op); |
1917 | if (op[sdslen(op) - 1] == ')') { |
1918 | open_bracket_start = -1; |
1919 | acl_args[*merged_argc] = selector; |
1920 | (*merged_argc)++; |
1921 | } |
1922 | continue; |
1923 | } |
1924 | |
1925 | acl_args[*merged_argc] = sdsdup(argv[j]); |
1926 | (*merged_argc)++; |
1927 | } |
1928 | |
1929 | if (open_bracket_start != -1) { |
1930 | for (int i = 0; i < *merged_argc; i++) sdsfree(acl_args[i]); |
1931 | zfree(acl_args); |
1932 | sdsfree(selector); |
1933 | if (invalid_idx) *invalid_idx = open_bracket_start; |
1934 | return NULL; |
1935 | } |
1936 | |
1937 | return acl_args; |
1938 | } |
1939 | |
1940 | /* Given an argument vector describing a user in the form: |
1941 | * |
1942 | * user <username> ... ACL rules and flags ... |
1943 | * |
1944 | * this function validates, and if the syntax is valid, appends |
1945 | * the user definition to a list for later loading. |
1946 | * |
1947 | * The rules are tested for validity and if there obvious syntax errors |
1948 | * the function returns C_ERR and does nothing, otherwise C_OK is returned |
1949 | * and the user is appended to the list. |
1950 | * |
1951 | * Note that this function cannot stop in case of commands that are not found |
1952 | * and, in that case, the error will be emitted later, because certain |
1953 | * commands may be defined later once modules are loaded. |
1954 | * |
1955 | * When an error is detected and C_ERR is returned, the function populates |
1956 | * by reference (if not set to NULL) the argc_err argument with the index |
1957 | * of the argv vector that caused the error. */ |
1958 | int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) { |
1959 | if (argc < 2 || strcasecmp(argv[0],"user" )) { |
1960 | if (argc_err) *argc_err = 0; |
1961 | return C_ERR; |
1962 | } |
1963 | |
1964 | if (listSearchKey(UsersToLoad, argv[1])) { |
1965 | if (argc_err) *argc_err = 1; |
1966 | errno = EALREADY; |
1967 | return C_ERR; |
1968 | } |
1969 | |
1970 | /* Try to apply the user rules in a fake user to see if they |
1971 | * are actually valid. */ |
1972 | user *fakeuser = ACLCreateUnlinkedUser(); |
1973 | |
1974 | /* Merged selectors before trying to process */ |
1975 | int merged_argc; |
1976 | sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, argc_err); |
1977 | |
1978 | if (!acl_args) { |
1979 | return C_ERR; |
1980 | } |
1981 | |
1982 | for (int j = 0; j < merged_argc; j++) { |
1983 | if (ACLSetUser(fakeuser,acl_args[j],sdslen(acl_args[j])) == C_ERR) { |
1984 | if (errno != ENOENT) { |
1985 | ACLFreeUser(fakeuser); |
1986 | if (argc_err) *argc_err = j; |
1987 | for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); |
1988 | zfree(acl_args); |
1989 | return C_ERR; |
1990 | } |
1991 | } |
1992 | } |
1993 | |
1994 | /* Rules look valid, let's append the user to the list. */ |
1995 | sds *copy = zmalloc(sizeof(sds)*(merged_argc + 2)); |
1996 | copy[0] = sdsdup(argv[1]); |
1997 | for (int j = 0; j < merged_argc; j++) copy[j+1] = sdsdup(acl_args[j]); |
1998 | copy[merged_argc + 1] = NULL; |
1999 | listAddNodeTail(UsersToLoad,copy); |
2000 | ACLFreeUser(fakeuser); |
2001 | for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); |
2002 | zfree(acl_args); |
2003 | return C_OK; |
2004 | } |
2005 | |
2006 | /* This function will load the configured users appended to the server |
2007 | * configuration via ACLAppendUserForLoading(). On loading errors it will |
2008 | * log an error and return C_ERR, otherwise C_OK will be returned. */ |
2009 | int ACLLoadConfiguredUsers(void) { |
2010 | listIter li; |
2011 | listNode *ln; |
2012 | listRewind(UsersToLoad,&li); |
2013 | while ((ln = listNext(&li)) != NULL) { |
2014 | sds *aclrules = listNodeValue(ln); |
2015 | sds username = aclrules[0]; |
2016 | |
2017 | if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) { |
2018 | serverLog(LL_WARNING,"Spaces not allowed in ACL usernames" ); |
2019 | return C_ERR; |
2020 | } |
2021 | |
2022 | user *u = ACLCreateUser(username,sdslen(username)); |
2023 | if (!u) { |
2024 | /* Only valid duplicate user is the default one. */ |
2025 | serverAssert(!strcmp(username, "default" )); |
2026 | u = ACLGetUserByName("default" ,7); |
2027 | ACLSetUser(u,"reset" ,-1); |
2028 | } |
2029 | |
2030 | /* Load every rule defined for this user. */ |
2031 | for (int j = 1; aclrules[j]; j++) { |
2032 | if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) { |
2033 | const char *errmsg = ACLSetUserStringError(); |
2034 | serverLog(LL_WARNING,"Error loading ACL rule '%s' for " |
2035 | "the user named '%s': %s" , |
2036 | aclrules[j],aclrules[0],errmsg); |
2037 | return C_ERR; |
2038 | } |
2039 | } |
2040 | |
2041 | /* Having a disabled user in the configuration may be an error, |
2042 | * warn about it without returning any error to the caller. */ |
2043 | if (u->flags & USER_FLAG_DISABLED) { |
2044 | serverLog(LL_NOTICE, "The user '%s' is disabled (there is no " |
2045 | "'on' modifier in the user description). Make " |
2046 | "sure this is not a configuration error." , |
2047 | aclrules[0]); |
2048 | } |
2049 | } |
2050 | return C_OK; |
2051 | } |
2052 | |
2053 | /* This function loads the ACL from the specified filename: every line |
2054 | * is validated and should be either empty or in the format used to specify |
2055 | * users in the redis.conf configuration or in the ACL file, that is: |
2056 | * |
2057 | * user <username> ... rules ... |
2058 | * |
2059 | * Note that this function considers comments starting with '#' as errors |
2060 | * because the ACL file is meant to be rewritten, and comments would be |
2061 | * lost after the rewrite. Yet empty lines are allowed to avoid being too |
2062 | * strict. |
2063 | * |
2064 | * One important part of implementing ACL LOAD, that uses this function, is |
2065 | * to avoid ending with broken rules if the ACL file is invalid for some |
2066 | * reason, so the function will attempt to validate the rules before loading |
2067 | * each user. For every line that will be found broken the function will |
2068 | * collect an error message. |
2069 | * |
2070 | * IMPORTANT: If there is at least a single error, nothing will be loaded |
2071 | * and the rules will remain exactly as they were. |
2072 | * |
2073 | * At the end of the process, if no errors were found in the whole file then |
2074 | * NULL is returned. Otherwise an SDS string describing in a single line |
2075 | * a description of all the issues found is returned. */ |
2076 | sds ACLLoadFromFile(const char *filename) { |
2077 | FILE *fp; |
2078 | char buf[1024]; |
2079 | |
2080 | /* Open the ACL file. */ |
2081 | if ((fp = fopen(filename,"r" )) == NULL) { |
2082 | sds errors = sdscatprintf(sdsempty(), |
2083 | "Error loading ACLs, opening file '%s': %s" , |
2084 | filename, strerror(errno)); |
2085 | return errors; |
2086 | } |
2087 | |
2088 | /* Load the whole file as a single string in memory. */ |
2089 | sds acls = sdsempty(); |
2090 | while(fgets(buf,sizeof(buf),fp) != NULL) |
2091 | acls = sdscat(acls,buf); |
2092 | fclose(fp); |
2093 | |
2094 | /* Split the file into lines and attempt to load each line. */ |
2095 | int totlines; |
2096 | sds *lines, errors = sdsempty(); |
2097 | lines = sdssplitlen(acls,strlen(acls),"\n" ,1,&totlines); |
2098 | sdsfree(acls); |
2099 | |
2100 | /* We do all the loading in a fresh instance of the Users radix tree, |
2101 | * so if there are errors loading the ACL file we can rollback to the |
2102 | * old version. */ |
2103 | rax *old_users = Users; |
2104 | Users = raxNew(); |
2105 | |
2106 | /* Load each line of the file. */ |
2107 | for (int i = 0; i < totlines; i++) { |
2108 | sds *argv; |
2109 | int argc; |
2110 | int linenum = i+1; |
2111 | |
2112 | lines[i] = sdstrim(lines[i]," \t\r\n" ); |
2113 | |
2114 | /* Skip blank lines */ |
2115 | if (lines[i][0] == '\0') continue; |
2116 | |
2117 | /* Split into arguments */ |
2118 | argv = sdssplitlen(lines[i],sdslen(lines[i])," " ,1,&argc); |
2119 | if (argv == NULL) { |
2120 | errors = sdscatprintf(errors, |
2121 | "%s:%d: unbalanced quotes in acl line. " , |
2122 | server.acl_filename, linenum); |
2123 | continue; |
2124 | } |
2125 | |
2126 | /* Skip this line if the resulting command vector is empty. */ |
2127 | if (argc == 0) { |
2128 | sdsfreesplitres(argv,argc); |
2129 | continue; |
2130 | } |
2131 | |
2132 | /* The line should start with the "user" keyword. */ |
2133 | if (strcmp(argv[0],"user" ) || argc < 2) { |
2134 | errors = sdscatprintf(errors, |
2135 | "%s:%d should start with user keyword followed " |
2136 | "by the username. " , server.acl_filename, |
2137 | linenum); |
2138 | sdsfreesplitres(argv,argc); |
2139 | continue; |
2140 | } |
2141 | |
2142 | /* Spaces are not allowed in usernames. */ |
2143 | if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) { |
2144 | errors = sdscatprintf(errors, |
2145 | "'%s:%d: username '%s' contains invalid characters. " , |
2146 | server.acl_filename, linenum, argv[1]); |
2147 | sdsfreesplitres(argv,argc); |
2148 | continue; |
2149 | } |
2150 | |
2151 | user *u = ACLCreateUser(argv[1],sdslen(argv[1])); |
2152 | |
2153 | /* If the user already exists we assume it's an error and abort. */ |
2154 | if (!u) { |
2155 | errors = sdscatprintf(errors,"WARNING: Duplicate user '%s' found on line %d. " , argv[1], linenum); |
2156 | sdsfreesplitres(argv,argc); |
2157 | continue; |
2158 | } |
2159 | |
2160 | /* Finally process the options and validate they can |
2161 | * be cleanly applied to the user. If any option fails |
2162 | * to apply, the other values won't be applied since |
2163 | * all the pending changes will get dropped. */ |
2164 | int merged_argc; |
2165 | sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, NULL); |
2166 | if (!acl_args) { |
2167 | errors = sdscatprintf(errors, |
2168 | "%s:%d: Unmatched parenthesis in selector definition." , |
2169 | server.acl_filename, linenum); |
2170 | } |
2171 | |
2172 | int syntax_error = 0; |
2173 | for (int j = 0; j < merged_argc; j++) { |
2174 | acl_args[j] = sdstrim(acl_args[j],"\t\r\n" ); |
2175 | if (ACLSetUser(u,acl_args[j],sdslen(acl_args[j])) != C_OK) { |
2176 | const char *errmsg = ACLSetUserStringError(); |
2177 | if (errno == ENOENT) { |
2178 | /* For missing commands, we print out more information since |
2179 | * it shouldn't contain any sensitive information. */ |
2180 | errors = sdscatprintf(errors, |
2181 | "%s:%d: Error in applying operation '%s': %s. " , |
2182 | server.acl_filename, linenum, acl_args[j], errmsg); |
2183 | } else if (syntax_error == 0) { |
2184 | /* For all other errors, only print out the first error encountered |
2185 | * since it might affect future operations. */ |
2186 | errors = sdscatprintf(errors, |
2187 | "%s:%d: %s. " , |
2188 | server.acl_filename, linenum, errmsg); |
2189 | syntax_error = 1; |
2190 | } |
2191 | } |
2192 | } |
2193 | |
2194 | for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); |
2195 | zfree(acl_args); |
2196 | |
2197 | /* Apply the rule to the new users set only if so far there |
2198 | * are no errors, otherwise it's useless since we are going |
2199 | * to discard the new users set anyway. */ |
2200 | if (sdslen(errors) != 0) { |
2201 | sdsfreesplitres(argv,argc); |
2202 | continue; |
2203 | } |
2204 | |
2205 | sdsfreesplitres(argv,argc); |
2206 | } |
2207 | |
2208 | sdsfreesplitres(lines,totlines); |
2209 | |
2210 | /* Check if we found errors and react accordingly. */ |
2211 | if (sdslen(errors) == 0) { |
2212 | /* The default user pointer is referenced in different places: instead |
2213 | * of replacing such occurrences it is much simpler to copy the new |
2214 | * default user configuration in the old one. */ |
2215 | user *new_default = ACLGetUserByName("default" ,7); |
2216 | if (!new_default) { |
2217 | new_default = ACLCreateDefaultUser(); |
2218 | } |
2219 | |
2220 | ACLCopyUser(DefaultUser,new_default); |
2221 | ACLFreeUser(new_default); |
2222 | raxInsert(Users,(unsigned char*)"default" ,7,DefaultUser,NULL); |
2223 | raxRemove(old_users,(unsigned char*)"default" ,7,NULL); |
2224 | ACLFreeUsersSet(old_users); |
2225 | sdsfree(errors); |
2226 | return NULL; |
2227 | } else { |
2228 | ACLFreeUsersSet(Users); |
2229 | Users = old_users; |
2230 | errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed" ); |
2231 | return errors; |
2232 | } |
2233 | } |
2234 | |
2235 | /* Generate a copy of the ACLs currently in memory in the specified filename. |
2236 | * Returns C_OK on success or C_ERR if there was an error during the I/O. |
2237 | * When C_ERR is returned a log is produced with hints about the issue. */ |
2238 | int ACLSaveToFile(const char *filename) { |
2239 | sds acl = sdsempty(); |
2240 | int fd = -1; |
2241 | sds tmpfilename = NULL; |
2242 | int retval = C_ERR; |
2243 | |
2244 | /* Let's generate an SDS string containing the new version of the |
2245 | * ACL file. */ |
2246 | raxIterator ri; |
2247 | raxStart(&ri,Users); |
2248 | raxSeek(&ri,"^" ,NULL,0); |
2249 | while(raxNext(&ri)) { |
2250 | user *u = ri.data; |
2251 | /* Return information in the configuration file format. */ |
2252 | sds user = sdsnew("user " ); |
2253 | user = sdscatsds(user,u->name); |
2254 | user = sdscatlen(user," " ,1); |
2255 | sds descr = ACLDescribeUser(u); |
2256 | user = sdscatsds(user,descr); |
2257 | sdsfree(descr); |
2258 | acl = sdscatsds(acl,user); |
2259 | acl = sdscatlen(acl,"\n" ,1); |
2260 | sdsfree(user); |
2261 | } |
2262 | raxStop(&ri); |
2263 | |
2264 | /* Create a temp file with the new content. */ |
2265 | tmpfilename = sdsnew(filename); |
2266 | tmpfilename = sdscatfmt(tmpfilename,".tmp-%i-%I" , |
2267 | (int)getpid(),(int)mstime()); |
2268 | if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) { |
2269 | serverLog(LL_WARNING,"Opening temp ACL file for ACL SAVE: %s" , |
2270 | strerror(errno)); |
2271 | goto cleanup; |
2272 | } |
2273 | |
2274 | /* Write it. */ |
2275 | if (write(fd,acl,sdslen(acl)) != (ssize_t)sdslen(acl)) { |
2276 | serverLog(LL_WARNING,"Writing ACL file for ACL SAVE: %s" , |
2277 | strerror(errno)); |
2278 | goto cleanup; |
2279 | } |
2280 | close(fd); fd = -1; |
2281 | |
2282 | /* Let's replace the new file with the old one. */ |
2283 | if (rename(tmpfilename,filename) == -1) { |
2284 | serverLog(LL_WARNING,"Renaming ACL file for ACL SAVE: %s" , |
2285 | strerror(errno)); |
2286 | goto cleanup; |
2287 | } |
2288 | sdsfree(tmpfilename); tmpfilename = NULL; |
2289 | retval = C_OK; /* If we reached this point, everything is fine. */ |
2290 | |
2291 | cleanup: |
2292 | if (fd != -1) close(fd); |
2293 | if (tmpfilename) unlink(tmpfilename); |
2294 | sdsfree(tmpfilename); |
2295 | sdsfree(acl); |
2296 | return retval; |
2297 | } |
2298 | |
2299 | /* This function is called once the server is already running, modules are |
2300 | * loaded, and we are ready to start, in order to load the ACLs either from |
2301 | * the pending list of users defined in redis.conf, or from the ACL file. |
2302 | * The function will just exit with an error if the user is trying to mix |
2303 | * both the loading methods. */ |
2304 | void ACLLoadUsersAtStartup(void) { |
2305 | if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) { |
2306 | serverLog(LL_WARNING, |
2307 | "Configuring Redis with users defined in redis.conf and at " |
2308 | "the same setting an ACL file path is invalid. This setup " |
2309 | "is very likely to lead to configuration errors and security " |
2310 | "holes, please define either an ACL file or declare users " |
2311 | "directly in your redis.conf, but not both." ); |
2312 | exit(1); |
2313 | } |
2314 | |
2315 | if (ACLLoadConfiguredUsers() == C_ERR) { |
2316 | serverLog(LL_WARNING, |
2317 | "Critical error while loading ACLs. Exiting." ); |
2318 | exit(1); |
2319 | } |
2320 | |
2321 | if (server.acl_filename[0] != '\0') { |
2322 | sds errors = ACLLoadFromFile(server.acl_filename); |
2323 | if (errors) { |
2324 | serverLog(LL_WARNING, |
2325 | "Aborting Redis startup because of ACL errors: %s" , errors); |
2326 | sdsfree(errors); |
2327 | exit(1); |
2328 | } |
2329 | } |
2330 | } |
2331 | |
2332 | /* ============================================================================= |
2333 | * ACL log |
2334 | * ==========================================================================*/ |
2335 | |
2336 | #define ACL_LOG_GROUPING_MAX_TIME_DELTA 60000 |
2337 | |
2338 | /* This structure defines an entry inside the ACL log. */ |
2339 | typedef struct ACLLogEntry { |
2340 | uint64_t count; /* Number of times this happened recently. */ |
2341 | int reason; /* Reason for denying the command. ACL_DENIED_*. */ |
2342 | int context; /* Toplevel, Lua or MULTI/EXEC? ACL_LOG_CTX_*. */ |
2343 | sds object; /* The key name or command name. */ |
2344 | sds username; /* User the client is authenticated with. */ |
2345 | mstime_t ctime; /* Milliseconds time of last update to this entry. */ |
2346 | sds cinfo; /* Client info (last client if updated). */ |
2347 | } ACLLogEntry; |
2348 | |
2349 | /* This function will check if ACL entries 'a' and 'b' are similar enough |
2350 | * that we should actually update the existing entry in our ACL log instead |
2351 | * of creating a new one. */ |
2352 | int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) { |
2353 | if (a->reason != b->reason) return 0; |
2354 | if (a->context != b->context) return 0; |
2355 | mstime_t delta = a->ctime - b->ctime; |
2356 | if (delta < 0) delta = -delta; |
2357 | if (delta > ACL_LOG_GROUPING_MAX_TIME_DELTA) return 0; |
2358 | if (sdscmp(a->object,b->object) != 0) return 0; |
2359 | if (sdscmp(a->username,b->username) != 0) return 0; |
2360 | return 1; |
2361 | } |
2362 | |
2363 | /* Release an ACL log entry. */ |
2364 | void ACLFreeLogEntry(void *leptr) { |
2365 | ACLLogEntry *le = leptr; |
2366 | sdsfree(le->object); |
2367 | sdsfree(le->username); |
2368 | sdsfree(le->cinfo); |
2369 | zfree(le); |
2370 | } |
2371 | |
2372 | /* Adds a new entry in the ACL log, making sure to delete the old entry |
2373 | * if we reach the maximum length allowed for the log. This function attempts |
2374 | * to find similar entries in the current log in order to bump the counter of |
2375 | * the log entry instead of creating many entries for very similar ACL |
2376 | * rules issues. |
2377 | * |
2378 | * The argpos argument is used when the reason is ACL_DENIED_KEY or |
2379 | * ACL_DENIED_CHANNEL, since it allows the function to log the key or channel |
2380 | * name that caused the problem. |
2381 | * |
2382 | * The last 2 arguments are a manual override to be used, instead of any of the automatic |
2383 | * ones which depend on the client and reason arguments (use NULL for default). |
2384 | * |
2385 | * If `object` is not NULL, this functions takes over it. |
2386 | */ |
2387 | void addACLLogEntry(client *c, int reason, int context, int argpos, sds username, sds object) { |
2388 | /* Create a new entry. */ |
2389 | struct ACLLogEntry *le = zmalloc(sizeof(*le)); |
2390 | le->count = 1; |
2391 | le->reason = reason; |
2392 | le->username = sdsdup(username ? username : c->user->name); |
2393 | le->ctime = mstime(); |
2394 | |
2395 | if (object) { |
2396 | le->object = object; |
2397 | } else { |
2398 | switch(reason) { |
2399 | case ACL_DENIED_CMD: le->object = sdsdup(c->cmd->fullname); break; |
2400 | case ACL_DENIED_KEY: le->object = sdsdup(c->argv[argpos]->ptr); break; |
2401 | case ACL_DENIED_CHANNEL: le->object = sdsdup(c->argv[argpos]->ptr); break; |
2402 | case ACL_DENIED_AUTH: le->object = sdsdup(c->argv[0]->ptr); break; |
2403 | default: le->object = sdsempty(); |
2404 | } |
2405 | } |
2406 | |
2407 | client *realclient = c; |
2408 | if (realclient->flags & CLIENT_SCRIPT) realclient = server.script_caller; |
2409 | |
2410 | le->cinfo = catClientInfoString(sdsempty(),realclient); |
2411 | le->context = context; |
2412 | |
2413 | /* Try to match this entry with past ones, to see if we can just |
2414 | * update an existing entry instead of creating a new one. */ |
2415 | long toscan = 10; /* Do a limited work trying to find duplicated. */ |
2416 | listIter li; |
2417 | listNode *ln; |
2418 | listRewind(ACLLog,&li); |
2419 | ACLLogEntry *match = NULL; |
2420 | while (toscan-- && (ln = listNext(&li)) != NULL) { |
2421 | ACLLogEntry *current = listNodeValue(ln); |
2422 | if (ACLLogMatchEntry(current,le)) { |
2423 | match = current; |
2424 | listDelNode(ACLLog,ln); |
2425 | listAddNodeHead(ACLLog,current); |
2426 | break; |
2427 | } |
2428 | } |
2429 | |
2430 | /* If there is a match update the entry, otherwise add it as a |
2431 | * new one. */ |
2432 | if (match) { |
2433 | /* We update a few fields of the existing entry and bump the |
2434 | * counter of events for this entry. */ |
2435 | sdsfree(match->cinfo); |
2436 | match->cinfo = le->cinfo; |
2437 | match->ctime = le->ctime; |
2438 | match->count++; |
2439 | |
2440 | /* Release the old entry. */ |
2441 | le->cinfo = NULL; |
2442 | ACLFreeLogEntry(le); |
2443 | } else { |
2444 | /* Add it to our list of entries. We'll have to trim the list |
2445 | * to its maximum size. */ |
2446 | listAddNodeHead(ACLLog, le); |
2447 | while(listLength(ACLLog) > server.acllog_max_len) { |
2448 | listNode *ln = listLast(ACLLog); |
2449 | ACLLogEntry *le = listNodeValue(ln); |
2450 | ACLFreeLogEntry(le); |
2451 | listDelNode(ACLLog,ln); |
2452 | } |
2453 | } |
2454 | } |
2455 | |
2456 | const char* getAclErrorMessage(int acl_res) { |
2457 | /* Notice that a variant of this code also exists on aclCommand so |
2458 | * it also need to be updated on changed. */ |
2459 | switch (acl_res) { |
2460 | case ACL_DENIED_CMD: |
2461 | return "can't run this command or subcommand" ; |
2462 | case ACL_DENIED_KEY: |
2463 | return "can't access at least one of the keys mentioned in the command arguments" ; |
2464 | case ACL_DENIED_CHANNEL: |
2465 | return "can't publish to the channel mentioned in the command" ; |
2466 | default: |
2467 | return "lacking the permissions for the command" ; |
2468 | } |
2469 | serverPanic("Reached deadcode on getAclErrorMessage" ); |
2470 | } |
2471 | |
2472 | /* ============================================================================= |
2473 | * ACL related commands |
2474 | * ==========================================================================*/ |
2475 | |
2476 | /* ACL CAT category */ |
2477 | void aclCatWithFlags(client *c, dict *commands, uint64_t cflag, int *arraylen) { |
2478 | dictEntry *de; |
2479 | dictIterator *di = dictGetIterator(commands); |
2480 | |
2481 | while ((de = dictNext(di)) != NULL) { |
2482 | struct redisCommand *cmd = dictGetVal(de); |
2483 | if (cmd->flags & CMD_MODULE) continue; |
2484 | if (cmd->acl_categories & cflag) { |
2485 | addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname)); |
2486 | (*arraylen)++; |
2487 | } |
2488 | |
2489 | if (cmd->subcommands_dict) { |
2490 | aclCatWithFlags(c, cmd->subcommands_dict, cflag, arraylen); |
2491 | } |
2492 | } |
2493 | dictReleaseIterator(di); |
2494 | } |
2495 | |
2496 | /* Add the formatted response from a single selector to the ACL GETUSER |
2497 | * response. This function returns the number of fields added. |
2498 | * |
2499 | * Setting verbose to 1 means that the full qualifier for key and channel |
2500 | * permissions are shown. |
2501 | */ |
2502 | int aclAddReplySelectorDescription(client *c, aclSelector *s) { |
2503 | listIter li; |
2504 | listNode *ln; |
2505 | |
2506 | /* Commands */ |
2507 | addReplyBulkCString(c,"commands" ); |
2508 | sds cmddescr = ACLDescribeSelectorCommandRules(s); |
2509 | addReplyBulkSds(c,cmddescr); |
2510 | |
2511 | /* Key patterns */ |
2512 | addReplyBulkCString(c,"keys" ); |
2513 | if (s->flags & SELECTOR_FLAG_ALLKEYS) { |
2514 | addReplyBulkCBuffer(c,"~*" ,2); |
2515 | } else { |
2516 | sds dsl = sdsempty(); |
2517 | listRewind(s->patterns,&li); |
2518 | while((ln = listNext(&li))) { |
2519 | keyPattern *thispat = (keyPattern *) listNodeValue(ln); |
2520 | if (ln != listFirst(s->patterns)) dsl = sdscat(dsl, " " ); |
2521 | dsl = sdsCatPatternString(dsl, thispat); |
2522 | } |
2523 | addReplyBulkSds(c, dsl); |
2524 | } |
2525 | |
2526 | /* Pub/sub patterns */ |
2527 | addReplyBulkCString(c,"channels" ); |
2528 | if (s->flags & SELECTOR_FLAG_ALLCHANNELS) { |
2529 | addReplyBulkCBuffer(c,"&*" ,2); |
2530 | } else { |
2531 | sds dsl = sdsempty(); |
2532 | listRewind(s->channels,&li); |
2533 | while((ln = listNext(&li))) { |
2534 | sds thispat = listNodeValue(ln); |
2535 | if (ln != listFirst(s->channels)) dsl = sdscat(dsl, " " ); |
2536 | dsl = sdscatfmt(dsl, "&%S" , thispat); |
2537 | } |
2538 | addReplyBulkSds(c, dsl); |
2539 | } |
2540 | return 3; |
2541 | } |
2542 | |
2543 | /* ACL -- show and modify the configuration of ACL users. |
2544 | * ACL HELP |
2545 | * ACL LOAD |
2546 | * ACL SAVE |
2547 | * ACL LIST |
2548 | * ACL USERS |
2549 | * ACL CAT [<category>] |
2550 | * ACL SETUSER <username> ... acl rules ... |
2551 | * ACL DELUSER <username> [...] |
2552 | * ACL GETUSER <username> |
2553 | * ACL GENPASS [<bits>] |
2554 | * ACL WHOAMI |
2555 | * ACL LOG [<count> | RESET] |
2556 | */ |
2557 | void aclCommand(client *c) { |
2558 | char *sub = c->argv[1]->ptr; |
2559 | if (!strcasecmp(sub,"setuser" ) && c->argc >= 3) { |
2560 | /* Initially redact all of the arguments to not leak any information |
2561 | * about the user. */ |
2562 | for (int j = 2; j < c->argc; j++) { |
2563 | redactClientCommandArgument(c, j); |
2564 | } |
2565 | |
2566 | sds username = c->argv[2]->ptr; |
2567 | /* Check username validity. */ |
2568 | if (ACLStringHasSpaces(username,sdslen(username))) { |
2569 | addReplyErrorFormat(c, |
2570 | "Usernames can't contain spaces or null characters" ); |
2571 | return; |
2572 | } |
2573 | |
2574 | int merged_argc = 0, invalid_idx = 0; |
2575 | sds *temp_argv = zmalloc(c->argc * sizeof(sds)); |
2576 | for (int i = 3; i < c->argc; i++) temp_argv[i-3] = c->argv[i]->ptr; |
2577 | sds *acl_args = ACLMergeSelectorArguments(temp_argv, c->argc - 3, &merged_argc, &invalid_idx); |
2578 | zfree(temp_argv); |
2579 | |
2580 | if (!acl_args) { |
2581 | addReplyErrorFormat(c, |
2582 | "Unmatched parenthesis in acl selector starting " |
2583 | "at '%s'." , (char *) c->argv[invalid_idx]->ptr); |
2584 | return; |
2585 | } |
2586 | |
2587 | /* Create a temporary user to validate and stage all changes against |
2588 | * before applying to an existing user or creating a new user. If all |
2589 | * arguments are valid the user parameters will all be applied together. |
2590 | * If there are any errors then none of the changes will be applied. */ |
2591 | user *tempu = ACLCreateUnlinkedUser(); |
2592 | user *u = ACLGetUserByName(username,sdslen(username)); |
2593 | if (u) ACLCopyUser(tempu, u); |
2594 | |
2595 | for (int j = 0; j < merged_argc; j++) { |
2596 | if (ACLSetUser(tempu,acl_args[j],sdslen(acl_args[j])) != C_OK) { |
2597 | const char *errmsg = ACLSetUserStringError(); |
2598 | addReplyErrorFormat(c, |
2599 | "Error in ACL SETUSER modifier '%s': %s" , |
2600 | (char*)acl_args[j], errmsg); |
2601 | goto setuser_cleanup; |
2602 | } |
2603 | } |
2604 | |
2605 | /* Existing pub/sub clients authenticated with the user may need to be |
2606 | * disconnected if (some of) their channel permissions were revoked. */ |
2607 | if (u) ACLKillPubsubClientsIfNeeded(tempu, u); |
2608 | |
2609 | /* Overwrite the user with the temporary user we modified above. */ |
2610 | if (!u) u = ACLCreateUser(username,sdslen(username)); |
2611 | serverAssert(u != NULL); |
2612 | ACLCopyUser(u, tempu); |
2613 | addReply(c,shared.ok); |
2614 | setuser_cleanup: |
2615 | ACLFreeUser(tempu); |
2616 | for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); |
2617 | zfree(acl_args); |
2618 | return; |
2619 | } else if (!strcasecmp(sub,"deluser" ) && c->argc >= 3) { |
2620 | int deleted = 0; |
2621 | for (int j = 2; j < c->argc; j++) { |
2622 | sds username = c->argv[j]->ptr; |
2623 | if (!strcmp(username,"default" )) { |
2624 | addReplyError(c,"The 'default' user cannot be removed" ); |
2625 | return; |
2626 | } |
2627 | } |
2628 | |
2629 | for (int j = 2; j < c->argc; j++) { |
2630 | sds username = c->argv[j]->ptr; |
2631 | user *u; |
2632 | if (raxRemove(Users,(unsigned char*)username, |
2633 | sdslen(username), |
2634 | (void**)&u)) |
2635 | { |
2636 | ACLFreeUserAndKillClients(u); |
2637 | deleted++; |
2638 | } |
2639 | } |
2640 | addReplyLongLong(c,deleted); |
2641 | } else if (!strcasecmp(sub,"getuser" ) && c->argc == 3) { |
2642 | user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr)); |
2643 | if (u == NULL) { |
2644 | addReplyNull(c); |
2645 | return; |
2646 | } |
2647 | |
2648 | void *ufields = addReplyDeferredLen(c); |
2649 | int fields = 3; |
2650 | |
2651 | /* Flags */ |
2652 | addReplyBulkCString(c,"flags" ); |
2653 | void *deflen = addReplyDeferredLen(c); |
2654 | int numflags = 0; |
2655 | for (int j = 0; ACLUserFlags[j].flag; j++) { |
2656 | if (u->flags & ACLUserFlags[j].flag) { |
2657 | addReplyBulkCString(c,ACLUserFlags[j].name); |
2658 | numflags++; |
2659 | } |
2660 | } |
2661 | setDeferredSetLen(c,deflen,numflags); |
2662 | |
2663 | /* Passwords */ |
2664 | addReplyBulkCString(c,"passwords" ); |
2665 | addReplyArrayLen(c,listLength(u->passwords)); |
2666 | listIter li; |
2667 | listNode *ln; |
2668 | listRewind(u->passwords,&li); |
2669 | while((ln = listNext(&li))) { |
2670 | sds thispass = listNodeValue(ln); |
2671 | addReplyBulkCBuffer(c,thispass,sdslen(thispass)); |
2672 | } |
2673 | /* Include the root selector at the top level for backwards compatibility */ |
2674 | fields += aclAddReplySelectorDescription(c, ACLUserGetRootSelector(u)); |
2675 | |
2676 | /* Describe all of the selectors on this user, including duplicating the root selector */ |
2677 | addReplyBulkCString(c,"selectors" ); |
2678 | addReplyArrayLen(c, listLength(u->selectors) - 1); |
2679 | listRewind(u->selectors,&li); |
2680 | serverAssert(listNext(&li)); |
2681 | while((ln = listNext(&li))) { |
2682 | void *slen = addReplyDeferredLen(c); |
2683 | int sfields = aclAddReplySelectorDescription(c, (aclSelector *)listNodeValue(ln)); |
2684 | setDeferredMapLen(c, slen, sfields); |
2685 | } |
2686 | setDeferredMapLen(c, ufields, fields); |
2687 | } else if ((!strcasecmp(sub,"list" ) || !strcasecmp(sub,"users" )) && |
2688 | c->argc == 2) |
2689 | { |
2690 | int justnames = !strcasecmp(sub,"users" ); |
2691 | addReplyArrayLen(c,raxSize(Users)); |
2692 | raxIterator ri; |
2693 | raxStart(&ri,Users); |
2694 | raxSeek(&ri,"^" ,NULL,0); |
2695 | while(raxNext(&ri)) { |
2696 | user *u = ri.data; |
2697 | if (justnames) { |
2698 | addReplyBulkCBuffer(c,u->name,sdslen(u->name)); |
2699 | } else { |
2700 | /* Return information in the configuration file format. */ |
2701 | sds config = sdsnew("user " ); |
2702 | config = sdscatsds(config,u->name); |
2703 | config = sdscatlen(config," " ,1); |
2704 | sds descr = ACLDescribeUser(u); |
2705 | config = sdscatsds(config,descr); |
2706 | sdsfree(descr); |
2707 | addReplyBulkSds(c,config); |
2708 | } |
2709 | } |
2710 | raxStop(&ri); |
2711 | } else if (!strcasecmp(sub,"whoami" ) && c->argc == 2) { |
2712 | if (c->user != NULL) { |
2713 | addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name)); |
2714 | } else { |
2715 | addReplyNull(c); |
2716 | } |
2717 | } else if (server.acl_filename[0] == '\0' && |
2718 | (!strcasecmp(sub,"load" ) || !strcasecmp(sub,"save" ))) |
2719 | { |
2720 | addReplyError(c,"This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration." ); |
2721 | return; |
2722 | } else if (!strcasecmp(sub,"load" ) && c->argc == 2) { |
2723 | sds errors = ACLLoadFromFile(server.acl_filename); |
2724 | if (errors == NULL) { |
2725 | addReply(c,shared.ok); |
2726 | } else { |
2727 | addReplyError(c,errors); |
2728 | sdsfree(errors); |
2729 | } |
2730 | } else if (!strcasecmp(sub,"save" ) && c->argc == 2) { |
2731 | if (ACLSaveToFile(server.acl_filename) == C_OK) { |
2732 | addReply(c,shared.ok); |
2733 | } else { |
2734 | addReplyError(c,"There was an error trying to save the ACLs. " |
2735 | "Please check the server logs for more " |
2736 | "information" ); |
2737 | } |
2738 | } else if (!strcasecmp(sub,"cat" ) && c->argc == 2) { |
2739 | void *dl = addReplyDeferredLen(c); |
2740 | int j; |
2741 | for (j = 0; ACLCommandCategories[j].flag != 0; j++) |
2742 | addReplyBulkCString(c,ACLCommandCategories[j].name); |
2743 | setDeferredArrayLen(c,dl,j); |
2744 | } else if (!strcasecmp(sub,"cat" ) && c->argc == 3) { |
2745 | uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr); |
2746 | if (cflag == 0) { |
2747 | addReplyErrorFormat(c, "Unknown category '%.128s'" , (char*)c->argv[2]->ptr); |
2748 | return; |
2749 | } |
2750 | int arraylen = 0; |
2751 | void *dl = addReplyDeferredLen(c); |
2752 | aclCatWithFlags(c, server.orig_commands, cflag, &arraylen); |
2753 | setDeferredArrayLen(c,dl,arraylen); |
2754 | } else if (!strcasecmp(sub,"genpass" ) && (c->argc == 2 || c->argc == 3)) { |
2755 | #define GENPASS_MAX_BITS 4096 |
2756 | char pass[GENPASS_MAX_BITS/8*2]; /* Hex representation. */ |
2757 | long bits = 256; /* By default generate 256 bits passwords. */ |
2758 | |
2759 | if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL) |
2760 | != C_OK) return; |
2761 | |
2762 | if (bits <= 0 || bits > GENPASS_MAX_BITS) { |
2763 | addReplyErrorFormat(c, |
2764 | "ACL GENPASS argument must be the number of " |
2765 | "bits for the output password, a positive number " |
2766 | "up to %d" ,GENPASS_MAX_BITS); |
2767 | return; |
2768 | } |
2769 | |
2770 | long chars = (bits+3)/4; /* Round to number of characters to emit. */ |
2771 | getRandomHexChars(pass,chars); |
2772 | addReplyBulkCBuffer(c,pass,chars); |
2773 | } else if (!strcasecmp(sub,"log" ) && (c->argc == 2 || c->argc ==3)) { |
2774 | long count = 10; /* Number of entries to emit by default. */ |
2775 | |
2776 | /* Parse the only argument that LOG may have: it could be either |
2777 | * the number of entries the user wants to display, or alternatively |
2778 | * the "RESET" command in order to flush the old entries. */ |
2779 | if (c->argc == 3) { |
2780 | if (!strcasecmp(c->argv[2]->ptr,"reset" )) { |
2781 | listSetFreeMethod(ACLLog,ACLFreeLogEntry); |
2782 | listEmpty(ACLLog); |
2783 | listSetFreeMethod(ACLLog,NULL); |
2784 | addReply(c,shared.ok); |
2785 | return; |
2786 | } else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL) |
2787 | != C_OK) |
2788 | { |
2789 | return; |
2790 | } |
2791 | if (count < 0) count = 0; |
2792 | } |
2793 | |
2794 | /* Fix the count according to the number of entries we got. */ |
2795 | if ((size_t)count > listLength(ACLLog)) |
2796 | count = listLength(ACLLog); |
2797 | |
2798 | addReplyArrayLen(c,count); |
2799 | listIter li; |
2800 | listNode *ln; |
2801 | listRewind(ACLLog,&li); |
2802 | mstime_t now = mstime(); |
2803 | while (count-- && (ln = listNext(&li)) != NULL) { |
2804 | ACLLogEntry *le = listNodeValue(ln); |
2805 | addReplyMapLen(c,7); |
2806 | addReplyBulkCString(c,"count" ); |
2807 | addReplyLongLong(c,le->count); |
2808 | |
2809 | addReplyBulkCString(c,"reason" ); |
2810 | char *reasonstr; |
2811 | switch(le->reason) { |
2812 | case ACL_DENIED_CMD: reasonstr="command" ; break; |
2813 | case ACL_DENIED_KEY: reasonstr="key" ; break; |
2814 | case ACL_DENIED_CHANNEL: reasonstr="channel" ; break; |
2815 | case ACL_DENIED_AUTH: reasonstr="auth" ; break; |
2816 | default: reasonstr="unknown" ; |
2817 | } |
2818 | addReplyBulkCString(c,reasonstr); |
2819 | |
2820 | addReplyBulkCString(c,"context" ); |
2821 | char *ctxstr; |
2822 | switch(le->context) { |
2823 | case ACL_LOG_CTX_TOPLEVEL: ctxstr="toplevel" ; break; |
2824 | case ACL_LOG_CTX_MULTI: ctxstr="multi" ; break; |
2825 | case ACL_LOG_CTX_LUA: ctxstr="lua" ; break; |
2826 | case ACL_LOG_CTX_MODULE: ctxstr="module" ; break; |
2827 | default: ctxstr="unknown" ; |
2828 | } |
2829 | addReplyBulkCString(c,ctxstr); |
2830 | |
2831 | addReplyBulkCString(c,"object" ); |
2832 | addReplyBulkCBuffer(c,le->object,sdslen(le->object)); |
2833 | addReplyBulkCString(c,"username" ); |
2834 | addReplyBulkCBuffer(c,le->username,sdslen(le->username)); |
2835 | addReplyBulkCString(c,"age-seconds" ); |
2836 | double age = (double)(now - le->ctime)/1000; |
2837 | addReplyDouble(c,age); |
2838 | addReplyBulkCString(c,"client-info" ); |
2839 | addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo)); |
2840 | } |
2841 | } else if (!strcasecmp(sub,"dryrun" ) && c->argc >= 4) { |
2842 | struct redisCommand *cmd; |
2843 | user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr)); |
2844 | if (u == NULL) { |
2845 | addReplyErrorFormat(c, "User '%s' not found" , (char *)c->argv[2]->ptr); |
2846 | return; |
2847 | } |
2848 | |
2849 | if ((cmd = lookupCommand(c->argv + 3, c->argc - 3)) == NULL) { |
2850 | addReplyErrorFormat(c, "Command '%s' not found" , (char *)c->argv[3]->ptr); |
2851 | return; |
2852 | } |
2853 | |
2854 | if ((cmd->arity > 0 && cmd->arity != c->argc-3) || |
2855 | (c->argc-3 < -cmd->arity)) |
2856 | { |
2857 | addReplyErrorFormat(c,"wrong number of arguments for '%s' command" , cmd->fullname); |
2858 | return; |
2859 | } |
2860 | |
2861 | int idx; |
2862 | int result = ACLCheckAllUserCommandPerm(u, cmd, c->argv + 3, c->argc - 3, &idx); |
2863 | /* Notice that a variant of this code also exists on getAclErrorMessage so |
2864 | * it also need to be updated on changed. */ |
2865 | if (result != ACL_OK) { |
2866 | sds err = sdsempty(); |
2867 | if (result == ACL_DENIED_CMD) { |
2868 | err = sdscatfmt(err, "This user has no permissions to run " |
2869 | "the '%s' command" , cmd->fullname); |
2870 | } else if (result == ACL_DENIED_KEY) { |
2871 | err = sdscatfmt(err, "This user has no permissions to access " |
2872 | "the '%s' key" , c->argv[idx + 3]->ptr); |
2873 | } else if (result == ACL_DENIED_CHANNEL) { |
2874 | err = sdscatfmt(err, "This user has no permissions to access " |
2875 | "the '%s' channel" , c->argv[idx + 3]->ptr); |
2876 | } else { |
2877 | serverPanic("Invalid permission result" ); |
2878 | } |
2879 | addReplyBulkSds(c, err); |
2880 | return; |
2881 | } |
2882 | |
2883 | addReply(c,shared.ok); |
2884 | } else if (c->argc == 2 && !strcasecmp(sub,"help" )) { |
2885 | const char *help[] = { |
2886 | "CAT [<category>]" , |
2887 | " List all commands that belong to <category>, or all command categories" , |
2888 | " when no category is specified." , |
2889 | "DELUSER <username> [<username> ...]" , |
2890 | " Delete a list of users." , |
2891 | "DRYRUN <username> <command> [<arg> ...]" , |
2892 | " Returns whether the user can execute the given command without executing the command." , |
2893 | "GETUSER <username>" , |
2894 | " Get the user's details." , |
2895 | "GENPASS [<bits>]" , |
2896 | " Generate a secure 256-bit user password. The optional `bits` argument can" , |
2897 | " be used to specify a different size." , |
2898 | "LIST" , |
2899 | " Show users details in config file format." , |
2900 | "LOAD" , |
2901 | " Reload users from the ACL file." , |
2902 | "LOG [<count> | RESET]" , |
2903 | " Show the ACL log entries." , |
2904 | "SAVE" , |
2905 | " Save the current config to the ACL file." , |
2906 | "SETUSER <username> <attribute> [<attribute> ...]" , |
2907 | " Create or modify a user with the specified attributes." , |
2908 | "USERS" , |
2909 | " List all the registered usernames." , |
2910 | "WHOAMI" , |
2911 | " Return the current connection username." , |
2912 | NULL |
2913 | }; |
2914 | addReplyHelp(c,help); |
2915 | } else { |
2916 | addReplySubcommandSyntaxError(c); |
2917 | } |
2918 | } |
2919 | |
2920 | void addReplyCommandCategories(client *c, struct redisCommand *cmd) { |
2921 | int flagcount = 0; |
2922 | void *flaglen = addReplyDeferredLen(c); |
2923 | for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { |
2924 | if (cmd->acl_categories & ACLCommandCategories[j].flag) { |
2925 | addReplyStatusFormat(c, "@%s" , ACLCommandCategories[j].name); |
2926 | flagcount++; |
2927 | } |
2928 | } |
2929 | setDeferredSetLen(c, flaglen, flagcount); |
2930 | } |
2931 | |
2932 | /* AUTH <password> |
2933 | * AUTH <username> <password> (Redis >= 6.0 form) |
2934 | * |
2935 | * When the user is omitted it means that we are trying to authenticate |
2936 | * against the default user. */ |
2937 | void authCommand(client *c) { |
2938 | /* Only two or three argument forms are allowed. */ |
2939 | if (c->argc > 3) { |
2940 | addReplyErrorObject(c,shared.syntaxerr); |
2941 | return; |
2942 | } |
2943 | /* Always redact the second argument */ |
2944 | redactClientCommandArgument(c, 1); |
2945 | |
2946 | /* Handle the two different forms here. The form with two arguments |
2947 | * will just use "default" as username. */ |
2948 | robj *username, *password; |
2949 | if (c->argc == 2) { |
2950 | /* Mimic the old behavior of giving an error for the two argument |
2951 | * form if no password is configured. */ |
2952 | if (DefaultUser->flags & USER_FLAG_NOPASS) { |
2953 | addReplyError(c,"AUTH <password> called without any password " |
2954 | "configured for the default user. Are you sure " |
2955 | "your configuration is correct?" ); |
2956 | return; |
2957 | } |
2958 | |
2959 | username = shared.default_username; |
2960 | password = c->argv[1]; |
2961 | } else { |
2962 | username = c->argv[1]; |
2963 | password = c->argv[2]; |
2964 | redactClientCommandArgument(c, 2); |
2965 | } |
2966 | |
2967 | if (ACLAuthenticateUser(c,username,password) == C_OK) { |
2968 | addReply(c,shared.ok); |
2969 | } else { |
2970 | addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled." ); |
2971 | } |
2972 | } |
2973 | |
2974 | /* Set the password for the "default" ACL user. This implements supports for |
2975 | * requirepass config, so passing in NULL will set the user to be nopass. */ |
2976 | void ACLUpdateDefaultUserPassword(sds password) { |
2977 | ACLSetUser(DefaultUser,"resetpass" ,-1); |
2978 | if (password) { |
2979 | sds aclop = sdscatlen(sdsnew(">" ), password, sdslen(password)); |
2980 | ACLSetUser(DefaultUser,aclop,sdslen(aclop)); |
2981 | sdsfree(aclop); |
2982 | } else { |
2983 | ACLSetUser(DefaultUser,"nopass" ,-1); |
2984 | } |
2985 | } |
2986 | |