@@ -61,6 +61,7 @@ namespace DB
6161
6262namespace Setting
6363{
64+ extern const SettingsUInt64 readonly;
6465 extern const SettingsBool s3_disable_checksum;
6566}
6667
@@ -71,6 +72,7 @@ namespace ServerSetting
7172
7273namespace ErrorCodes
7374{
75+ extern const int ACCESS_DENIED;
7476 extern const int BAD_ARGUMENTS;
7577 extern const int LOGICAL_ERROR;
7678 extern const int QUERY_WAS_CANCELLED;
@@ -390,6 +392,12 @@ struct BackupsWorker::BackupStarter
390392 backup_info = BackupInfo::fromAST (*backup_query->backup_name );
391393 backup_name_for_logging = backup_info.toStringForLogging ();
392394 is_internal_backup = backup_settings.internal ;
395+
396+ // / The "internal" option can only be used by a query that was initiated by another query (e.g., ON CLUSTER query).
397+ // / It should not be allowed for an initial query explicitly specified by a user.
398+ if (is_internal_backup && (query_context->getClientInfo ().query_kind == ClientInfo::QueryKind::INITIAL_QUERY))
399+ throw Exception (ErrorCodes::ACCESS_DENIED, " Setting 'internal' cannot be set explicitly" );
400+
393401 on_cluster = !backup_query->cluster .empty () || is_internal_backup;
394402
395403 if (!backup_settings.backup_uuid )
@@ -462,14 +470,25 @@ struct BackupsWorker::BackupStarter
462470 cluster = backup_context->getCluster (backup_query->cluster );
463471 backup_settings.cluster_host_ids = cluster->getHostIDs ();
464472 }
473+
474+ // / Check access rights before opening the backup destination (e.g., S3).
475+ // / This ensures we fail fast with a proper ACCESS_DENIED error instead of trying to connect to external storage first.
476+ // / For ON CLUSTER queries, access rights are checked in executeDDLQueryOnCluster() before distributing the query.
477+ if (!on_cluster)
478+ {
479+ backup_query->setCurrentDatabase (backup_context->getCurrentDatabase ());
480+ auto required_access = BackupUtils::getRequiredAccessToBackup (backup_query->elements );
481+ query_context->checkAccess (required_access);
482+ }
483+
465484 chassert (backup_settings.data_file_name_prefix_length );
466485 backup_coordination = backups_worker.makeBackupCoordination (on_cluster, backup_settings, backup_context);
467486 backup_coordination->startup ();
468487
469488 chassert (!backup);
470489 backup = backups_worker.openBackupForWriting (backup_info, backup_settings, backup_coordination, backup_context);
471490
472- backups_worker.doBackup (backup, backup_query, backup_id, backup_settings, backup_coordination, backup_context, query_context,
491+ backups_worker.doBackup (backup, backup_query, backup_id, backup_settings, backup_coordination, backup_context,
473492 on_cluster, cluster);
474493
475494 if (!is_internal_backup)
@@ -621,7 +640,6 @@ void BackupsWorker::doBackup(
621640 const BackupSettings & backup_settings,
622641 std::shared_ptr<IBackupCoordination> backup_coordination,
623642 ContextMutablePtr context,
624- const ContextPtr & query_context,
625643 bool on_cluster,
626644 const ClusterPtr & cluster)
627645{
@@ -636,17 +654,13 @@ void BackupsWorker::doBackup(
636654
637655 bool is_internal_backup = backup_settings.internal ;
638656
639- // / Checks access rights if this is not ON CLUSTER query.
640- // / (If this is ON CLUSTER query executeDDLQueryOnCluster() will check access rights later.)
641- auto required_access = BackupUtils::getRequiredAccessToBackup (backup_query->elements );
642- if (!on_cluster)
643- query_context->checkAccess (required_access);
644-
645657 maybeSleepForTesting ();
646658
647659 // / Write the backup.
648660 if (on_cluster && !is_internal_backup)
649661 {
662+ auto required_access = BackupUtils::getRequiredAccessToBackup (backup_query->elements );
663+
650664 // / Send the BACKUP query to other hosts.
651665 backup_settings.copySettingsToQuery (*backup_query);
652666 sendQueryToOtherHosts (*backup_query, cluster, backup_settings.shard_num , backup_settings.replica_num ,
@@ -847,6 +861,19 @@ struct BackupsWorker::RestoreStarter
847861 backup_info = BackupInfo::fromAST (*restore_query->backup_name );
848862 backup_name_for_logging = backup_info.toStringForLogging ();
849863 is_internal_restore = restore_settings.internal ;
864+
865+ // / The "internal" option can only be used by a query that was initiated by another query (e.g., ON CLUSTER query).
866+ // / It should not be allowed for an initial query explicitly specified by a user.
867+ if (is_internal_restore && (query_context->getClientInfo ().query_kind == ClientInfo::QueryKind::INITIAL_QUERY))
868+ throw Exception (ErrorCodes::ACCESS_DENIED, " Setting 'internal' cannot be set explicitly" );
869+
870+ // / RESTORE is a write operation, it should be forbidden in strict readonly mode (readonly=1).
871+ // / Note: readonly=2 allows changing settings but still restricts writes - however it's set automatically
872+ // / by the HTTP interface for GET requests (to protect against accidental writes), so we only block readonly=1
873+ // / which is explicitly set by the user to enforce read-only mode.
874+ if (query_context->getSettingsRef ()[Setting::readonly] == 1 )
875+ throw Exception (ErrorCodes::ACCESS_DENIED, " Cannot execute RESTORE in readonly mode" );
876+
850877 on_cluster = !restore_query->cluster .empty () || is_internal_restore;
851878
852879 if (!restore_settings.restore_uuid )
0 commit comments