Skip to content

Commit a311cc4

Browse files
Support for Redis 6 ACLs (phpredis#1791)
Add support for Redis 6 ACLs in the `Redis`, `RedisCluster`, and `RedisArray` classes. On a related note, it adds a mechanism for users to customize how we generate persistent connection IDs such that they can be grouped in different ways depending on the specific use case required (e.g. it would allow connections to be grouped by username, or by user-defined persistent_id, or both).
1 parent 04def9f commit a311cc4

25 files changed

+1641
-656
lines changed

.travis.yml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,23 @@ before_install:
4040
- ./configure $CFGARGS
4141
install: make install
4242
before_script:
43+
- sudo add-apt-repository ppa:chris-lea/redis-server -y && sudo apt-get update && sudo apt install redis-server
4344
- mkdir -p tests/nodes/ && echo > tests/nodes/nodemap
44-
- redis-server --port 0 --daemonize yes --requirepass phpredis --unixsocket /tmp/redis.sock
45-
- for PORT in $(seq 6379 6382) $(seq 32767 32769); do redis-server --port $PORT --daemonize yes --requirepass phpredis; done
46-
- for PORT in $(seq 7000 7011); do redis-server --port $PORT --cluster-enabled yes --cluster-config-file $PORT.conf --daemonize yes --requirepass phpredis --masterauth phpredis; echo 127.0.0.1:$PORT >> tests/nodes/nodemap; done
45+
- redis-server --port 0 --daemonize yes --aclfile tests/users.acl --unixsocket /tmp/redis.sock
46+
- for PORT in $(seq 6379 6382) $(seq 32767 32769); do redis-server --port $PORT --daemonize yes --aclfile tests/users.acl; done
47+
- for PORT in $(seq 7000 7011); do redis-server --port $PORT --cluster-enabled yes --cluster-config-file $PORT.conf --daemonize yes --aclfile tests/users.acl; echo 127.0.0.1:$PORT >> tests/nodes/nodemap; done
4748
- for PORT in $(seq 26379 26380); do wget download.redis.io/redis-stable/sentinel.conf -O $PORT.conf; echo sentinel auth-pass mymaster phpredis >> $PORT.conf; redis-server $PORT.conf --port $PORT --daemonize yes --sentinel; done
48-
- echo yes | redis-cli --cluster create $(seq -f 127.0.0.1:%g 7000 7011) --cluster-replicas 3 -a phpredis
49+
- echo yes | redis-cli --cluster create $(seq -f 127.0.0.1:%g 7000 7011) --cluster-replicas 3 --user phpredis -a phpredis
4950
- echo 'extension = redis.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
5051
- openssl req -x509 -newkey rsa:1024 -nodes -keyout stunnel.key -out stunnel.pem -days 1 -subj '/CN=localhost'
5152
- echo -e 'key=stunnel.key\ncert=stunnel.pem\npid=/tmp/stunnel.pid\n[redis]\naccept=6378\nconnect=6379' > stunnel.conf
5253
- stunnel stunnel.conf
5354
script:
54-
- php tests/TestRedis.php --class Redis --auth phpredis
55-
- php tests/TestRedis.php --class RedisArray --auth phpredis
56-
- php tests/TestRedis.php --class RedisCluster --auth phpredis
55+
- php tests/TestRedis.php --class Redis --user phpredis --auth phpredis
56+
- php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis
57+
- php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis
5758
- php tests/TestRedis.php --class RedisSentinel --auth phpredis
58-
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class Redis --auth phpredis
59-
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisArray --auth phpredis
60-
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisCluster --auth phpredis
59+
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class Redis --user phpredis --auth phpredis
60+
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis
61+
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis
6162
- USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisSentinel --auth phpredis

README.markdown

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeou
7575
* timeout (float): the connection timeout to a redis host, expressed in seconds. If the host is unreachable in that amount of time, the session storage will be unavailable for the client. The default timeout is very high (86400 seconds).
7676
* persistent (integer, should be 1 or 0): defines if a persistent connection should be used. **(experimental setting)**
7777
* prefix (string, defaults to "PHPREDIS_SESSION:"): used as a prefix to the Redis key in which the session is stored. The key is composed of the prefix followed by the session ID.
78-
* auth (string, empty by default): used to authenticate with the server prior to sending commands.
78+
* auth (string, or an array with one or two elements): used to authenticate with the server prior to sending commands.
7979
* database (integer): selects a different database.
8080

8181
Sessions have a lifetime expressed in seconds and stored in the INI variable "session.gc_maxlifetime". You can change it with [`ini_set()`](http://php.net/ini_set).
@@ -270,18 +270,27 @@ $redis->pconnect('/tmp/redis.sock'); // unix domain socket - would be another co
270270

271271
### auth
272272
-----
273-
_**Description**_: Authenticate the connection using a password.
273+
_**Description**_: Authenticate the connection using a password or a username and password.
274274
*Warning*: The password is sent in plain-text over the network.
275275

276276
##### *Parameters*
277-
*STRING*: password
277+
*MIXED*: password
278278

279279
##### *Return value*
280280
*BOOL*: `TRUE` if the connection is authenticated, `FALSE` otherwise.
281281

282+
*Note*: In order to authenticate with a username and password you need Redis >= 6.0.
283+
282284
##### *Example*
283285
~~~php
286+
/* Authenticate with the password 'foobared' */
284287
$redis->auth('foobared');
288+
289+
/* Authenticate with the username 'phpredis', and password 'haxx00r' */
290+
$redis->auth(['phpredis', 'haxx00r']);
291+
292+
/* Authenticate with the password 'foobared' */
293+
$redis->auth(['foobared']);
285294
~~~
286295

287296
### select
@@ -417,6 +426,7 @@ _**Description**_: Sends a string to Redis, which replies with the same string
417426

418427
## Server
419428

429+
1. [acl](#acl) - Manage Redis ACLs
420430
1. [bgRewriteAOF](#bgrewriteaof) - Asynchronously rewrite the append-only file
421431
1. [bgSave](#bgsave) - Asynchronously save the dataset to disk (in background)
422432
1. [config](#config) - Get or Set the Redis server configuration parameters
@@ -431,6 +441,23 @@ _**Description**_: Sends a string to Redis, which replies with the same string
431441
1. [time](#time) - Return the current server time
432442
1. [slowLog](#slowlog) - Access the Redis slowLog entries
433443

444+
### acl
445+
-----
446+
_**Description**_: Execute the Redis ACL command.
447+
448+
##### *Parameters*
449+
_variable_: Minumum of one argument for `Redis` and two for `RedisCluster`.
450+
451+
##### *Example*
452+
~~~php
453+
$redis->acl('USERS'); /* Get a list of users */
454+
$redis->acl('LOG'); /* See log of Redis' ACL subsystem */
455+
~~~
456+
457+
*Note*: In order to user the `ACL` command you must be communicating with Redis >= 6.0 and be logged into an account that has access to administration commands such as ACL. Please reference [this tutorial](https://redis.io/topics/acl) for an overview of Redis 6 ACLs and [the redis command reference](https://redis.io/commands) for every ACL subcommand.
458+
459+
*Note*: If you are connecting to Redis server >= 4.0.0 you can remove a key with the `unlink` method in the exact same way you would use `del`. The Redis [unlink](https://redis.io/commands/unlink) command is non-blocking and will perform the actual deletion asynchronously.
460+
434461
### bgRewriteAOF
435462
-----
436463
_**Description**_: Start the background rewrite of AOF (Append-Only File)

cluster_library.c

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,7 @@ cluster_node_create(redisCluster *c, char *host, size_t host_len,
637637
node->sock = redis_sock_create(host, host_len, port, c->timeout,
638638
c->read_timeout, c->persistent, NULL, 0);
639639

640-
if (c->flags->auth) {
641-
node->sock->auth = zend_string_copy(c->flags->auth);
642-
}
640+
redis_sock_set_auth(node->sock, c->flags->user, c->flags->pass);
643641

644642
return node;
645643
}
@@ -850,8 +848,8 @@ cluster_free(redisCluster *c, int free_ctx)
850848

851849
/* Free any allocated prefix */
852850
if (c->flags->prefix) zend_string_release(c->flags->prefix);
853-
/* Free auth info we've got */
854-
if (c->flags->auth) zend_string_release(c->flags->auth);
851+
852+
redis_sock_free_auth(c->flags);
855853
efree(c->flags);
856854

857855
/* Call hash table destructors */
@@ -1050,10 +1048,8 @@ cluster_init_seeds(redisCluster *cluster, zend_string **seeds, uint32_t nseeds)
10501048
(unsigned short)atoi(sep+1), cluster->timeout,
10511049
cluster->read_timeout, cluster->persistent, NULL, 0);
10521050

1053-
// Set auth information if specified
1054-
if (cluster->flags->auth) {
1055-
sock->auth = zend_string_copy(cluster->flags->auth);
1056-
}
1051+
/* Credentials */
1052+
redis_sock_set_auth(sock, cluster->flags->user, cluster->flags->pass);
10571053

10581054
// Index this seed by host/port
10591055
key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(sock->host),
@@ -2294,11 +2290,40 @@ cluster_xinfo_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx)
22942290
add_next_index_zval(&c->multi_resp, &z_ret);
22952291
}
22962292

2293+
static void
2294+
cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx,
2295+
int (*cb)(RedisSock*, zval*, long))
2296+
{
2297+
zval z_ret;
2298+
2299+
array_init(&z_ret);
2300+
if (cb(c->cmd_sock, &z_ret, c->reply_len) != SUCCESS) {
2301+
zval_dtor(&z_ret);
2302+
CLUSTER_RETURN_FALSE(c);
2303+
}
2304+
2305+
if (CLUSTER_IS_ATOMIC(c)) {
2306+
RETURN_ZVAL(&z_ret, 0, 1);
2307+
}
2308+
add_next_index_zval(&c->multi_resp, &z_ret);
2309+
}
2310+
2311+
PHP_REDIS_API void
2312+
cluster_acl_getuser_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) {
2313+
cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx, redis_read_acl_getuser_reply);
2314+
}
2315+
2316+
PHP_REDIS_API void
2317+
cluster_acl_log_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) {
2318+
cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx, redis_read_acl_log_reply);
2319+
}
2320+
22972321
/* MULTI BULK response loop where we might pull the next one */
22982322
PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
22992323
redisCluster *c, int pull, mbulk_cb cb, zval *z_ret)
23002324
{
23012325
ZVAL_NULL(z_ret);
2326+
23022327
// Pull our next response if directed
23032328
if (pull) {
23042329
if (cluster_check_response(c, &c->reply_type) < 0)
@@ -2712,7 +2737,7 @@ void free_seed_array(zend_string **seeds, uint32_t nseeds) {
27122737
if (seeds == NULL)
27132738
return;
27142739

2715-
for (i = 0; i < nseeds; i++)
2740+
for (i = 0; i < nseeds; i++)
27162741
zend_string_release(seeds[i]);
27172742

27182743
efree(seeds);
@@ -2771,11 +2796,11 @@ static zend_string **get_valid_seeds(HashTable *input, uint32_t *nseeds) {
27712796
/* Validate cluster construction arguments and return a sanitized and validated
27722797
* array of seeds */
27732798
zend_string**
2774-
cluster_validate_args(double timeout, double read_timeout, HashTable *seeds,
2775-
uint32_t *nseeds, char **errstr)
2799+
cluster_validate_args(double timeout, double read_timeout, HashTable *seeds,
2800+
uint32_t *nseeds, char **errstr)
27762801
{
27772802
zend_string **retval;
2778-
2803+
27792804
if (timeout < 0L || timeout > INT_MAX) {
27802805
if (errstr) *errstr = "Invalid timeout";
27812806
return NULL;
@@ -2862,21 +2887,9 @@ PHP_REDIS_API redisCachedCluster *cluster_cache_load(zend_string *hash) {
28622887

28632888
/* Cache a cluster's slot information in persistent_list if it's enabled */
28642889
PHP_REDIS_API int cluster_cache_store(zend_string *hash, HashTable *nodes) {
2865-
redisCachedCluster *cc;
2866-
2867-
/* Construct our cache */
2868-
cc = cluster_cache_create(hash, nodes);
2869-
2870-
/* Set up our resource */
2871-
#if PHP_VERSION_ID < 70300
2872-
zend_resource le;
2873-
le.type = le_cluster_slot_cache;
2874-
le.ptr = cc;
2890+
redisCachedCluster *cc = cluster_cache_create(hash, nodes);
28752891

2876-
zend_hash_update_mem(&EG(persistent_list), cc->hash, (void*)&le, sizeof(zend_resource));
2877-
#else
2878-
zend_register_persistent_resource_ex(cc->hash, cc, le_cluster_slot_cache);
2879-
#endif
2892+
redis_register_persistent_resource(cc->hash, cc, le_cluster_slot_cache);
28802893

28812894
return SUCCESS;
28822895
}

cluster_library.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,10 @@ PHP_REDIS_API void cluster_xclaim_resp(INTERNAL_FUNCTION_PARAMETERS,
498498
PHP_REDIS_API void cluster_xinfo_resp(INTERNAL_FUNCTION_PARAMETERS,
499499
redisCluster *c, void *ctx);
500500

501+
/* Custom ACL handlers */
502+
PHP_REDIS_API void cluster_acl_getuser_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx);
503+
PHP_REDIS_API void cluster_acl_log_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx);
504+
501505
/* MULTI BULK processing callbacks */
502506
int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result,
503507
long long count, void *ctx);

common.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ typedef enum {
210210
REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \
211211
}
212212

213+
/* Case sensitive compare against compile-time static string */
214+
#define REDIS_STRCMP_STATIC(s, len, sstr) \
215+
(len == sizeof(sstr) - 1 && !strncmp(s, sstr, len))
216+
213217
/* Case insensitive compare against compile-time static string */
214218
#define REDIS_STRICMP_STATIC(s, len, sstr) \
215219
(len == sizeof(sstr) - 1 && !strncasecmp(s, sstr, len))
@@ -263,7 +267,8 @@ typedef struct {
263267
php_stream_context *stream_ctx;
264268
zend_string *host;
265269
int port;
266-
zend_string *auth;
270+
zend_string *user;
271+
zend_string *pass;
267272
double timeout;
268273
double read_timeout;
269274
long retry_interval;

0 commit comments

Comments
 (0)