Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting redis username #1351

Merged

Conversation

gabor-boros
Copy link
Contributor

Although the underlying Redis package supports setting username for connections, the connection params for username are not passed to the Redis client.

This PR extracts the userid from connection info (or host) if and passes to Redis client ensuring the connection can be established for other users than the default default user (set when username is None).

Dependencies: None

Testing instructions:

  1. Start Redis (using docker: docker run --name kombu_redis --rm -it -p 6379:6379 redis:latest)
  2. Connect to redis using redis-cli in a separate terminal window (docker exec -it kombu_redis redis-cli)
  3. List ACLs (ACL LIST) and validate only default user is listed
  4. Run snippet "example 1" (defined below) and validate the message object is printed
  5. Set a new password for default by running acl setuser default on >securepass
  6. List ACLs (ACL LIST) and validate default user now has a password hash set
  7. Set a new user by running acl setuser testuser on >password allcommands
  8. List ACLs (ACL LIST) and validate testuser is created with a password hash
  9. Run snippet "example 2" (defined below) and validate the message object is printed

Example 1

from kombu import Exchange, Queue, Producer, Connection, Consumer
connection = Connection('redis://')
channel = connection.channel()
producer = Producer(channel)
queue = Queue('a_queue_name', exchange=Exchange('default'))

# Publish a message
producer.publish('message_1', exchange=queue.exchange, declare=[queue])

def printer(message):
    print(message)
    # Remove the line below for unacknowledged behavior
    message.ack()

with Consumer(connection, [queue], on_message=printer):
    connection.drain_events()

Example 2

from kombu import Exchange, Queue, Producer, Connection, Consumer
connection = Connection('redis://testuser:password@127.0.0.1:6379/0')
channel = connection.channel()
producer = Producer(channel)
queue = Queue('queue_name', exchange=Exchange('default'))

# Publish a message
producer.publish('message_1', exchange=queue.exchange, declare=[queue])

def printer(message):
    print(message)
    # Remove the line below for unacknowledged behavior
    message.ack()

with Consumer(connection, [queue], on_message=printer):                      
    connection.drain_events()

Author notes and concerns:

Redis' "new" ACL features released as part of Redis 6 introduced the possibility of defining additional users identified by passwords. Therefore it would be a great achievement if Kombu (and Celery) would support connecting to Redis using an ACL limited user.

Copy link
Contributor

@pomegranited pomegranited left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Have requested 1 additional test, and I had to use a slightly modified command to set up the testuser, but this works great, thank you @gabor-boros !

acl setuser testuser on >password allkeys allcommands

  • I tested this using the instructions in the PR description.
  • I read through the code
  • Includes documentation N/A -- standard connection string

t/unit/transport/test_redis.py Show resolved Hide resolved
@@ -944,6 +944,7 @@ def _connparams(self, asynchronous=False):
'host': conninfo.hostname or '127.0.0.1',
'port': conninfo.port or self.connection.default_port,
'virtual_host': conninfo.virtual_host,
'username': conninfo.userid,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also update the related docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@auvipy Actually, this is a good question. I'd say yes if the documentation explicitly says that the username part of the connection URL won't be parsed (before this change), otherwise, I'd say that this should be "mainstream" that a connection URL can contain a username.

If you would like me, I'm happy to find the relevant section in the documentation and explicitly state that, from now on, usernames will be parsed too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thedrow @auvipy I'm happy to extend any documentation, if you could advise a bit. The documentation has no mention at all about usernames in terms of the Redis transport. In fact, the documentation is defined as

.. code-block::

    redis://REDIS_ADDRESS[:PORT][/VIRTUALHOST]
    rediss://REDIS_ADDRESS[:PORT][/VIRTUALHOST]

To use sentinel for dynamic Redis discovery,
the connection string has the following format:

.. code-block::

    sentinel://SENTINEL_ADDRESS[:PORT]

I do believe -- but correct me if I'm wrong -- the reason behind this is that the host parameter (REDIS_ADDRESS) may contain username and password as per RFC 2617. If you would like me, I can describe that in the inline documentation, though that would be a bit verbose.

If you would like me to extend another documentation that I did not find (yet), please help me with the link.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation is not 100% complete. I am not 100% whether is good idea to refer to some standard because URL parsing is done by urllib.parse.urlparse but some magic is involved during the parsing - see kombu/utils/url.py:url_to_parts(). Let it keep simple. Please extend the documentation as follows:

redis://[USER:PASSWORD@]REDIS_ADDRESS[:PORT][/VIRTUALHOST]
rediss://[USER:PASSWORD@]REDIS_ADDRESS[:PORT][/VIRTUALHOST]

Maybe we can also add some examples to the documentation.

Otherwise, the PR seems to be fine for me.

@matusvalo
Copy link
Member

@gabor-boros thank you for our PR. Let me review this PR. I try to do it in next few days.

@matusvalo matusvalo self-assigned this Jul 8, 2021
@matusvalo matusvalo merged commit 2d036f5 into celery:master Jul 8, 2021
@matusvalo
Copy link
Member

Thank you @gabor-boros for your PR. I have accepted the PR. The documentation update is committed in next commit.

@pomegranited pomegranited deleted the gabor/add-redis-username-support branch July 9, 2021 01:26
pomegranited pushed a commit to open-craft/kombu that referenced this pull request Aug 20, 2021
* feat: add support for setting redis username

* tests: add redis connparams credentials tests

(cherry picked from commit 2d036f5)
pomegranited pushed a commit to open-craft/kombu that referenced this pull request Aug 28, 2021
* feat: add support for setting redis username

* tests: add redis connparams credentials tests

(cherry picked from commit 2d036f5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants