├── .github ├── dependabot.yml └── workflows │ ├── python-release.yml │ └── python-tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGES ├── LICENSE ├── Makefile ├── README.md ├── pyproject.toml ├── requirements-dev.lock ├── requirements.lock ├── setup.cfg ├── src └── django_iam_dbauth │ ├── __init__.py │ ├── aws │ ├── __init__.py │ ├── database_wrapper.py │ ├── mysql │ │ ├── __init__.py │ │ └── base.py │ └── postgresql │ │ ├── __init__.py │ │ └── base.py │ └── utils.py └── tests ├── __init__.py ├── conftest.py ├── test_aws_mysql.py └── test_aws_postgresql.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 5 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | open-pull-requests-limit: 5 13 | -------------------------------------------------------------------------------- /.github/workflows/python-release.yml: -------------------------------------------------------------------------------- 1 | name: Release to PyPi 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Set up Python 3.12 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: "3.12" 17 | 18 | - name: Install the latest version of rye 19 | uses: eifinger/setup-rye@v4 20 | 21 | - name: Build package 22 | run: rye build 23 | 24 | - name: Publish package 25 | uses: pypa/gh-action-pypi-publish@release/v1 26 | with: 27 | user: __token__ 28 | password: ${{ secrets.pypi_password }} 29 | -------------------------------------------------------------------------------- /.github/workflows/python-tests.yml: -------------------------------------------------------------------------------- 1 | name: Python Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Set up Python 11 | uses: actions/setup-python@v5 12 | with: 13 | python-version: "3.12" 14 | 15 | - name: Install the latest version of rye 16 | uses: eifinger/setup-rye@v4 17 | 18 | - name: Install dependencies 19 | run: rye sync 20 | 21 | - name: Validate formatting 22 | run: rye format --check 23 | 24 | - name: Test 25 | run: rye test 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | .tox 4 | .coverage* 5 | htmlcov/ 6 | dist/ 7 | build/ 8 | env 9 | .idea/ 10 | .pytest_cache/ 11 | venv/ 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # File format: https://pre-commit.com/#plugins 2 | # Supported hooks: https://pre-commit.com/hooks.html 3 | # Running "make format" fixes most issues for you 4 | repos: 5 | - repo: https://github.com/psf/black 6 | rev: 24.4.2 7 | hooks: 8 | - id: black 9 | exclude: migrations/ 10 | - repo: https://github.com/charliermarsh/ruff-pre-commit 11 | # Ruff version. 12 | rev: "v0.5.0" 13 | hooks: 14 | - id: ruff 15 | - repo: https://github.com/pre-commit/pre-commit-hooks 16 | rev: v4.6.0 17 | hooks: 18 | - id: check-merge-conflict 19 | - id: debug-statements 20 | - id: trailing-whitespace 21 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.2.1 (2024.9.12) 2 | ================= 3 | - add precommit 4 | - remove breakpoint 5 | 6 | 0.2.0 (2024.8.9) 7 | ================ 8 | - chore: just simplify 9 | - ci: add whitelist_externals = python to tox 10 | - ci: attempt to fix issues 11 | - ci: install deps 12 | - ci: copy/paste issue 13 | - ci: resolve issues 14 | - switch to rye 15 | - chore: update release pipeline 16 | - Use boto3 session instead of boto3 client 17 | - Add dependabot support 18 | - chore: update dependencies and fix github actions 19 | - Adds resolve_cname_enabled option 20 | - Expand documentation with discussion on CNAME use. 21 | - Raise OperationalError when CNAME resolution fails instead of generic Exception. 22 | - Implement recursive CNAME resolution with a stop condition. 23 | 24 | 0.1.4 (2020.12.7) 25 | ================= 26 | - Remove default ca-central-1 region name for boto3 27 | 28 | 29 | 0.1.3 (2020.11.5) 30 | ================= 31 | - Add support for MySQL via AWS RDS 32 | - Add support for defining custom AWS RDS region name 33 | - Add Github actions workflows 34 | - Run tests against Python 3.8 and Django 3.1 as well 35 | 36 | 37 | 0.1.2 (2019-09-26) 38 | ================== 39 | - Fix github actions release workflow 40 | 41 | 42 | 0.1.1 (2019-09-26) 43 | ================== 44 | - Fix deployment pipeline 45 | 46 | 47 | 0.1.0 (2019-09-26) 48 | ================== 49 | - Resolve the hostname if it is a CNAME to use the correct RDS hostname 50 | for generating the auth token 51 | 52 | 53 | 0.3.0 (2019-09-12) 54 | ================== 55 | - Only enable the IAM token generation when the db option `use_iam_auth` 56 | is set. 57 | 58 | 59 | 0.2.0 (2019-09-11) 60 | ================== 61 | - Fix KeyError when no password was set 62 | 63 | 64 | 0.1.0 (2019-09-11) 65 | ================== 66 | - Initial project release 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Lab Digital B.V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install test upload docs format 2 | 3 | install: 4 | pip install -e .[docs,test] 5 | 6 | test: 7 | py.test 8 | 9 | retest: 10 | py.test -vvv --lf 11 | 12 | coverage: 13 | py.test --cov=django_iam_dbauth --cov-report=term-missing --cov-report=html 14 | 15 | docs: 16 | $(MAKE) -C docs html 17 | 18 | release: 19 | rm -rf dist/* 20 | python setup.py sdist bdist_wheel 21 | twine upload dist/* 22 | 23 | format: 24 | isort --profile black src tests 25 | black -t py310 src/ tests/ 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django IAM database backends 2 | 3 | 4 | [![codecov](https://codecov.io/gh/labd/django-iam-dbauth/branch/master/graph/badge.svg)](https://codecov.io/gh/labd/django-iam-dbauth) 5 | [![pypi](https://img.shields.io/pypi/v/django-iam-dbauth.svg)](https://pypi.python.org/pypi/django-iam-dbauth/) 6 | [![readthedocs](https://readthedocs.org/projects/django-iam-dbauth/badge/)](https://django-iam-dbauth.readthedocs.io/en/latest/) 7 | [![tests](https://github.com/labd/django-iam-dbauth/workflows/Python%20Tests/badge.svg)](https://github.com/labd/django-iam-dbauth/actions) 8 | 9 | 10 | ## Usage 11 | 12 | ```shell 13 | pip install django-iam-dbauth 14 | ``` 15 | 16 | In your settings use the following 17 | 18 | ```python 19 | DATABASES = { 20 | "default": { 21 | "HOST": "", 22 | "USER": "", 23 | "NAME": "", 24 | "ENGINE": 'django_iam_dbauth.aws.postgresql', 25 | "OPTIONS": { 26 | "use_iam_auth": True, 27 | "sslmode": "require", # See discussion on SSL below 28 | "resolve_cname_enabled": True, 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | ### SSL and PostgreSQL 35 | 36 | When using IAM authentication with RDS, SSL is required. If it's not used, such as when using a CNAME (see below), login will be denied with the below error: 37 | 38 | ``` 39 | django.db.utils.OperationalError: FATAL: pg_hba.conf rejects connection for host "1.2.3.4", user "some_user", database "some_database", SSL off 40 | ``` 41 | 42 | ### SSL and MySQL 43 | 44 | Acquired Token won't work with MySQL if use RDS instance name, which is a CNAME record, as a HOST, because by default it will be resolved to a cannonical name by django-iam-dbauth. As a result the hostname of the token will bi different. To prevent the module from resolving CNAME, set "resolve_cname_enabled" to False. 45 | 46 | ### CNAME considerations 47 | 48 | Currently, IAM authentication is not supported with CNAMEs. However, this package does CNAME resolution so that the 49 | signed request for a password will work. The issue with this approach is that from the DB library's point of view, the 50 | connection is initiated to the hostname as defined in the settings. If using SSL, certificate verification will fail. 51 | In this case, for PostgreSQL you may want to set `sslmode` to `require` or `verify-ca`. 52 | 53 | Further documentation: 54 | 55 | * [PostgreSQL SSL modes](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION) 56 | * [AWS guide on IAM DB authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "django-iam-dbauth" 3 | version = "0.2.1" 4 | description = "Django database backends to use AWS Database IAM Authentication" 5 | readme = "README.md" 6 | license = { text = "MIT" } 7 | authors = [ 8 | { name = "Lab Digital", email = "opensource@labdigital.nl" } 9 | ] 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Environment :: Web Environment", 13 | "Framework :: Django", 14 | "Framework :: Django :: 4.2", 15 | "Framework :: Django :: 5.0", 16 | "Framework :: Django :: 5.1", 17 | "License :: OSI Approved :: MIT License", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Programming Language :: Python :: 3.12", 25 | ] 26 | dependencies = [ 27 | "Django>=4.2", 28 | "boto3", 29 | "dnspython" 30 | ] 31 | 32 | [project.urls] 33 | Homepage = "https://github.com/LabD/django-iam-dbauth" 34 | 35 | [tool.rye] 36 | managed = true 37 | dev-dependencies = [ 38 | "pytest>=8.3.2", 39 | "coverage>=7.6.1", 40 | "tox>=4.18.0", 41 | "tox-gh-actions>=3.2.0", 42 | "pretend>=1.0.9", 43 | "psycopg2>=2.9.9", 44 | "mysqlclient>=2.2.4", 45 | "pytest-mock>=3.14.0", 46 | "pytest-django>=4.8.0", 47 | "moto>=5.0.13", 48 | ] 49 | 50 | [build-system] 51 | requires = ["hatchling"] 52 | build-backend = "hatchling.build" 53 | 54 | [tool.hatch.metadata] 55 | allow-direct-references = true 56 | 57 | [tool.hatch.build.targets.wheel] 58 | packages = ["src/django_iam_dbauth"] 59 | 60 | [tool.ruff] 61 | src = ["src", "tests"] 62 | fix = true 63 | -------------------------------------------------------------------------------- /requirements-dev.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -e file:. 13 | asgiref==3.8.1 14 | # via django 15 | boto3==1.35.0 16 | # via django-iam-dbauth 17 | # via moto 18 | botocore==1.35.0 19 | # via boto3 20 | # via moto 21 | # via s3transfer 22 | cachetools==5.5.0 23 | # via tox 24 | certifi==2024.7.4 25 | # via requests 26 | cffi==1.17.0 27 | # via cryptography 28 | chardet==5.2.0 29 | # via tox 30 | charset-normalizer==3.3.2 31 | # via requests 32 | colorama==0.4.6 33 | # via tox 34 | coverage==7.6.1 35 | cryptography==43.0.0 36 | # via moto 37 | distlib==0.3.8 38 | # via virtualenv 39 | django==5.1 40 | # via django-iam-dbauth 41 | dnspython==2.6.1 42 | # via django-iam-dbauth 43 | filelock==3.15.4 44 | # via tox 45 | # via virtualenv 46 | idna==3.7 47 | # via requests 48 | iniconfig==2.0.0 49 | # via pytest 50 | jinja2==3.1.4 51 | # via moto 52 | jmespath==1.0.1 53 | # via boto3 54 | # via botocore 55 | markupsafe==2.1.5 56 | # via jinja2 57 | # via werkzeug 58 | moto==5.0.13 59 | mysqlclient==2.2.4 60 | packaging==24.1 61 | # via pyproject-api 62 | # via pytest 63 | # via tox 64 | platformdirs==4.2.2 65 | # via tox 66 | # via virtualenv 67 | pluggy==1.5.0 68 | # via pytest 69 | # via tox 70 | pretend==1.0.9 71 | psycopg2==2.9.9 72 | pycparser==2.22 73 | # via cffi 74 | pyproject-api==1.7.1 75 | # via tox 76 | pytest==8.3.2 77 | # via pytest-django 78 | # via pytest-mock 79 | pytest-django==4.8.0 80 | pytest-mock==3.14.0 81 | python-dateutil==2.9.0.post0 82 | # via botocore 83 | # via moto 84 | pyyaml==6.0.2 85 | # via responses 86 | requests==2.32.3 87 | # via moto 88 | # via responses 89 | responses==0.25.3 90 | # via moto 91 | s3transfer==0.10.2 92 | # via boto3 93 | six==1.16.0 94 | # via python-dateutil 95 | sqlparse==0.5.1 96 | # via django 97 | tox==4.18.0 98 | # via tox-gh-actions 99 | tox-gh-actions==3.2.0 100 | urllib3==2.2.2 101 | # via botocore 102 | # via requests 103 | # via responses 104 | virtualenv==20.26.3 105 | # via tox 106 | werkzeug==3.0.3 107 | # via moto 108 | xmltodict==0.13.0 109 | # via moto 110 | -------------------------------------------------------------------------------- /requirements.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -e file:. 13 | asgiref==3.8.1 14 | # via django 15 | boto3==1.35.0 16 | # via django-iam-dbauth 17 | botocore==1.35.0 18 | # via boto3 19 | # via s3transfer 20 | django==5.1 21 | # via django-iam-dbauth 22 | dnspython==2.6.1 23 | # via django-iam-dbauth 24 | jmespath==1.0.1 25 | # via boto3 26 | # via botocore 27 | python-dateutil==2.9.0.post0 28 | # via botocore 29 | s3transfer==0.10.2 30 | # via boto3 31 | six==1.16.0 32 | # via python-dateutil 33 | sqlparse==0.5.1 34 | # via django 35 | urllib3==2.2.2 36 | # via botocore 37 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.4 3 | commit = true 4 | tag = true 5 | tag_name = {new_version} 6 | 7 | [tool:pytest] 8 | minversion = 3.0 9 | testpaths = tests 10 | 11 | [wheel] 12 | python-tag = py3 13 | 14 | [flake8] 15 | max-line-length = 99 16 | 17 | [bumpversion:file:setup.py] 18 | -------------------------------------------------------------------------------- /src/django_iam_dbauth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labd/django-iam-dbauth/bdfc67a7bf10c29745e728067ecc1d4398c1ec47/src/django_iam_dbauth/__init__.py -------------------------------------------------------------------------------- /src/django_iam_dbauth/aws/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labd/django-iam-dbauth/bdfc67a7bf10c29745e728067ecc1d4398c1ec47/src/django_iam_dbauth/aws/__init__.py -------------------------------------------------------------------------------- /src/django_iam_dbauth/aws/database_wrapper.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | 3 | import boto3 4 | 5 | from django_iam_dbauth.utils import resolve_cname 6 | 7 | 8 | def get_aws_connection_params(params): 9 | enabled = params.pop("use_iam_auth", None) 10 | if enabled: 11 | region_name = params.pop("region_name", None) 12 | session = boto3.session.Session() 13 | rds_client = session.client(service_name="rds", region_name=region_name) 14 | 15 | hostname = params.get("host") 16 | if hostname: 17 | hostname = ( 18 | resolve_cname(hostname) 19 | if params.pop("resolve_cname_enabled", True) 20 | else hostname 21 | ) 22 | else: 23 | hostname = "localhost" 24 | 25 | params["password"] = rds_client.generate_db_auth_token( 26 | DBHostname=hostname, 27 | Port=params.get("port", 5432), 28 | DBUsername=params.get("user") or getpass.getuser(), 29 | ) 30 | 31 | return params 32 | -------------------------------------------------------------------------------- /src/django_iam_dbauth/aws/mysql/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labd/django-iam-dbauth/bdfc67a7bf10c29745e728067ecc1d4398c1ec47/src/django_iam_dbauth/aws/mysql/__init__.py -------------------------------------------------------------------------------- /src/django_iam_dbauth/aws/mysql/base.py: -------------------------------------------------------------------------------- 1 | from django.db.backends.mysql import base 2 | 3 | from django_iam_dbauth.aws.database_wrapper import get_aws_connection_params 4 | 5 | 6 | class DatabaseWrapper(base.DatabaseWrapper): 7 | def get_connection_params(self): 8 | params = super().get_connection_params() 9 | params.setdefault("port", 3306) 10 | 11 | return get_aws_connection_params(params) 12 | -------------------------------------------------------------------------------- /src/django_iam_dbauth/aws/postgresql/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labd/django-iam-dbauth/bdfc67a7bf10c29745e728067ecc1d4398c1ec47/src/django_iam_dbauth/aws/postgresql/__init__.py -------------------------------------------------------------------------------- /src/django_iam_dbauth/aws/postgresql/base.py: -------------------------------------------------------------------------------- 1 | from django.db.backends.postgresql import base 2 | 3 | from django_iam_dbauth.aws.database_wrapper import get_aws_connection_params 4 | 5 | 6 | class DatabaseWrapper(base.DatabaseWrapper): 7 | def get_connection_params(self): 8 | params = super().get_connection_params() 9 | params.setdefault("port", 5432) 10 | 11 | return get_aws_connection_params(params) 12 | -------------------------------------------------------------------------------- /src/django_iam_dbauth/utils.py: -------------------------------------------------------------------------------- 1 | import dns.name 2 | import dns.rdatatype 3 | import dns.resolver 4 | from django.db.utils import OperationalError 5 | from dns.exception import DNSException 6 | 7 | 8 | def resolve_cname(hostname): 9 | """Resolve a CNAME record to the original RDS endpoint. 10 | 11 | This is required for AWS where the hostname of the RDS instance is part of 12 | the signing request. 13 | 14 | Looking for the endpoint which is of the form cluster-name.accountandregionhash.regionid.rds.amazonaws.com 15 | To do so, recursively resolve the host name until it's a subdomain of rds.amazonaws.com. 16 | """ 17 | base_domain = dns.name.from_text("rds.amazonaws.com") 18 | answer = dns.name.from_text(hostname) 19 | while not answer.is_subdomain(base_domain): 20 | try: 21 | # Replace deprecated `query` with `resolve` 22 | # There is one and only one answer for a CNAME and its type is CNAME. 23 | # If the name doesn't exist or exists with a different type, an exception is raised. 24 | answer = dns.resolver.resolve(answer, dns.rdatatype.CNAME, search=True)[ 25 | 0 26 | ].target 27 | except DNSException as e: 28 | # Break when resolution doesn't work. 29 | # This avoids cryptic authentication failures from RDS. 30 | raise OperationalError("Failed to resolve hostname to RDS endpoint.") from e 31 | 32 | return answer.to_text().strip(".") 33 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labd/django-iam-dbauth/bdfc67a7bf10c29745e728067ecc1d4398c1ec47/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from moto import mock_aws 3 | import boto3 4 | import pytest 5 | 6 | 7 | def pytest_configure(): 8 | settings.configure( 9 | HEALTH_CHECKS={}, 10 | MIDDLEWARE_CLASSES=[], 11 | SECRET_KEY="0123456789", 12 | INSTALLED_APPS=[ 13 | "django.contrib.admin", 14 | "django.contrib.contenttypes", 15 | "django.contrib.auth", 16 | "django.contrib.sessions", 17 | ], 18 | CACHES={ 19 | "default": { 20 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", 21 | "LOCATION": "unique-snowflake", 22 | } 23 | }, 24 | DATABASES={ 25 | "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "db.sqlite"} 26 | }, 27 | MIDDLEWARE=( 28 | "django.contrib.sessions.middleware.SessionMiddleware", 29 | "django.contrib.auth.middleware.AuthenticationMiddleware", 30 | ), 31 | TEMPLATES=[ 32 | { 33 | "BACKEND": "django.template.backends.django.DjangoTemplates", 34 | "OPTIONS": { 35 | "loaders": ("django.template.loaders.app_directories.Loader",) 36 | }, 37 | } 38 | ], 39 | USE_TZ=True, 40 | ROOT_URLCONF="test_urls", 41 | ) 42 | 43 | 44 | @pytest.fixture 45 | def rds_client(): 46 | # Use moto to mock the RDS client 47 | with mock_aws(): 48 | session = boto3.session.Session() 49 | client = session.client(service_name="rds", region_name="us-west-2") 50 | yield client 51 | -------------------------------------------------------------------------------- /tests/test_aws_mysql.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import urllib 3 | import pretend 4 | 5 | from django_iam_dbauth.aws.mysql.base import DatabaseWrapper 6 | 7 | 8 | def test_get_connection_params(rds_client): 9 | settings = { 10 | "NAME": "example", 11 | "USER": "mysql", 12 | "PASSWORD": "secret", 13 | "PORT": 3306, 14 | "HOST": "example-cname.labd.nl", 15 | "ENGINE": "django_iam_dbauth.aws.mysql", 16 | "OPTIONS": { 17 | "use_iam_auth": 1, 18 | "region_name": "test", 19 | "resolve_cname_enabled": False, 20 | }, 21 | } 22 | 23 | db = DatabaseWrapper(settings) 24 | params = db.get_connection_params() 25 | 26 | assert params["password"].startswith("example-cname.labd.nl") 27 | -------------------------------------------------------------------------------- /tests/test_aws_postgresql.py: -------------------------------------------------------------------------------- 1 | from django_iam_dbauth.aws.postgresql.base import DatabaseWrapper 2 | 3 | 4 | def test_get_connection_params(rds_client): 5 | settings = { 6 | "NAME": "example", 7 | "USER": "postgresql", 8 | "PASSWORD": "secret", 9 | "PORT": 5432, 10 | "HOST": "example-cname.labd.nl", 11 | "ENGINE": "django_iam_dbauth.aws.postgresql", 12 | "OPTIONS": { 13 | "use_iam_auth": 1, 14 | "region_name": "test", 15 | "resolve_cname_enabled": False, 16 | }, 17 | } 18 | 19 | db = DatabaseWrapper(settings) 20 | params = db.get_connection_params() 21 | 22 | assert params["password"].startswith("example-cname.labd.nl") 23 | --------------------------------------------------------------------------------