Skip to content

Commit b28591d

Browse files
szachovyCopilot
andcommitted
Fix race condition between secret upload and chmod
Add mode parameter to upload_file() that sets permissions atomically via SFTP before writing content. All secret files (passwords, keys, .mylogin.cnf) now use mode=0o600. Remove separate change_permissions_to_root() calls that left a readable window. Closes #90 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0ab8ce7 commit b28591d

3 files changed

Lines changed: 26 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
* CI workflow to build and push service images to GitHub Container Registry on master merge. (#97)
1717
* Pull-first with local build fallback for container images during deployment. (#97)
1818

19+
### Fixed
20+
21+
* Atomic file permissions via SFTP — secrets no longer readable between upload and chmod. (#90)
22+
1923
### Changed
2024

2125
* Completed [ARCHITECTURE.md](./docs/ARCHITECTURE.md) (#93)

src/initialize.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,19 +167,22 @@ def start_mysql_servers(self) -> None:
167167
)
168168
node.upload_file(
169169
content=self.mysql_root_password,
170-
remote_file_path="/opt/superset-cluster/mysql-server/mysql_root_password"
170+
remote_file_path="/opt/superset-cluster/mysql-server/mysql_root_password",
171+
mode=0o600
171172
)
172173
node.upload_file(
173174
content=self.cert_manager.deserialization(self.ca_key),
174-
remote_file_path="/opt/superset-cluster/mysql-server/superset_cluster_ca_key.pem")
175+
remote_file_path="/opt/superset-cluster/mysql-server/superset_cluster_ca_key.pem",
176+
mode=0o600)
175177
node.upload_file(
176178
content="".join(self.cert_manager.deserialization(node.certificate) for node in self.mysql_nodes)
177179
+ self.cert_manager.deserialization(self.ca_certificate),
178180
remote_file_path="/opt/superset-cluster/mysql-server/superset_cluster_ca_certificate.pem"
179181
)
180182
node.upload_file(
181183
content=self.cert_manager.deserialization(node.key),
182-
remote_file_path="/opt/superset-cluster/mysql-server/mysql_server_key.pem"
184+
remote_file_path="/opt/superset-cluster/mysql-server/mysql_server_key.pem",
185+
mode=0o600
183186
)
184187
node.upload_file(
185188
content=self.cert_manager.deserialization(node.certificate),
@@ -198,18 +201,18 @@ def start_mysql_mgmt(self, node: remote.RemoteConnection, state: str, priority:
198201
)
199202
node.upload_file(
200203
content=self.mysql_superset_password,
201-
remote_file_path="/opt/superset-cluster/mysql-mgmt/mysql_superset_password"
204+
remote_file_path="/opt/superset-cluster/mysql-mgmt/mysql_superset_password",
205+
mode=0o600
202206
)
203207
node.upload_file(
204208
content=self.get_mylogin_cnf(self.mysql_nodes[0]),
205-
remote_file_path="/opt/superset-cluster/mysql-mgmt/.mylogin.cnf"
206-
)
207-
node.change_permissions_to_root(
208-
"/opt/superset-cluster/mysql-mgmt/.mylogin.cnf"
209+
remote_file_path="/opt/superset-cluster/mysql-mgmt/.mylogin.cnf",
210+
mode=0o600
209211
)
210212
node.upload_file(
211213
content=self.cert_manager.deserialization(self.ca_key),
212-
remote_file_path="/opt/superset-cluster/mysql-mgmt/superset_cluster_ca_key.pem"
214+
remote_file_path="/opt/superset-cluster/mysql-mgmt/superset_cluster_ca_key.pem",
215+
mode=0o600
213216
)
214217
node.upload_file(
215218
content="".join(self.cert_manager.deserialization(node.certificate) for node in self.mysql_nodes)
@@ -218,7 +221,8 @@ def start_mysql_mgmt(self, node: remote.RemoteConnection, state: str, priority:
218221
)
219222
node.upload_file(
220223
content=self.cert_manager.deserialization(node.key),
221-
remote_file_path="/opt/superset-cluster/mysql-mgmt/mysql_router_key.pem"
224+
remote_file_path="/opt/superset-cluster/mysql-mgmt/mysql_router_key.pem",
225+
mode=0o600
222226
)
223227
node.upload_file(
224228
content=self.cert_manager.deserialization(node.certificate),
@@ -254,15 +258,17 @@ def start_superset(self) -> None:
254258
)
255259
node.upload_file(
256260
content=self.cert_manager.deserialization(self.ca_key),
257-
remote_file_path="/opt/superset-cluster/superset/superset_cluster_ca_key.pem"
261+
remote_file_path="/opt/superset-cluster/superset/superset_cluster_ca_key.pem",
262+
mode=0o600
258263
)
259264
node.upload_file(
260265
content=self.cert_manager.deserialization(self.ca_certificate),
261266
remote_file_path="/opt/superset-cluster/superset/superset_cluster_ca_certificate.pem"
262267
)
263268
node.upload_file(
264269
content=self.cert_manager.deserialization(node.superset_key),
265-
remote_file_path="/opt/superset-cluster/superset/superset_cluster_key.pem"
270+
remote_file_path="/opt/superset-cluster/superset/superset_cluster_key.pem",
271+
mode=0o600
266272
)
267273
node.upload_file(
268274
content=self.cert_manager.deserialization(node.superset_certificate),

src/remote.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,12 @@ def upload_directory(self, local_directory_path: str, remote_directory_path: str
154154
def create_directory(self, remote_directory_path: str) -> None:
155155
self.sftp_client.mkdir(remote_directory_path)
156156

157-
def upload_file(self, content: str | bytes, remote_file_path: str) -> None:
157+
def upload_file(self, content: str | bytes, remote_file_path: str, mode: int = 0o644) -> None:
158158
if isinstance(content, str):
159159
content = content.encode('utf-8')
160-
self.sftp_client.putfo(io.BytesIO(content), remote_file_path)
160+
with self.sftp_client.open(remote_file_path, 'wb') as remote_file:
161+
remote_file.set_pmode(mode)
162+
remote_file.write(content)
161163

162164
def change_permissions_to_root(self, filepath: str) -> None:
163165
self.ssh_client.exec_command(f"chmod 600 {filepath}")

0 commit comments

Comments
 (0)