From 8326e369fb9d733d715ae1f4ec1be6f442f8d389 Mon Sep 17 00:00:00 2001 From: Bruno Brito Semedo Date: Fri, 30 Jan 2026 12:26:22 +0100 Subject: [PATCH] feat: add AWS IAM Database Authentication support for RDS/Aurora PostgreSQL This adds support for AWS IAM Database Authentication, allowing users to connect to Amazon RDS and Aurora PostgreSQL databases using IAM credentials instead of traditional passwords. Features: - New "AWS IAM authentication" toggle in the Server Connection tab - AWS Profile and Region configuration fields - Automatic IAM token generation using botocore - Automatic SSL enforcement (required by IAM auth) - Token refresh retry on authentication failures - No password prompt when IAM auth is enabled Implementation: - Database migration for new server fields (use_iam_auth, aws_profile, aws_region, aws_role_arn) - AWS IAM token generator module (web/pgadmin/utils/aws_iam.py) - Integration with ServerManager connection string generation - Connection retry logic for expired tokens - UI fields in server dialog - RST documentation Prerequisites for users: - AWS credentials configured (profile or environment variables) - RDS/Aurora instance with IAM auth enabled - Database user with rds_iam role granted - IAM policy allowing rds-db:connect action --- docs/en_US/aws_iam_authentication.rst | 134 ++++++++++++++++++ docs/en_US/getting_started.rst | 1 + docs/en_US/server_dialog.rst | 7 + requirements.txt | 1 + .../versions/7ce2161fb957_add_iam_auth.py | 41 ++++++ .../browser/server_groups/servers/__init__.py | 30 +++- .../servers/static/js/server.ui.js | 36 ++++- web/pgadmin/model/__init__.py | 7 +- web/pgadmin/utils/aws_iam.py | 110 ++++++++++++++ .../utils/driver/psycopg3/connection.py | 74 +++++++--- .../utils/driver/psycopg3/server_manager.py | 25 ++++ 11 files changed, 431 insertions(+), 35 deletions(-) create mode 100644 docs/en_US/aws_iam_authentication.rst create mode 100644 web/migrations/versions/7ce2161fb957_add_iam_auth.py create mode 100644 web/pgadmin/utils/aws_iam.py diff --git a/docs/en_US/aws_iam_authentication.rst b/docs/en_US/aws_iam_authentication.rst new file mode 100644 index 00000000000..9c4fba93c81 --- /dev/null +++ b/docs/en_US/aws_iam_authentication.rst @@ -0,0 +1,134 @@ +.. _aws_iam_authentication: + +******************************************* +`AWS IAM Database Authentication`:index: +******************************************* + +**Prerequisite:** AWS account with RDS/Aurora PostgreSQL and IAM configuration + +Reference: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html + +pgAdmin supports AWS IAM Database Authentication for connecting to Amazon RDS +and Aurora PostgreSQL databases. This feature allows you to authenticate using +AWS IAM credentials instead of a database password, providing enhanced security +through temporary authentication tokens. + +How It Works +============ + +When IAM authentication is enabled: + +1. pgAdmin generates a temporary authentication token using your AWS credentials +2. The token is valid for 15 minutes and is automatically refreshed as needed +3. SSL/TLS is automatically enforced (required by AWS for IAM authentication) +4. No database password needs to be stored or transmitted + +Prerequisites +============= + +Before using IAM authentication with pgAdmin, ensure the following: + +AWS RDS/Aurora Configuration +---------------------------- + +* IAM Database Authentication must be enabled on your RDS instance or Aurora cluster +* A database user must be created and granted the ``rds_iam`` role: + + .. code-block:: sql + + CREATE USER your_username WITH LOGIN; + GRANT rds_iam TO your_username; + +* An IAM policy must allow the ``rds-db:connect`` action for the database user + + .. code-block:: json + + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "rds-db:connect", + "Resource": "arn:aws:rds-db:region:account-id:dbuser:resource-id/db-user-name" + } + ] + } + +Local AWS Configuration +----------------------- + +* AWS credentials must be configured on the machine running pgAdmin +* Credentials can be provided via: + + * AWS credentials file (``~/.aws/credentials``) + * Environment variables (``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``) + * IAM role (when running on EC2/ECS/Lambda) + +* The ``botocore`` Python package must be installed (included with pgAdmin) + +Configuring IAM Authentication in pgAdmin +========================================= + +To connect to a PostgreSQL server using IAM authentication: + +1. Open the *Server* dialog (right-click on *Servers* and select *Register* > *Server*) +2. In the *Connection* tab: + + * Set *Host name/address* to your RDS/Aurora endpoint + * Set *Port* to the database port (default: 5432) + * Set *Maintenance database* to the database name + * Set *Username* to the IAM-enabled database user + * Enable *AWS IAM authentication* + * Leave the *Password* field empty + +3. Configure the AWS-specific fields: + + * *AWS Profile*: (Optional) The AWS profile name from your credentials file. + Leave empty to use the default profile or environment credentials. + * *AWS Region*: The AWS region where your RDS/Aurora instance is located + (e.g., ``us-east-1``, ``eu-west-1``). + +4. Click *Save* to store the server configuration + +Connection Behavior +=================== + +When connecting with IAM authentication: + +* pgAdmin automatically generates a fresh IAM authentication token +* If the connection fails due to an expired token, pgAdmin will automatically + retry with a new token +* SSL mode is automatically set to ``require`` if not explicitly configured +* You will not be prompted for a password + +Troubleshooting +=============== + +**Connection fails with "PAM authentication failed"** + +* Verify the database user has the ``rds_iam`` role granted +* Ensure the IAM policy allows the ``rds-db:connect`` action +* Check that the AWS region is correctly configured + +**Token generation fails** + +* Verify AWS credentials are properly configured +* Check that the AWS profile name (if specified) exists in your credentials file +* Ensure the machine has network access to AWS STS endpoints + +**SSL connection required** + +* IAM authentication requires SSL. pgAdmin automatically enables SSL mode, + but ensure your RDS instance has SSL enabled and the client can establish + secure connections. + +Limitations +=========== + +* IAM authentication tokens expire after 15 minutes. pgAdmin handles token + refresh automatically, but very long-running idle connections may need + to reconnect. +* This feature requires the ``botocore`` Python package. +* IAM authentication is only supported for Amazon RDS and Aurora PostgreSQL, + not for self-hosted PostgreSQL servers. + diff --git a/docs/en_US/getting_started.rst b/docs/en_US/getting_started.rst index 6dfb6a2bcfb..08d33004519 100644 --- a/docs/en_US/getting_started.rst +++ b/docs/en_US/getting_started.rst @@ -40,6 +40,7 @@ Mode is pre-configured for security. restore_locked_user ldap kerberos + aws_iam_authentication oauth2 webserver diff --git a/docs/en_US/server_dialog.rst b/docs/en_US/server_dialog.rst index 233b438d023..798d3c30994 100644 --- a/docs/en_US/server_dialog.rst +++ b/docs/en_US/server_dialog.rst @@ -70,6 +70,13 @@ Use the fields in the *Connection* tab to configure a connection: authenticating with the server. * When *Kerberos authentication?* is set to *True*, pgAdmin will try to connect the PostgreSQL server using Kerberos authentication. +* When *AWS IAM authentication?* is set to *True*, pgAdmin will use AWS IAM + Database Authentication to connect to Amazon RDS or Aurora PostgreSQL. + For more information, see :ref:`AWS IAM Database Authentication `. + + * *AWS Profile*: (Optional) The AWS profile name from your credentials file. + * *AWS Region*: The AWS region where your RDS/Aurora instance is located. + * Use the *Password* field to provide a password that will be supplied when authenticating with the server. * Check the box next to *Save password?* to instruct pgAdmin to save the diff --git a/requirements.txt b/requirements.txt index 1cafae5e7a1..6fbbd818a31 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ azure-mgmt-resource==24.0.0 azure-mgmt-subscription==3.1.1 bcrypt==5.0.* boto3==1.42.* +botocore>=1.31.0 certifi==2026.1.4 cryptography==46.0.* Flask-Babel==4.0.* diff --git a/web/migrations/versions/7ce2161fb957_add_iam_auth.py b/web/migrations/versions/7ce2161fb957_add_iam_auth.py new file mode 100644 index 00000000000..d4604c17e54 --- /dev/null +++ b/web/migrations/versions/7ce2161fb957_add_iam_auth.py @@ -0,0 +1,41 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2026, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""Add AWS IAM authentication support + +Added use_iam_auth, aws_profile, aws_region, and aws_role_arn columns +to server configuration for AWS RDS/Aurora IAM authentication. + +Revision ID: 7ce2161fb957 +Revises: 018e16dad6aa +Create Date: 2026-01-30 11:00:00.000000 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = '7ce2161fb957' +down_revision = '018e16dad6aa' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('server', sa.Column('use_iam_auth', sa.Boolean(), + server_default='0', nullable=False)) + op.add_column('server', sa.Column('aws_profile', sa.String(length=64))) + op.add_column('server', sa.Column('aws_region', sa.String(length=32))) + op.add_column('server', sa.Column('aws_role_arn', sa.String(length=256))) + # ### end Alembic commands ### + + +def downgrade(): + # pgAdmin only upgrades, downgrade not implemented. + pass diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 0f317a08c09..ac13ee972b2 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -797,6 +797,10 @@ def update(self, gid, sid): 'shared': 'shared', 'shared_username': 'shared_username', 'kerberos_conn': 'kerberos_conn', + 'use_iam_auth': 'use_iam_auth', + 'aws_profile': 'aws_profile', + 'aws_region': 'aws_region', + 'aws_role_arn': 'aws_role_arn', 'connection_params': 'connection_params', 'prepare_threshold': 'prepare_threshold', 'tags': 'tags', @@ -1094,6 +1098,10 @@ def properties(self, gid, sid): 'tunnel_authentication': tunnel_authentication, 'tunnel_keep_alive': tunnel_keep_alive, 'kerberos_conn': bool(server.kerberos_conn), + 'use_iam_auth': bool(server.use_iam_auth) if hasattr(server, 'use_iam_auth') else False, + 'aws_profile': server.aws_profile if hasattr(server, 'aws_profile') and server.aws_profile else None, + 'aws_region': server.aws_region if hasattr(server, 'aws_region') and server.aws_region else None, + 'aws_role_arn': server.aws_role_arn if hasattr(server, 'aws_role_arn') and server.aws_role_arn else None, 'gss_authenticated': manager.gss_authenticated, 'gss_encrypted': manager.gss_encrypted, 'cloud_status': server.cloud_status, @@ -1225,6 +1233,10 @@ def create(self, gid): passexec_cmd=data.get('passexec_cmd', None), passexec_expiration=data.get('passexec_expiration', None), kerberos_conn=1 if data.get('kerberos_conn', False) else 0, + use_iam_auth=1 if data.get('use_iam_auth', False) else 0, + aws_profile=data.get('aws_profile', None), + aws_region=data.get('aws_region', None), + aws_role_arn=data.get('aws_role_arn', None), connection_params=connection_params, prepare_threshold=data.get('prepare_threshold', None), tags=data.get('tags', None), @@ -1455,7 +1467,7 @@ def connect(self, gid, sid, is_qt=False, server=None): establish the connection OR just connect the server and do not store the password. """ - current_app.logger.info( + current_app.logger.error( 'Connection Request for server#{0}'.format(sid) ) @@ -1543,9 +1555,11 @@ def connect(self, gid, sid, is_qt=False, server=None): except Exception as e: current_app.logger.exception(e) return internal_server_error(errormsg=str(e)) + # Skip password prompt for Kerberos and IAM authentication + use_iam_auth = getattr(server, 'use_iam_auth', False) if 'password' not in data and (server.kerberos_conn is False or - server.kerberos_conn is None): - + server.kerberos_conn is None) and \ + not use_iam_auth: passfile_param = None if hasattr(server, 'connection_params') and \ 'passfile' in server.connection_params: @@ -1595,8 +1609,10 @@ def connect(self, gid, sid, is_qt=False, server=None): server_types=ServerType.types() ) except Exception as e: + # Don't prompt for password on IAM auth failures - just show error + should_prompt_password = not server.save_password and not use_iam_auth return self.get_response_for_password( - server, 401, not server.save_password, prompt_tunnel_password, + server, 401, should_prompt_password, prompt_tunnel_password, getattr(e, 'message', str(e))) if not status: @@ -1607,8 +1623,10 @@ def connect(self, gid, sid, is_qt=False, server=None): if errmsg.find('Ticket expired') != -1: return internal_server_error(errmsg) + # Don't prompt for password on IAM auth failures - just show error + should_prompt_password = not server.save_password and not use_iam_auth return self.get_response_for_password( - server, 401, not server.save_password, + server, 401, should_prompt_password, prompt_tunnel_password, errmsg) else: if save_password and config.ALLOW_SAVE_PASSWORD: @@ -1652,7 +1670,7 @@ def connect(self, gid, sid, is_qt=False, server=None): return internal_server_error(errormsg=e.message) - current_app.logger.info('Connection Established for server: \ + current_app.logger.error('Connection Established for server: \ %s - %s' % (server.id, server.name)) # Update the recovery and wal pause option for the server # if connected successfully diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js index d7b11cfb8c8..6c950ac48f1 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js @@ -381,11 +381,37 @@ export default class ServerSchema extends BaseUISchema { },{ id: 'gss_encrypted', label: gettext('GSS encrypted?'), type: 'switch', group: gettext('Connection'), mode: ['properties'], visible: obj.isConnected, + },{ + id: 'use_iam_auth', label: gettext('AWS IAM authentication?'), type: 'switch', + group: gettext('Connection'), mode: ['create', 'edit'], + disabled: obj.isShared, + helpMessage: gettext('Use AWS IAM authentication tokens for RDS/Aurora PostgreSQL databases') + },{ + id: 'aws_profile', label: gettext('AWS Profile'), type: 'text', + group: gettext('Connection'), mode: ['create', 'edit'], + deps: ['use_iam_auth'], + disabled: function(state) { return !state.use_iam_auth; }, + readonly: obj.isConnected, + helpMessage: gettext('AWS profile name for credentials (leave empty for default)') + },{ + id: 'aws_region', label: gettext('AWS Region'), type: 'text', + group: gettext('Connection'), mode: ['create', 'edit'], + deps: ['use_iam_auth'], + disabled: function(state) { return !state.use_iam_auth; }, + readonly: obj.isConnected, + helpMessage: gettext('AWS region where the database is located (e.g., us-east-1)') + },{ + id: 'aws_role_arn', label: gettext('AWS Role ARN (Optional)'), type: 'text', + group: gettext('Connection'), mode: ['create', 'edit'], + deps: ['use_iam_auth'], + disabled: function(state) { return !state.use_iam_auth; }, + readonly: obj.isConnected, + helpMessage: gettext('IAM role ARN for cross-account or assumed role access') },{ id: 'password', label: gettext('Password'), type: 'password', group: gettext('Connection'), mode: ['create', 'edit'], - deps: ['kerberos_conn', 'save_password'], + deps: ['kerberos_conn', 'use_iam_auth', 'save_password'], controlProps: { maxLength: null, autoComplete: 'new-password' @@ -395,17 +421,17 @@ export default class ServerSchema extends BaseUISchema { return false; return state.connected || !state.save_password; }, - disabled: function(state) {return state.kerberos_conn;}, - helpMessage: gettext('In edit mode the password field is enabled only if Save Password is set to true.') + disabled: function(state) {return state.kerberos_conn || state.use_iam_auth;}, + helpMessage: gettext('In edit mode the password field is enabled only if Save Password is set to true. Password is not required for Kerberos or IAM authentication.') },{ id: 'save_password', label: gettext('Save password?'), type: 'switch', group: gettext('Connection'), mode: ['create', 'edit'], - deps: ['kerberos_conn'], + deps: ['kerberos_conn', 'use_iam_auth'], readonly: function(state) { return state.connected; }, disabled: function(state) { - return !current_user.allow_save_password || state.kerberos_conn; + return !current_user.allow_save_password || state.kerberos_conn || state.use_iam_auth; }, },{ id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'), diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 69b934683dd..cc6a39890c6 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -33,7 +33,7 @@ # ########################################################################## -SCHEMA_VERSION = 49 +SCHEMA_VERSION = 50 ########################################################################## # @@ -256,6 +256,11 @@ class Server(db.Model): shared = db.Column(db.Boolean(), nullable=False) shared_username = db.Column(db.String(64), nullable=True) kerberos_conn = db.Column(db.Boolean(), nullable=False, default=0) + # AWS IAM Authentication + use_iam_auth = db.Column(db.Boolean(), nullable=False, default=False) + aws_profile = db.Column(db.String(64), nullable=True) + aws_region = db.Column(db.String(32), nullable=True) + aws_role_arn = db.Column(db.String(256), nullable=True) cloud_status = db.Column(db.Integer(), nullable=False, default=0) connection_params = db.Column(MutableDict.as_mutable(types.JSON)) prepare_threshold = db.Column(db.Integer(), nullable=True) diff --git a/web/pgadmin/utils/aws_iam.py b/web/pgadmin/utils/aws_iam.py new file mode 100644 index 00000000000..bae5e335396 --- /dev/null +++ b/web/pgadmin/utils/aws_iam.py @@ -0,0 +1,110 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2026, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +"""AWS IAM Authentication Module + +This module provides functionality for generating AWS RDS/Aurora IAM +authentication tokens for database connections. +""" + +import logging +from flask_babel import gettext + +try: + import botocore.session + from botocore.exceptions import BotoCoreError, ClientError, \ + NoCredentialsError, PartialCredentialsError + BOTOCORE_AVAILABLE = True +except ImportError: + BOTOCORE_AVAILABLE = False + +_ = gettext + + +def generate_rds_auth_token(host, port, username, region, profile=None, + role_arn=None): + """ + Generate an AWS RDS IAM authentication token. + + Args: + host (str): The hostname of the database server + port (int): The port number of the database server + username (str): The database username to authenticate as + region (str): The AWS region where the database is located + profile (str, optional): The AWS profile name to use for credentials + role_arn (str, optional): The ARN of an IAM role to assume (future) + + Returns: + str: The generated authentication token (valid for 15 minutes) + + Raises: + Exception: If botocore is not available or token generation fails + """ + if not BOTOCORE_AVAILABLE: + raise Exception( + _("AWS IAM authentication requires the 'botocore' package. " + "Please install it with: pip install botocore") + ) + + try: + # Create a botocore session with the specified profile (if any) + session = botocore.session.Session(profile=profile) + + # TODO: Future enhancement - support role assumption via STS + # if role_arn: + # sts_client = session.create_client('sts', region_name=region) + # assumed_role = sts_client.assume_role( + # RoleArn=role_arn, + # RoleSessionName='pgAdmin4-IAM-Session' + # ) + # # Use temporary credentials from assumed role + # ... + + # Create RDS client + rds_client = session.create_client('rds', region_name=region) + + # Generate the authentication token + token = rds_client.generate_db_auth_token( + DBHostname=host, + Port=port, + DBUsername=username, + Region=region + ) + + logging.info( + f"Successfully generated IAM auth token for {username}@{host}" + ) + + return token + + except NoCredentialsError: + raise Exception( + _("AWS credentials not found. Please configure your AWS " + "credentials using 'aws configure' or ensure your " + "environment has valid credentials.") + ) + except PartialCredentialsError: + raise Exception( + _("Incomplete AWS credentials found. Please check your " + "AWS configuration.") + ) + except (BotoCoreError, ClientError) as e: + error_msg = str(e) + logging.error(f"AWS IAM token generation failed: {error_msg}") + raise Exception( + _("AWS IAM token generation failed: {error}").format( + error=error_msg + ) + ) + except Exception as e: + error_msg = str(e) + logging.error(f"Unexpected error during IAM auth: {error_msg}") + raise Exception( + _("IAM authentication error: {error}").format(error=error_msg) + ) diff --git a/web/pgadmin/utils/driver/psycopg3/connection.py b/web/pgadmin/utils/driver/psycopg3/connection.py index 55b77ccaac5..6335a8ce899 100644 --- a/web/pgadmin/utils/driver/psycopg3/connection.py +++ b/web/pgadmin/utils/driver/psycopg3/connection.py @@ -342,30 +342,58 @@ def connect(self, **kwargs): if ssl_key_file_permission > 600: os.chmod(ssl_key, 0o600) - with ConnectionLocker(manager.kerberos_conn): - # Create the connection string - connection_string = manager.create_connection_string( - database, user, password) - - if self.async_: - autocommit = True - if 'auto_commit' in kwargs: - autocommit = kwargs['auto_commit'] - - async def connectdbserver(): - return await psycopg.AsyncConnection.connect( - connection_string, - cursor_factory=AsyncDictCursor, - autocommit=autocommit, - prepare_threshold=manager.prepare_threshold + # Retry logic for IAM token refresh + max_retries = 2 if (hasattr(manager, 'use_iam_auth') and + manager.use_iam_auth) else 1 + last_error = None + + for attempt in range(max_retries): + try: + with ConnectionLocker(manager.kerberos_conn): + # Create/regenerate connection string (gets fresh IAM token) + connection_string = manager.create_connection_string( + database, user, password) + + if self.async_: + autocommit = True + if 'auto_commit' in kwargs: + autocommit = kwargs['auto_commit'] + + async def connectdbserver(): + return await psycopg.AsyncConnection.connect( + connection_string, + cursor_factory=AsyncDictCursor, + autocommit=autocommit, + prepare_threshold=manager.prepare_threshold + ) + pg_conn = asyncio.run(connectdbserver()) + pg_conn.server_cursor_factory = AsyncDictServerCursor + else: + pg_conn = psycopg.Connection.connect( + connection_string, + cursor_factory=DictCursor, + prepare_threshold=manager.prepare_threshold) + + # Connection successful, break out of retry loop + break + + except psycopg.Error as e: + last_error = e + # Check if this is an authentication failure (SQLSTATE 28P01) + # and we have retries remaining + if (hasattr(e, 'pgcode') and e.pgcode == '28P01' and + attempt < max_retries - 1 and + hasattr(manager, 'use_iam_auth') and + manager.use_iam_auth): + # Retry with fresh token + current_app.logger.info( + f"IAM auth failed for connection ({conn_id}), " + f"retrying with fresh token (attempt {attempt + 2}/{max_retries})" ) - pg_conn = asyncio.run(connectdbserver()) - pg_conn.server_cursor_factory = AsyncDictServerCursor - else: - pg_conn = psycopg.Connection.connect( - connection_string, - cursor_factory=DictCursor, - prepare_threshold=manager.prepare_threshold) + continue + else: + # No retry, re-raise + raise except psycopg.Error as e: manager.stop_ssh_tunnel() diff --git a/web/pgadmin/utils/driver/psycopg3/server_manager.py b/web/pgadmin/utils/driver/psycopg3/server_manager.py index 76cee8b8446..5a2b5c8518f 100644 --- a/web/pgadmin/utils/driver/psycopg3/server_manager.py +++ b/web/pgadmin/utils/driver/psycopg3/server_manager.py @@ -114,6 +114,11 @@ def update(self, server): self.tunnel_keep_alive = 0 self.kerberos_conn = server.kerberos_conn + # AWS IAM Authentication + self.use_iam_auth = server.use_iam_auth if hasattr(server, 'use_iam_auth') else False + self.aws_profile = server.aws_profile if hasattr(server, 'aws_profile') else None + self.aws_region = server.aws_region if hasattr(server, 'aws_region') else None + self.aws_role_arn = server.aws_role_arn if hasattr(server, 'aws_role_arn') else None self.gss_authenticated = False self.gss_encrypted = False self.connection_params = server.connection_params @@ -666,6 +671,26 @@ def create_connection_string(self, database, user, password=None): if self.use_ssh_tunnel: dsn_args['hostaddr'] = self.local_bind_host + # AWS IAM Authentication: Generate token and force SSL + if hasattr(self, 'use_iam_auth') and self.use_iam_auth: + from pgadmin.utils.aws_iam import generate_rds_auth_token + try: + password = generate_rds_auth_token( + host=self.host, + port=self.port, + username=user, + region=self.aws_region, + profile=self.aws_profile if self.aws_profile else None, + role_arn=self.aws_role_arn if self.aws_role_arn else None + ) + # IAM authentication requires SSL + if not self.connection_params: + self.connection_params = {} + if 'sslmode' not in self.connection_params: + self.connection_params['sslmode'] = 'require' + except Exception as e: + raise Exception(f"IAM authentication failed: {str(e)}") + # Make a copy to display the connection string on GUI. display_dsn_args = dsn_args.copy() # Password should not be visible into the connection string, so