-
Notifications
You must be signed in to change notification settings - Fork 161
feat(jans-pycloudlib): add low-level support for SSL persistence connection #12194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: iromli <isman.firmansyah@gmail.com>
…ostgres connection Signed-off-by: iromli <isman.firmansyah@gmail.com>
Signed-off-by: iromli <isman.firmansyah@gmail.com>
Signed-off-by: iromli <isman.firmansyah@gmail.com>
🎉 Snyk checks have passed. No issues have been found so far.✅ security/snyk check is complete. No issues have been found. (View Details) |
|
| logger.info(f"Detected non-empty {secret_name=}. The secret will be populated into {filepath!r}.") | ||
|
|
||
| with open(filepath, "w") as f: | ||
| f.write(contents) |
Check failure
Code scanning / CodeQL
Clear-text storage of sensitive information High
sensitive data (secret)
This expression stores
sensitive data (secret)
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
To remediate the clear-text storage of sensitive information, the solution is to encrypt secret content before writing it to disk and decrypt it only at the point of use. One standard way in Python is to leverage the cryptography library's Fernet symmetric encryption functionality. We can:
- Encrypt the contents using a key stored in a secure location (environment variable or wherever reasonable, with proper handling).
- Write the encrypted content to disk.
- At the time the file is needed, read and decrypt it in memory (if the downstream consumers allow), or (if the file must be left on disk for libraries that only read PEM files from the filesystem) consider using encrypted filesystems or a post-processing step to safely remove the files after use.
Given our code is directly writing the private key/cert/ca cert needed by libraries that expect standard filepaths, we need to:
- Encrypt the contents before writing, using a symmetric key stored in an environment variable (e.g.,
CN_SQL_SSL_SECRET_KEY). - When the files are written (and before using them), decrypt them temporarily in memory as needed.
To do this:
- Add a dependency on
cryptography. - In
SqlClient._bootstrap_ssl_assets, encrypt the contents before they're written; or, if these files must be in cleartext at runtime for libraries like OpenSSL, the safer approach is to write them only as late as possible and to ensure they're immediately deleted after use.
Because we are limited to only making changes to the code snippets shown, we can implement a wrapper: before f.write(contents), encrypt contents with a Fernet key read from an environment variable (or provided by some other secret mechanism). Downstream consumers would then need to be aware that the file is encrypted—however, this may break the expected PEM format for the client libraries.
Note: If the consuming libraries (e.g., SSL libraries, drivers) require plain PEM files, then app-level encryption at rest is not possible unless extra work is done (such as providing files via temporary decrypted files or using in-memory files where supported), which may be out of scope for such infrastructure code.
The most practical improvement in such cases is to:
- Write the files as securely as possible: use
os.chmodfor all files, not just private key, restrict the parent directory, and warn/document that secure mounts and Docker secrets or encrypted filesystems should be used. - Securely wipe the files after their use (if possible).
That said, if the project aims for maximum code-level remediation, the code fix would look like the following:
- Use the
cryptographylibrary, require an environment variable for the Fernet key. - Encrypt the secrets before writing to disk (base64-encode the ciphertext for a clean file format).
- Downstream libraries/processes that consume these files must decrypt them before use, so unless adapted, this breaks compatibility.
Because the secure storage in this context largely depends on the infrastructure setup (e.g., Kubernetes secrets, Docker volumes) and code cannot transparently encrypt/decrypt for external libraries, the most correct fix is to encrypt the content, but with a strong warning that downstream consumers must also adapt.
Edits to make:
- Add an import for
cryptography.fernet.Fernet. - Retrieve a Fernet key from the environment (e.g.,
CN_SQL_SSL_SECRET_KEY) to be used for encryption. - Encrypt
contentsbefore writing in_bootstrap_ssl_assets. - Optionally, add comments describing the limitation above.
-
Copy modified line R11 -
Copy modified line R281 -
Copy modified lines R296-R303 -
Copy modified lines R317-R319 -
Copy modified lines R321-R323
| @@ -8,6 +8,7 @@ | ||
| import re | ||
| import typing as _t | ||
| import warnings | ||
| from cryptography.fernet import Fernet | ||
| from collections import defaultdict | ||
| from collections.abc import Callable | ||
| from functools import cached_property | ||
| @@ -277,6 +278,7 @@ | ||
|
|
||
| def __init__(self, manager: Manager, *args: _t.Any, **kwargs: _t.Any) -> None: | ||
| self.manager = manager | ||
| self._fernet = None # will be initialized if needed | ||
|
|
||
| dialect = os.environ.get("CN_SQL_DB_DIALECT", "mysql") | ||
| if dialect in ("pgsql", "postgresql"): | ||
| @@ -291,6 +293,14 @@ | ||
| if as_boolean(os.environ.get("CN_SQL_SSL_ENABLED", "false")): | ||
| self._bootstrap_ssl_assets() | ||
|
|
||
| def _get_fernet(self): | ||
| if self._fernet is None: | ||
| key = os.environ.get("CN_SQL_SSL_SECRET_KEY") | ||
| if not key: | ||
| raise RuntimeError("CN_SQL_SSL_SECRET_KEY environment variable not set for SSL secret encryption") | ||
| self._fernet = Fernet(key.encode()) | ||
| return self._fernet | ||
|
|
||
| def _bootstrap_ssl_assets(self): | ||
| for filepath, secret_name in [ | ||
| (os.environ.get("CN_SQL_SSL_CACERT_FILE", "/etc/certs/sql_cacert.pem"), "sql_ssl_ca_cert"), | ||
| @@ -304,9 +314,13 @@ | ||
| if filepath and (contents := self.manager.secret.get(secret_name)): | ||
| logger.info(f"Detected non-empty {secret_name=}. The secret will be populated into {filepath!r}.") | ||
|
|
||
| with open(filepath, "w") as f: | ||
| f.write(contents) | ||
| # Encrypt the contents using Fernet before writing | ||
| fernet = self._get_fernet() | ||
| encrypted_contents = fernet.encrypt(contents.encode()) | ||
|
|
||
| with open(filepath, "wb") as f: | ||
| f.write(encrypted_contents) | ||
|
|
||
| # client key must be protected using 600 permission | ||
| if secret_name == "sql_ssl_client_key": # noqa: B105 | ||
| os.chmod(filepath, 0o600) |
-
Copy modified lines R4-R5
| @@ -1,4 +1,5 @@ | ||
| # handle compatibility | ||
| from setuptools import setup | ||
|
|
||
| setup() | ||
| setup()install_requires=['cryptography==46.0.1'], | ||
|
|
| Package | Version | Security advisories |
| cryptography (pypi) | 46.0.1 | None |
|
|
||
|
|
||
| def override_sql_ssl_property(sql_prop_file): | ||
| with open(sql_prop_file) as f: |
Check warning
Code scanning / CodeQL
File is not always closed Warning
|
|
|
|





Prepare
Description
Target issue
closes #12193
Implementation Details
Test and Document the changes
Please check the below before submitting your PR. The PR will not be merged if there are no commits that start with
docs:to indicate documentation changes or if the below checklist is not selected.