Skip to content

Conversation

@iromli
Copy link
Contributor

@iromli iromli commented Sep 19, 2025

Prepare


Description

Target issue

closes #12193

Implementation Details


Test and Document the changes

  • Static code analysis has been run locally and issues have been fixed
  • Relevant unit and integration tests have been added/updated
  • Relevant documentation has been updated if any (i.e. user guides, installation and configuration guides, technical design docs etc)

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.

  • I confirm that there is no impact on the docs due to the code changes in this PR.

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>
@iromli iromli self-assigned this Sep 19, 2025
@iromli iromli requested a review from moabu as a code owner September 19, 2025 06:30
@mo-auto
Copy link
Member

mo-auto commented Sep 19, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed for 'jans-pycloudlib'

Failed conditions
2 Security Hotspots

See analysis details on SonarQube Cloud

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

This expression stores
sensitive data (secret)
as clear text.
This expression stores
sensitive data (secret)
as clear text.

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.chmod for 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 cryptography library, 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 contents before writing in _bootstrap_ssl_assets.
  • Optionally, add comments describing the limitation above.

Suggested changeset 2
jans-pycloudlib/jans/pycloudlib/persistence/sql.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/jans-pycloudlib/jans/pycloudlib/persistence/sql.py b/jans-pycloudlib/jans/pycloudlib/persistence/sql.py
--- a/jans-pycloudlib/jans/pycloudlib/persistence/sql.py
+++ b/jans-pycloudlib/jans/pycloudlib/persistence/sql.py
@@ -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)
EOF
@@ -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)
jans-pycloudlib/setup.py
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/jans-pycloudlib/setup.py b/jans-pycloudlib/setup.py
--- a/jans-pycloudlib/setup.py
+++ b/jans-pycloudlib/setup.py
@@ -1,4 +1,5 @@
 # handle compatibility
 from setuptools import setup
 
-setup()
+setup()install_requires=['cryptography==46.0.1'],
+
EOF
@@ -1,4 +1,5 @@
# handle compatibility
from setuptools import setup

setup()
setup()install_requires=['cryptography==46.0.1'],

This fix introduces these dependencies
Package Version Security advisories
cryptography (pypi) 46.0.1 None
Copilot is powered by AI and may make mistakes. Always verify output.


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

File may not be closed if an exception is raised.
@mo-auto mo-auto added comp-jans-pycloudlib kind-feature Issue or PR is a new feature request labels Sep 19, 2025
@sonarqubecloud
Copy link

@sonarqubecloud
Copy link

@moabu moabu merged commit a9bbd27 into main Sep 19, 2025
5 of 6 checks passed
@moabu moabu deleted the cn-sqla-ssl branch September 19, 2025 06:37
@sonarqubecloud
Copy link

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp-jans-pycloudlib kind-feature Issue or PR is a new feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(jans-pycloudlib): add low-level support for SSL persistence connection

4 participants