AWS Database Blog
Cross-database access using module signing on Amazon RDS for SQL Server
If you need cross-database access on Amazon Relational Database Service (Amazon RDS) for SQL Server but can’t enable the TRUSTWORTHY database property, module signing with certificates is the secure, RDS-compatible alternative. In this post, we show you how to use certificate-based module signing to grant cross-database permissions to specific stored procedures. This approach works without TRUSTWORTHY and provides a stronger security posture, scoped to individual procedures rather than to an entire trusted database.
Many database administrators and application developers migrating Microsoft SQL Server workloads to Amazon RDS rely on TRUSTWORTHY for patterns such as stored procedures that query tables in other databases, Service Broker activation across databases, or impersonation with EXECUTE AS. On-premises, enabling TRUSTWORTHY is a one-line ALTER DATABASE statement. On Amazon RDS for SQL Server, however, you can’t set this property because it requires the sysadmin server role, which isn’t granted to the RDS master user. This post is written primarily for database administrators planning migrations and developers maintaining cross-database logic after migration. Whether you’re evaluating workarounds before a move or remediating issues after one, the approach applies equally.
Note: If you’re using Amazon RDS Custom for SQL Server, you have OS-level access and can use the standard file-based certificate transfer (BACKUP CERTIFICATE ... TO FILE / CREATE CERTIFICATE ... FROM FILE) directly. The module signing technique in this post is specifically for fully managed Amazon RDS for SQL Server, where filesystem access is not available.
Solution overview
The TRUSTWORTHY database property tells the SQL Server instance to trust the database and its contents. When enabled, modules (stored procedures, functions, triggers) that use impersonation or cross-database references can extend their security context beyond the local database boundary. Although it’s convenient, Microsoft recommends against using TRUSTWORTHY because it opens a broad privilege escalation path. Any db_owner in a trusted database owned by a sysadmin login can escalate to server-level sysadmin privileges.
Module signing is the recommended alternative. Instead of trusting an entire database, you sign a specific stored procedure with a certificate. SQL Server then grants the permissions associated with the certificate-mapped user in the target database to that procedure alone. This follows the principle of least privilege: the permission grant is scoped to a single module, not the entire database.
The following steps describe the module signing flow:
- A certificate is created in DatabaseB (the target database) and mapped to a user with
SELECTpermission on the target table. - The same certificate is transferred to DatabaseA (the calling database) by using dynamic SQL with
sp_executesql. This is the RDS-compatible approach that avoids filesystem access. - The stored procedure in DatabaseA is signed with the certificate.
- When AppUser runs the signed procedure, SQL Server recognizes the certificate signature and temporarily adds the certificate-mapped user’s permissions from DatabaseB to the runtime context.
Disclaimer
- This code is for demonstration only and isn’t intended for production.
- We recommend that you add an explicit
EXPIRY_DATEin theCREATE CERTIFICATEstatement and set up rotation before expiry. - We recommend that you store database master key (DMK) passwords in AWS Secrets Manager and document the recovery procedure as needed.
- Because this is only a demo, we don’t recommend hardcoding passwords.
- We added extra characters next to the passwords to prevent copy and paste.
Prerequisites
To follow along with this walkthrough, you need:
- An AWS account.
- An Amazon RDS for SQL Server instance. Any edition works (Express, Web, Standard, or Enterprise). This post uses SQL Server 2022 Express Edition on a
db.t3.xlargeinstance. - A SQL client such as SQL Server Management Studio (SSMS) or
sqlcmd(included inmssql-tools18) connected to the RDS instance as the master user.
You can provision this entire environment automatically with the accompanying AWS CloudFormation template (rds-crossdb-lab-secretsmanager.yaml). The template creates the Amazon RDS for SQL Server instance and an Amazon EC2 workload host running Windows Server 2022. It auto-generates every password in AWS Secrets Manager: the RDS master user, AppUser, the two database master keys, and the certificate-transfer password. Each password is encrypted with a customer managed AWS Key Management Service (AWS KMS) key, so no credential is hardcoded anywhere. This replaces the earlier approach of passing the RDS master password as a stack parameter.
You reach the Windows workload host through AWS Systems Manager Fleet Manager Remote Desktop (no inbound RDP port is opened on the security group), then run the SQL steps from SSMS installed on that host. Retrieve each password at runtime with Get-SECSecretValue from AWS Tools for PowerShell rather than typing it. When you connect SSMS to RDS, set Encryption to Mandatory and validate the server certificate by importing the Amazon RDS CA bundle into the Windows Trusted Root Certification Authorities store. Do not enable Trust Server Certificate.
Understanding the limitation
To confirm the limitation exists, connect to your RDS instance as the master user (we use admin in this post) and run the following setup.
Create the test databases and objects
Create two databases, a target table with sample data, and a limited login:
Confirm TRUSTWORTHY is OFF
Expected output:
| name | is_trustworthy_on |
| DatabaseA | 0 |
| DatabaseB | 0 |
Demonstrate the cross-database access failure
Connect as AppUser and run the procedure:
This fails with Error 916:
AppUser has EXECUTE permission on the procedure in DatabaseA, but the procedure references DatabaseB, which AppUser cannot access. Without TRUSTWORTHY, the security context cannot cross the database boundary.
Demonstrate the TRUSTWORTHY limitation on RDS
Back as the admin user, attempt to enable TRUSTWORTHY:
This fails with Error 15247:
The RDS master user doesn’t have the sysadmin server role, which is required to set the TRUSTWORTHY property. This restriction applies across all RDS for SQL Server editions and engine versions. It stems from the managed-service design, in which AWS retains control of server-level operations to maintain the service boundary.
Implementing the workaround with module signing
Module signing uses certificates to grant cross-database permissions to specific signed modules. The process involves four steps:
- Create database master keys in both databases.
- Create a certificate in DatabaseB and map it to a user with the required permissions.
- Transfer the certificate to DatabaseA and sign the stored procedure.
- Verify the result.
Step 1: Create database master keys
Database master keys are required for certificate operations:
Step 2: Create a certificate in DatabaseB and map it to a user
Create a certificate in the target database, then create a user from that certificate and grant it the required permissions:
CertUser is a certificate-mapped user, not a login. Nobody can connect to SQL Server as CertUser. Its sole purpose is to hold the SELECT permission that the signed procedure inherits at runtime.
Step 3: Transfer the certificate to DatabaseA and sign the procedure
This is the step where Amazon RDS differs from a self-managed SQL Server instance.
Why the file-based approach fails on RDS:
On-premises, the typical path is BACKUP CERTIFICATE ... TO FILE followed by CREATE CERTIFICATE ... FROM FILE. On RDS, the master user has no write access to the underlying Windows filesystem, so this approach fails with a permission error.
Why manual hex copy-paste is fragile:
The alternative documented in many guides is to extract the certificate as hex with CERTENCODED() and paste it into CREATE CERTIFICATE ... FROM BINARY, but this is error-prone in practice. Terminal tools like sqlcmd truncate long output by default, and CREATE CERTIFICATE ... FROM BINARY only accepts hex literals (not variables), which makes it difficult to script.
The recommended approach — dynamic SQL with sp_executesql:
The most reliable RDS-compatible method keeps everything server-side. When you build the CREATE CERTIFICATE ... FROM BINARY statement as a dynamic SQL string inside SQL Server, the full binary never passes through the terminal, which removes truncation and copy-paste errors:
How this works:
CERTENCODED()extracts the certificate’s public portion asVARBINARY(MAX).CERTPRIVATEKEY()extracts the private key, encrypted with the transfer password<YOUR-TRANSFER-PASSWORD>.CONVERT(..., 1)renders eachVARBINARYas a0x...hex string inside the dynamic SQL.sp_executesqlruns the fully formedCREATE CERTIFICATE ... FROM BINARYstatement against DatabaseA. By the time it runs, the hex is already a literal in the string, which satisfies the parser requirement.
Important: CERT_ID() resolves in the current database context. You must run this while connected to DatabaseB (where the certificate was created). Running it from DatabaseA returns NULL.
Now sign the stored procedure with the imported certificate:
Step 4: Verify the result
Confirm the signature is attached:
Output:
| signed_module | certificate_name | signature_type |
| GetSecretData | CrossDBCert | SIGNATURE BY CERTIFICATE |
Now test cross-database access as AppUser:
Output:
| Id | CustomerName | CreditScore |
| 1 | Acme Corp | 780 |
| 2 | Globex Inc | 720 |
AppUser can now read data from DatabaseB through the signed procedure. This works without TRUSTWORTHY, without sysadmin, and without granting AppUser any direct access to DatabaseB.
Verifying least privilege
To confirm that module signing is targeted and does not grant blanket cross-database access, create an identical but unsigned procedure:
As AppUser:
This fails with the same Error 916:
Only the signed procedure receives cross-database access. The unsigned procedure, which has identical code and permissions, is denied. This confirms that module signing follows the principle of least privilege.
Automating the transfer in continuous integration and continuous delivery (CI/CD) pipelines
The dynamic SQL approach shown in Step 3 works directly from sqlcmd or any SQL client with no external dependencies. For application-level automation (Python, C#, Windows PowerShell), you can also extract the binary at the driver layer:
However, for most use cases, the T-SQL dynamic SQL approach from Step 3 is simpler. It requires no external tools and works from any SQL client.
For validated TLS in the preceding automation example, import the Amazon RDS CA bundle on the client host and use Encrypt=yes;TrustServerCertificate=no instead of TrustServerCertificate=yes, and source the connection password from AWS Secrets Manager instead of embedding it in the connection string.
Pre-migration checklist
If you’re migrating SQL Server workloads to Amazon RDS, audit your databases for TRUSTWORTHY dependencies before migration:
Find databases with TRUSTWORTHY enabled:
Find modules using EXECUTE AS (potential cross-database impersonation):
Find cross-database references in stored procedures:
For each dependency found, implement module signing as described in this post before disabling TRUSTWORTHY on the source or migrating to Amazon RDS.
Key considerations
When you implement module signing in production, keep the following operational aspects in mind.
Certificates have an expiration date, so plan for periodic certificate rotation. The process involves creating a new certificate in the target database, mapping it to a user with the same grants, transferring it to the calling database, and re-signing the affected modules. During the transition, signed modules can carry multiple signatures simultaneously, so you can add the new signature before removing the old one. This avoids any downtime window.
Be aware of signature invalidation when modifying signed modules. If you alter a signed stored procedure (using ALTER PROCEDURE), SQL Server automatically removes the signature. You must re-sign the procedure after any modification. This is a security feature that prevents silent permission retention across code changes. It ensures that any change to the module’s logic requires an explicit re-authorization step.
If a procedure accesses tables in multiple target databases, you need a certificate-mapped user with the appropriate grants in each target database. You can use the same certificate across all of them, transferred through the same dynamic SQL approach shown in Step 3. Each target database independently validates the certificate thumbprint, so a single certificate can authorize access to as many databases as needed.
The DECRYPTION BY PASSWORD value used during certificate transfer is a transfer-time secret for the private key. Treat it like any other credential: keep it out of source control, rotate it periodically, and use AWS Secrets Manager to store and retrieve it in automated workflows.
Finally, the dynamic SQL approach requires no filesystem access. The certificate binary is extracted, transferred, and imported entirely in memory within SQL Server. No files are written to disk at any point, which aligns with compliance requirements that prohibit storing key material on shared storage. We recommend that you consult your compliance team. Under the AWS shared responsibility model, assessing compliance is your responsibility.
Cleaning up
To avoid ongoing charges, delete the resources you created as part of this post:
- Delete your RDS instance.
- Delete your EC2 instance.
- Drop the certificates, users, logins, and keys created during the demo.
If you deployed the accompanying CloudFormation stack, the simplest cleanup is to delete the stack. This removes the RDS instance, the Windows EC2 workload host, the Secrets Manager secrets, the KMS key, and all networking in a single step.
Conclusion
In this post, we showed you how to achieve cross-database access on Amazon RDS for SQL Server using module signing with certificates. Module signing is a secure, least-privilege alternative to the TRUSTWORTHY database property, which cannot be enabled on RDS.
The key RDS-specific finding is that the standard file-based certificate transfer (BACKUP CERTIFICATE TO FILE) does not work on Amazon RDS. The recommended approach is to use dynamic SQL with sp_executesql to build and run the CREATE CERTIFICATE ... FROM BINARY statement entirely server-side. This approach avoids filesystem access, removes terminal truncation issues, and works from any SQL client without external tooling.
To get started, try this approach in your own RDS environment using the steps in this post. For more information, see the following resources:
- Amazon RDS for SQL Server documentation.
- Microsoft: CERTENCODED function.