Skip to content

Commit 5d7c333

Browse files
committed
fix(replication): Skip GRANT statements with GRANTED BY rdsadmin
AWS RDS dumps include GRANT statements with "GRANTED BY rdsadmin" clauses that fail on restore because only rdsadmin can grant privileges as rdsadmin. This fix filters out GRANT statements that reference restricted grantors: - rdsadmin - rds_superuser - rdsrepladmin - rds_replication Fixes "permission denied to grant privileges as role rdsadmin" errors.
1 parent 2a59a6e commit 5d7c333

4 files changed

Lines changed: 36 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [5.3.6] - 2025-12-02
6+
7+
### Fixed
8+
9+
- **Skip GRANT statements with restricted GRANTED BY clauses**: `GRANT` statements that include `GRANTED BY rdsadmin`, `GRANTED BY rds_superuser`, or similar RDS admin roles are now automatically commented out during globals restore, preventing "permission denied to grant privileges as role" errors on AWS RDS targets.
10+
511
## [5.3.5] - 2025-12-02
612

713
### Fixed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "database-replicator"
3-
version = "5.3.5"
3+
version = "5.3.6"
44
edition = "2021"
55
license = "Apache-2.0"
66
description = "Universal database-to-PostgreSQL replication CLI. Supports PostgreSQL, SQLite, MongoDB, and MySQL."

src/migration/dump.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ pub fn remove_restricted_guc_settings(path: &str) -> Result<()> {
171171
/// Comments out `GRANT` statements for roles that are restricted on managed services.
172172
///
173173
/// AWS RDS and other managed services may prevent granting certain default roles
174-
/// like `pg_checkpoint`. This function comments out those statements to allow
175-
/// the globals restore to proceed without permission errors.
174+
/// like `pg_checkpoint`. This function also filters out GRANT statements that use
175+
/// `GRANTED BY` clauses referencing RDS admin roles (e.g., `rdsadmin`).
176176
pub fn remove_restricted_role_grants(path: &str) -> Result<()> {
177177
// Roles that cannot be granted on managed PostgreSQL services (AWS RDS, etc.)
178178
const RESTRICTED_ROLES: &[&str] = &[
@@ -192,6 +192,14 @@ pub fn remove_restricted_role_grants(path: &str) -> Result<()> {
192192
"pg_use_reserved_connections",
193193
];
194194

195+
// Roles that cannot be used as grantors in GRANTED BY clauses
196+
const RESTRICTED_GRANTORS: &[&str] = &[
197+
"rdsadmin",
198+
"rds_superuser",
199+
"rdsrepladmin",
200+
"rds_replication",
201+
];
202+
195203
let content = fs::read_to_string(path)
196204
.with_context(|| format!("Failed to read globals dump at {}", path))?;
197205

@@ -201,7 +209,8 @@ pub fn remove_restricted_role_grants(path: &str) -> Result<()> {
201209
for line in content.lines() {
202210
let lower_trimmed = line.trim().to_ascii_lowercase();
203211
if lower_trimmed.starts_with("grant ") {
204-
let is_restricted = RESTRICTED_ROLES.iter().any(|role| {
212+
// Check if granting a restricted role
213+
let is_restricted_role = RESTRICTED_ROLES.iter().any(|role| {
205214
// Get the role being granted (2nd word), stripping any quotes
206215
// e.g. "grant pg_checkpoint to some_user" or "grant \"pg_checkpoint\" to some_user"
207216
lower_trimmed
@@ -211,7 +220,14 @@ pub fn remove_restricted_role_grants(path: &str) -> Result<()> {
211220
.unwrap_or(false)
212221
});
213222

214-
if is_restricted {
223+
// Check if using a restricted grantor in GRANTED BY clause
224+
let has_restricted_grantor = RESTRICTED_GRANTORS.iter().any(|grantor| {
225+
// Look for "granted by rdsadmin" or "granted by \"rdsadmin\""
226+
lower_trimmed.contains(&format!("granted by {}", grantor))
227+
|| lower_trimmed.contains(&format!("granted by \"{}\"", grantor))
228+
});
229+
230+
if is_restricted_role || has_restricted_grantor {
215231
updated.push_str("-- ");
216232
updated.push_str(line);
217233
updated.push('\n');
@@ -888,6 +904,9 @@ GRANT "pg_read_all_stats" TO myuser;
888904
GRANT pg_monitor TO myuser;
889905
GRANT myrole TO myuser;
890906
GRANT SELECT ON TABLE users TO myuser;
907+
GRANT rds_superuser TO myuser GRANTED BY rdsadmin;
908+
GRANT ALL ON SCHEMA public TO myuser GRANTED BY "rdsadmin";
909+
GRANT SELECT ON TABLE orders TO myuser GRANTED BY postgres;
891910
"#;
892911
std::fs::write(&globals_file, content).unwrap();
893912

@@ -902,9 +921,14 @@ GRANT SELECT ON TABLE users TO myuser;
902921
assert!(result.contains("-- GRANT \"pg_read_all_stats\" TO myuser;"));
903922
assert!(result.contains("-- GRANT pg_monitor TO myuser;"));
904923

924+
// GRANTED BY rdsadmin clauses should be commented out
925+
assert!(result.contains("-- GRANT rds_superuser TO myuser GRANTED BY rdsadmin;"));
926+
assert!(result.contains("-- GRANT ALL ON SCHEMA public TO myuser GRANTED BY \"rdsadmin\";"));
927+
905928
// Non-restricted grants should remain
906929
assert!(result.contains("\nGRANT myrole TO myuser;\n"));
907930
assert!(result.contains("\nGRANT SELECT ON TABLE users TO myuser;\n"));
931+
assert!(result.contains("\nGRANT SELECT ON TABLE orders TO myuser GRANTED BY postgres;\n"));
908932

909933
// Other statements should remain unchanged
910934
assert!(result.contains("CREATE ROLE myuser;"));

0 commit comments

Comments
 (0)