Skip to content

NoPermissionError when using AWS ElastiCache #396

Open
@epicserve

Description

@epicserve

When trying to deploy Django Channels for the first time in production, I got the Redis NoPermissionError exception when using the group_send() function.

Example code that would produce the error:

from asgiref.sync import async_to_sync
from channels_redis.core import RedisChannelLayer

redis_url = (
    "rediss://<user>:<password>@master.<endpoint>.use1.cache.amazonaws.com:6379"
)
to_username = 'test'
data = {"type": "push_message", "message": "Testing 1, 2, 3."}
channel_layer = RedisChannelLayer(hosts=[{"address": redis_url, "db": 0, "ssl_cert_reqs": None}])
async_to_sync(channel_layer.group_send)(f"push_message_{to_username}", data)

The Traceback from the Exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/opt/venv/lib/python3.11/site-packages/asgiref/sync.py", line 240, in __call__
    return call_result.result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/opt/venv/lib/python3.11/site-packages/asgiref/sync.py", line 306, in main_wrap
    result = await self.awaitable(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/channels_redis/core.py", line 529, in group_send
    await connection.zremrangebyscore(
  File "/opt/venv/lib/python3.11/site-packages/sentry_sdk/integrations/redis/_async_common.py", line 98, in _sentry_execute_command
    value = await old_execute_command(self, name, *args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/redis/asyncio/client.py", line 612, in execute_command
    return await conn.retry.call_with_retry(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/redis/asyncio/retry.py", line 59, in call_with_retry
    return await do()
           ^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/redis/asyncio/client.py", line 586, in _send_command_parse_response
    return await self.parse_response(conn, command_name, **options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/redis/asyncio/client.py", line 633, in parse_response
    response = await connection.read_response()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/redis/asyncio/connection.py", line 569, in read_response
    raise response from None
redis.exceptions.NoPermissionError: this user has no permissions to access one of the keys used as arguments

I spent a long time debugging this and trying to figure out why it was failing because there wasn't anything I could find in the docs or searching the internet, and using ChatGPT wasn't helping much. It wasn't until I used the redis-cli in production and tried running the ACL GETUSER <username> command, which returned an error that then led me to discover that the user I was using only had the access string on ~app-test/* +@all -@dangerous +keys +info set. Looking at the docs, it says that the default prefix it uses is asgi, which explains why I was getting the error. When I added the prefix to RedisChannelLayer() (e.g., channel_layer = RedisChannelLayer(hosts=[{"address": redis_url, "db": 0, "ssl_cert_reqs": None}], prefix="app-test/")), the group_send() function worked. So I then updated the Django settings with the following:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [REDIS_URL],
            "prefix": REDIS_PREFIX,
        },
    },
}

Is there an explanation that could be added to the docs to make this easier to troubleshoot, or could a try/except be added in the code so that when it raises the exception, it gives a letter a better hint of what is going on? Maybe it could include the key it was trying to write to in the exception and suggest looking at the ACL for the Redis user.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions