From 7fa51dba8b8d9628bb1238db4c48da361483ee58 Mon Sep 17 00:00:00 2001 From: slach Date: Tue, 14 Jul 2026 18:56:06 +0400 Subject: [PATCH] add MASKING POLICY support to RBAC restore, fix https://github.com/Altinity/clickhouse-backup/issues/1332 MASKING POLICY (ClickHouse 25.12+, Cloud-only creation) is a regular access entity stored as .sql in access_control_path and in ZooKeeper (prefix char M), so backup side is already type-agnostic. Restore previously failed on such backups because detectRBACObject could not recognize ATTACH MASKING POLICY. - detect ATTACH MASKING POLICY, keep backticks in name (format is `name` ON db.table, same as ROW POLICY) - resolve conflicts via system.masking_policies only on 26.8+ where the table exists, otherwise check local and keeper user directories - drop existing objects in keeper via M prefix --- pkg/backup/restore.go | 24 +++++++++++++++--------- pkg/backup/restore_test.go | 12 ++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/pkg/backup/restore.go b/pkg/backup/restore.go index e9c53013..07edc590 100644 --- a/pkg/backup/restore.go +++ b/pkg/backup/restore.go @@ -746,20 +746,24 @@ func (b *Backuper) isRBACExists(ctx context.Context, kind string, name string, a "SETTINGS PROFILE": "settings_profiles", "QUOTA": "quotas", "USER": "users", + "MASKING POLICY": "masking_policies", } systemTable, systemTableExists := rbacSystemTableNames[kind] if !systemTableExists { log.Error().Msgf("unsupported RBAC object kind: %s", kind) return false, "", nil } - isRBACExistsSQL := fmt.Sprintf("SELECT toString(id) AS id, name FROM `system`.`%s` WHERE name=? LIMIT 1", systemTable) - existsRBACRow := make([]clickhouse.RBACObject, 0) - if err := b.ch.SelectContext(ctx, &existsRBACRow, isRBACExistsSQL, name); err != nil { - log.Warn().Msgf("RBAC object resolve failed, check SQL GRANTS or settings for user which you use to connect to clickhouse-server, kind: %s, name: %s, error: %v", kind, name, err) - return false, "", nil - } - if len(existsRBACRow) != 0 { - return true, "sql", []string{existsRBACRow[0].Id} + // system.masking_policies exists only in 26.8+, on older versions check only local and keeper user directories + if systemTable != "masking_policies" || version >= 26008000 { + isRBACExistsSQL := fmt.Sprintf("SELECT toString(id) AS id, name FROM `system`.`%s` WHERE name=? LIMIT 1", systemTable) + existsRBACRow := make([]clickhouse.RBACObject, 0) + if err := b.ch.SelectContext(ctx, &existsRBACRow, isRBACExistsSQL, name); err != nil { + log.Warn().Msgf("RBAC object resolve failed, check SQL GRANTS or settings for user which you use to connect to clickhouse-server, kind: %s, name: %s, error: %v", kind, name, err) + return false, "", nil + } + if len(existsRBACRow) != 0 { + return true, "sql", []string{existsRBACRow[0].Id} + } } } @@ -859,6 +863,7 @@ func (b *Backuper) dropExistsRBAC(ctx context.Context, kind string, name string, "SETTINGS PROFILE": "S", "QUOTA": "Q", "USER": "U", + "MASKING POLICY": "M", } keeperRBACTypePrefix, isKeeperRBACTypePrefixExists := keeperPrefixesRBAC[kind] if !isKeeperRBACTypePrefixExists { @@ -904,6 +909,7 @@ func (b *Backuper) detectRBACObject(sql string) (string, string, error) { "ATTACH SETTINGS PROFILE": "SETTINGS PROFILE", "ATTACH QUOTA": "QUOTA", "ATTACH USER": "USER", + "ATTACH MASKING POLICY": "MASKING POLICY", } // Iterate over the prefixes to find a match. @@ -932,7 +938,7 @@ func (b *Backuper) detectRBACObject(sql string) (string, string, error) { } else { name = names[0] } - if kind != "ROW POLICY" { + if kind != "ROW POLICY" && kind != "MASKING POLICY" { name = strings.Trim(name, "`") } name = strings.TrimSpace(name) diff --git a/pkg/backup/restore_test.go b/pkg/backup/restore_test.go index ca7a9277..96aad02f 100644 --- a/pkg/backup/restore_test.go +++ b/pkg/backup/restore_test.go @@ -61,6 +61,18 @@ func TestDetectRBACObject(t *testing.T) { expectedName: "test_rbac", expectedErr: nil, }, + { + inputSQL: "ATTACH MASKING POLICY `test_rbac` ON default.test_rbac UPDATE name = 'xxx' TO ID('e1469fb8-e014-c22b-4e5c-406134320f91');\n", + expectedKind: "MASKING POLICY", + expectedName: "`test_rbac` ON default.test_rbac", + expectedErr: nil, + }, + { + inputSQL: "ATTACH MASKING POLICY mask_email ON default.users UPDATE email = concat(substring(email, 1, 2), '***') WHERE 1 TO ALL PRIORITY 1;\n", + expectedKind: "MASKING POLICY", + expectedName: "mask_email ON default.users", + expectedErr: nil, + }, { inputSQL: "INVALID SQL", expectedKind: "",