Skip to content
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

Add internal CA #30

Merged
merged 21 commits into from
Dec 17, 2024
Merged

Add internal CA #30

merged 21 commits into from
Dec 17, 2024

Conversation

jschlyter
Copy link
Member

@jschlyter jschlyter commented Dec 16, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a new internal certificate authority management system.
    • Added functionality for signing Certificate Signing Requests (CSRs).
    • Implemented a utility for generating CA certificates.
    • Added support for internal CA settings in the server configuration.
    • Enhanced handling of cryptographic keys and certificate signing requests.
    • Added a Makefile target for creating a root Certificate Authority (CA) certificate.
  • Bug Fixes

    • Enhanced error handling for loading CA certificates and private keys.
  • Tests

    • Added comprehensive unit tests for the new internal certificate authority.
    • Updated existing tests to accommodate changes in CA client instantiation and function names.
    • Removed outdated dummy CA tests.
    • Updated tests to reflect the renaming of verification functions.
    • Modified tests to streamline JWS handling processes.
  • Chores

    • Cleaned up and refactored utility functions related to CA management.
    • Updated dependency versions in the project configuration.

Copy link
Contributor

coderabbitai bot commented Dec 16, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces a comprehensive certificate authority management system with the new InternalCertificateAuthority class in nodeman/internal_ca.py. This class provides functionality for creating, loading, and signing certificates using various cryptographic key types. The changes include the implementation of methods for certificate authority operations, updates to test utilities, and the removal of a previous dummy CA test implementation. Additionally, a new test file is added to validate the InternalCertificateAuthority across different key algorithms, enhancing the system's flexibility in handling certificate signing requests.

Changes

File Change Summary
nodeman/internal_ca.py Added InternalCertificateAuthority class with methods for initializing, loading, and signing certificates
tests/test_api.py Updated CA client creation with new get_ca_client() function
tests/test_dummy_ca.py Removed entire test file for dummy CA
tests/test_internal_ca.py New test file with comprehensive tests for InternalCertificateAuthority across different key types
tests/utils.py Removed CaTestClient class, added generate_ca_certificate() function
nodeman/server.py Modified NodemanServer to support internal CA management with new method get_internal_ca_client
nodeman/settings.py Added InternalCaSettings class for internal CA configuration
pyproject.toml Updated dnstapir dependency version and added redis as a new dependency
nodeman/x509.py Enhanced cryptographic key handling with new functions and type aliases for various public key types
nodeman/client.py Updated command_renew function to change how x509_key is generated
tests/test_step_ca.py Updated import statement to reflect the renaming of the CSR verification function
tests/test_x509.py Updated multiple test cases to use the new CSR verification function name
Makefile Added new target internal_ca for creating a root CA certificate and private key

Possibly related PRs

  • Add internal CA #30: The main PR introduces the InternalCertificateAuthority class in nodeman/internal_ca.py, which is the same file and class described in the retrieved PR Add internal CA #30, indicating a direct relationship between the two.

Suggested reviewers

  • morkrost

Poem

🐰 Certificates dance with grace,
Keys twirling in cryptic embrace,
RSA, Ed, curves so bright,
Our CA signs with pure delight!
Security's rabbit hops with glee 🔐


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 648fdec and 4eb9d7c.

📒 Files selected for processing (6)
  • Makefile (1 hunks)
  • nodeman/client.py (3 hunks)
  • nodeman/nodes.py (2 hunks)
  • nodeman/server.py (2 hunks)
  • nodeman/x509.py (4 hunks)
  • tests/test_api.py (9 hunks)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jschlyter
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Dec 16, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
tests/test_internal_ca.py (1)

36-36: Remove unnecessary print statement

Printing the certificate PEM in the test is unnecessary and can clutter the test output. It's best practice to remove such statements unless needed for debugging.

Apply this diff to remove the print statement:

-    print(x509_certificate_pem)
tests/utils.py (1)

27-29: Consider making certificate validity configurable

The certificate validity period is hardcoded to 1 day. Consider making this configurable through a parameter to improve flexibility for different test scenarios.

-def generate_ca_certificate(ca_name: x509.Name | str, ca_private_key: PrivateKey) -> x509.Certificate:
+def generate_ca_certificate(
+    ca_name: x509.Name | str,
+    ca_private_key: PrivateKey,
+    validity: timedelta = timedelta(days=1)
+) -> x509.Certificate:
     """Generate CA Certificate"""
 
     now = datetime.now(tz=timezone.utc)
-    validity = timedelta(days=1)
tests/test_api.py (1)

37-41: Consider parameterizing CA key type

The get_ca_client function always uses SECP256R1 for the CA key. Consider parameterizing the key type to allow testing with different CA key algorithms, especially since the test suite covers various client key types.

-def get_ca_client(ca_name: str) -> CertificateAuthorityClient:
+def get_ca_client(
+    ca_name: str,
+    key_type: str = "EC",
+    curve: str = "SECP256R1"
+) -> CertificateAuthorityClient:
-    ca_private_key = ec.generate_private_key(ec.SECP256R1())
+    ca_private_key = (
+        ec.generate_private_key(getattr(ec, curve))
+        if key_type == "EC"
+        else Ed25519PrivateKey.generate()
+        if key_type == "Ed25519"
+        else Ed448PrivateKey.generate()
+        if key_type == "Ed448"
+        else rsa.generate_private_key(
+            public_exponent=65537,
+            key_size=2048
+        )
+    )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 170e3d5 and 453febb.

📒 Files selected for processing (5)
  • nodeman/internal_ca.py (1 hunks)
  • tests/test_api.py (3 hunks)
  • tests/test_dummy_ca.py (0 hunks)
  • tests/test_internal_ca.py (1 hunks)
  • tests/utils.py (2 hunks)
💤 Files with no reviewable changes (1)
  • tests/test_dummy_ca.py
🔇 Additional comments (5)
tests/utils.py (2)

24-26: LGTM: Function signature and docstring

The function signature with type hints and docstring is clear and well-defined.


39-56: LGTM: CA certificate configuration

The certificate builder configuration is correct with appropriate extensions:

  • BasicConstraints with CA=True for CA certificate
  • KeyUsage with correct flags for certificate signing
tests/test_api.py (3)

43-47: LGTM: Test client setup

The test client setup with CA configuration is clean and well-structured.


Line range hint 55-196: LGTM: Comprehensive enrollment test implementation

The _test_enroll function provides thorough testing of the enrollment flow:

  • Creates and validates node
  • Tests enrollment with proper signatures
  • Verifies certificate renewal
  • Handles cleanup properly

Line range hint 198-238: LGTM: Excellent key type coverage

The test suite comprehensively covers various key types:

  • SECP256R1/P-256
  • SECP384R1/P-384
  • Ed25519
  • Ed448
  • RSA

nodeman/internal_ca.py Outdated Show resolved Hide resolved
nodeman/internal_ca.py Outdated Show resolved Hide resolved
nodeman/internal_ca.py Outdated Show resolved Hide resolved
tests/test_internal_ca.py Outdated Show resolved Hide resolved
@jschlyter jschlyter marked this pull request as ready for review December 16, 2024 16:07
@jschlyter jschlyter requested a review from a team as a code owner December 16, 2024 16:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (9)
nodeman/server.py (1)

68-74: Add logging to indicate which CA client is being used

To improve traceability and debugging, consider adding logging statements to indicate whether the internal_ca or step certificate authority is being used. This will help in understanding the server's configuration at startup.

tests/test_internal_ca.py (3)

59-63: Remove print statements from test functions

Printing certificate PEM data in tests can clutter the output. Consider removing these print statements or using logging with an appropriate log level if necessary.

Apply this diff to remove print statements:

 x509_certificate_pem = "".join(
     [certificate.public_bytes(serialization.Encoding.PEM).decode() for certificate in res.cert_chain]
 )
-print(x509_certificate_pem)

 x509_ca_certificate_pem = res.ca_cert.public_bytes(serialization.Encoding.PEM).decode()
-print(x509_ca_certificate_pem)

144-146: Document why verification is disabled for Ed25519 keys

In test_internal_ca_ed25519, verification is disabled by setting verify=False. Adding a comment explaining why verification is skipped will improve code clarity.

Apply this diff to add a comment:

 def test_internal_ca_ed25519() -> None:
     ca_private_key = Ed25519PrivateKey.generate()
+    # Verification is disabled due to lack of support for Ed25519 in the verification utility.
     return _test_internal_ca(ca_private_key, verify=False)

148-150: Document why verification is disabled for Ed448 keys

Similarly, in test_internal_ca_ed448, explain why verification is set to False to aid future maintenance.

Apply this diff to add a comment:

 def test_internal_ca_ed448() -> None:
     ca_private_key = Ed448PrivateKey.generate()
+    # Verification is disabled due to lack of support for Ed448 in the verification utility.
     return _test_internal_ca(ca_private_key, verify=False)
nodeman/settings.py (1)

35-39: Consider improving readability of the validity period default

The validity period is currently specified in seconds which reduces readability. Consider using a more explicit format.

-    validity: int = Field(default=24 * 60 * 60 * 90)
+    validity: int = Field(
+        default=24 * 60 * 60 * 90,  # 90 days
+        description="Validity period in seconds for issued certificates"
+    )
nodeman/internal_ca.py (2)

15-25: Consider more restrictive key usage flags

The current key usage flags might be too permissive. For client certificates, consider enabling only digital_signature since other operations aren't typically needed.


106-111: Consider documenting certificate chain behavior

The certificate chain building logic could benefit from a docstring explaining when and why the issuer certificate is included in the chain.

         if self.root_ca_certificate != self.issuer_ca_certificate:
+            # Include intermediate CA certificate in chain when using separate root and issuer CAs
             cert_chain = [certificate, self.issuer_ca_certificate]
         else:
+            # Use single certificate when root and issuer are the same
             cert_chain = [certificate]
tests/test_api.py (2)

38-48: Consider adding test cases for CA chain validation

The get_ca_client function creates a single CA certificate. Consider adding test cases with separate root and issuer CAs to validate certificate chain building.

def get_ca_client_with_chain() -> CertificateAuthorityClient:
    """Create CA client with separate root and issuer certificates for testing"""
    root_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Internal Root CA")])
    issuer_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Internal Issuing CA")])
    root_key = ec.generate_private_key(ec.SECP256R1())
    issuer_key = ec.generate_private_key(ec.SECP256R1())
    root_cert = generate_ca_certificate(root_name, root_key)
    issuer_cert = generate_ca_certificate(issuer_name, issuer_key, root_cert, root_key)
    return InternalCertificateAuthority(
        issuer_ca_certificate=issuer_cert,
        issuer_ca_private_key=issuer_key,
        root_ca_certificate=root_cert,
        validity=timedelta(minutes=10),
    )

41-42: Make test CA validity period more explicit

The test CA uses a 10-minute validity period which differs from the default in settings. Consider making this more explicit or aligning it with the default.

-    validity = timedelta(minutes=10)
+    # Use short validity for tests to avoid long-lived certificates
+    test_validity = timedelta(minutes=10)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 453febb and 4376d8e.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • nodeman/internal_ca.py (1 hunks)
  • nodeman/server.py (3 hunks)
  • nodeman/settings.py (2 hunks)
  • pyproject.toml (2 hunks)
  • tests/test_api.py (3 hunks)
  • tests/test_internal_ca.py (1 hunks)
  • tests/utils.py (2 hunks)
🔇 Additional comments (4)
tests/test_internal_ca.py (1)

48-55: Good job adding assertions to verify the signed certificate

The added assertions enhance the test by verifying that the certificate chain is not empty and that the common name matches the expected value.

pyproject.toml (2)

26-26: Confirm necessity and usage of redis dependency

The redis library has been added. Verify that it is required and that any new code utilizing redis is correctly implemented and tested.

Run the following script to check for redis usage:


14-14: Verify compatibility with updated dnstapir version

The dnstapir dependency has been updated to v1.1.0. Ensure that the project is compatible with this version and that any deprecated features are addressed.

Run the following script to check for deprecated dnstapir usage:

✅ Verification successful

No compatibility issues found with dnstapir v1.1.0

The codebase uses only stable, well-documented features from dnstapir:

  • OpenTelemetry integration (configure_opentelemetry, OtlpSettings)
  • JSON logging configuration (configure_json_logging)

These are core features that are actively maintained in the v1.1.0 release, and no deprecated functions or patterns are being used.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for deprecated functions from previous `dnstapir` versions.

# Test: Find usage of deprecated `dnstapir` functions.
rg --json --type py 'dnstapir\.\w+' | jq -r '.data.lines.text' | while read -r line; do
    if [[ "$line" =~ \.deprecated_function ]]; then
        echo "Deprecated function found: $line"
    fi
done

Length of output: 798


Script:

#!/bin/bash
# Let's check the actual usage of dnstapir in the codebase
rg -A 5 -B 5 'dnstapir' --type py

# Also check for any imports and their usage
rg -l 'from dnstapir|import dnstapir' --type py

# Look for any configuration or settings related to dnstapir
rg -A 3 'configure_opentelemetry|configure_json_logging|OtlpSettings' --type py

Length of output: 3103

nodeman/settings.py (1)

70-71: LGTM: Settings class updates

The addition of optional internal_ca and step fields provides good flexibility for different CA configurations.

nodeman/server.py Show resolved Hide resolved
nodeman/server.py Outdated Show resolved Hide resolved
tests/test_internal_ca.py Show resolved Hide resolved
tests/utils.py Outdated Show resolved Hide resolved
nodeman/internal_ca.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/utils.py (1)

23-28: Consider parameterizing the validity period.

The certificate validity period is hardcoded to 1 day. While this might be sufficient for tests, consider making it configurable through a parameter for better flexibility.

 def generate_ca_certificate(
     issuer_ca_name: x509.Name,
     issuer_ca_private_key: PrivateKey,
     root_ca_name: x509.Name | None = None,
     root_ca_private_key: PrivateKey | None = None,
+    validity_days: int = 1,
 ) -> x509.Certificate:
     """Generate CA Certificate"""
 
     now = datetime.now(tz=timezone.utc)
-    validity = timedelta(days=1)
+    validity = timedelta(days=validity_days)

Also applies to: 31-32

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4376d8e and 36bee7f.

📒 Files selected for processing (1)
  • tests/utils.py (2 hunks)
🔇 Additional comments (3)
tests/utils.py (3)

7-7: LGTM! Import changes align with the new functionality.

The imports are correctly organized and necessary for the certificate generation functionality.


Line range hint 10-21: Skipping review of unchanged code.

This existing function remains unchanged in this PR.


34-65: LGTM! Robust CA certificate generation with proper extensions.

The implementation correctly:

  • Sets all required certificate fields
  • Adds proper CA extensions with appropriate criticality
  • Uses secure defaults (random serial number)
  • Fixed the previous issue with hash algorithm selection

Let's verify the certificate generation with different key types:

✅ Verification successful

Let me check what key types are actually being used in the tests.


The CA certificate generation is thoroughly tested with diverse key types

The implementation is verified to work correctly with multiple key types through comprehensive test coverage:

  • RSA (2048-bit)
  • ECDSA with P-256 (SECP256R1)
  • ECDSA with P-384 (SECP384R1)
  • Ed25519
  • Ed448

The test suite in test_internal_ca.py explicitly verifies the CA certificate generation with each key type, and the get_hash_algorithm_from_key function (referenced in the code) is used to select the appropriate signing algorithm for each key type.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the function is used with different key types
# Expected: Find test cases using different key algorithms

rg -A 5 "generate_ca_certificate" tests/

Length of output: 3031


Script:

#!/bin/bash
# Search for key generation and usage patterns
rg -B 2 -A 2 "generate_private_key|private_key =" tests/

Length of output: 6812

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
nodeman/server.py (2)

67-73: ⚠️ Potential issue

Add validation for mutually exclusive CA settings.

When both internal_ca and step settings are provided, the code silently prefers internal_ca. This should be explicitly validated to prevent confusion.

+        if self.settings.internal_ca and self.settings.step:
+            raise ValueError("Cannot specify both internal_ca and step settings")
+
         if self.settings.internal_ca:
             self.ca_client = self.get_internal_ca_client(self.settings.internal_ca)
         elif self.settings.step:
             self.ca_client = self.get_step_client(self.settings.step)

74-81: ⚠️ Potential issue

Add error handling for certificate loading operations.

The get_internal_ca_client method should handle potential file operation errors and provide meaningful error messages.

     @staticmethod
     def get_internal_ca_client(settings: InternalCaSettings) -> InternalCertificateAuthority:
-        return InternalCertificateAuthority.load(
-            issuer_ca_certificate_file=settings.issuer_ca_certificate,
-            issuer_ca_private_key_file=settings.issuer_ca_private_key,
-            root_ca_certificate_file=settings.root_ca_certificate,
-            validity_days=settings.validity_days,
-        )
+        try:
+            return InternalCertificateAuthority.load(
+                issuer_ca_certificate_file=settings.issuer_ca_certificate,
+                issuer_ca_private_key_file=settings.issuer_ca_private_key,
+                root_ca_certificate_file=settings.root_ca_certificate,
+                validity_days=settings.validity_days,
+            )
+        except OSError as e:
+            logger.error("Failed to load CA certificates: %s", e)
+            raise
+        except ValueError as e:
+            logger.error("Invalid CA certificate or private key: %s", e)
+            raise
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36bee7f and b229839.

📒 Files selected for processing (4)
  • nodeman/internal_ca.py (1 hunks)
  • nodeman/server.py (2 hunks)
  • nodeman/settings.py (2 hunks)
  • tests/utils.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • nodeman/settings.py
🔇 Additional comments (4)
tests/utils.py (1)

23-66: LGTM! The certificate generation logic is well-implemented.

The function correctly:

  • Handles both issuer and root CA scenarios
  • Sets appropriate validity periods
  • Configures proper CA extensions
  • Uses the correct private key and hash algorithm for signing
nodeman/internal_ca.py (3)

34-49: LGTM! Constructor is well-implemented with proper defaults.

The initialization logic correctly:

  • Handles optional parameters with sensible defaults
  • Sets up the signature hash algorithm based on the private key
  • Properly manages the root CA certificate fallback

50-78: LGTM! The load method handles file operations safely.

The implementation:

  • Uses proper type hints with Path objects
  • Includes password=None as required
  • Handles root CA certificate loading conditionally

15-25: Consider enabling key_encipherment for broader compatibility.

The KEY_USAGE extension has key_encipherment=False, which might limit compatibility with some clients that expect to use the certificate for TLS connections where key encipherment is required.

     KEY_USAGE = x509.KeyUsage(
         digital_signature=True,
         content_commitment=False,
-        key_encipherment=False,
+        key_encipherment=True,
         data_encipherment=False,
         key_agreement=False,
         key_cert_sign=False,
         crl_sign=False,
         encipher_only=False,
         decipher_only=False,
     )
✅ Verification successful

The current key usage settings are appropriate for client authentication

The code appears to be generating client certificates for TLS client authentication, as evidenced by the client.py configuration which specifically handles TLS client certificates. For client authentication certificates, digital_signature capability alone is sufficient and standard practice, while key_encipherment is typically only required for server certificates. The current KEY_USAGE settings align with the intended use case.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if any existing certificates in the codebase use key_encipherment
rg -l "key_encipherment.*=.*True" --type python

Length of output: 80


Script:

#!/bin/bash
# Check if any existing certificates in the codebase use key_encipherment
rg -l "key_encipherment.*=.*True"

# Also search for any TLS-related code or configurations
rg -l "TLS|SSL"

# Look for any certificate-related test files or configurations
fd -e py -e yaml -e json "cert|certificate" --type f

Length of output: 126


Script:

#!/bin/bash
# Let's check the client.py file to understand the TLS/SSL usage context
rg -A 5 -B 5 "TLS|SSL" nodeman/client.py

# Let's also check if there are any certificate validation or usage patterns
rg "verify.*certificate|check.*certificate" nodeman/client.py

# Check for any certificate-related configurations or settings
rg "cert.*config|certificate.*config" nodeman/client.py

Length of output: 768

nodeman/internal_ca.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
nodeman/internal_ca.py (1)

56-56: 🛠️ Refactor suggestion

Default validity period should align with settings

The default validity period in InternalCertificateAuthority is set to 1 day (validity_days=1), which may be inconsistent with the project's default validity period defined in settings (e.g., validity = 90 days). Consider updating the default or passing the settings' value to ensure consistency.

🧹 Nitpick comments (2)
tests/test_api.py (2)

38-47: Consider making the CA certificate validity period configurable

The current implementation uses a fixed 10-minute validity period. Consider making this configurable to support different test scenarios, such as testing certificate expiration or long-lived certificates.

-def get_ca_client() -> CertificateAuthorityClient:
+def get_ca_client(validity: timedelta = timedelta(minutes=10)) -> CertificateAuthorityClient:
     ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Internal Test CA")])
     ca_private_key = ec.generate_private_key(ec.SECP256R1())
     ca_certificate = generate_ca_certificate(ca_name, ca_private_key)
-    validity = timedelta(minutes=10)
     return InternalCertificateAuthority(
         issuer_ca_certificate=ca_certificate,
         issuer_ca_private_key=ca_private_key,
         validity=validity,
     )

38-47: Consider adding edge case tests for CA operations

While the test coverage is comprehensive for normal operations, consider adding tests for the following scenarios:

  1. CA certificate expiration
  2. Invalid CA private key
  3. CA certificate chain validation
  4. Different key sizes for CA keys

Would you like me to help generate these additional test cases?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b229839 and f8b72d5.

📒 Files selected for processing (4)
  • nodeman/internal_ca.py (1 hunks)
  • nodeman/x509.py (4 hunks)
  • tests/test_api.py (4 hunks)
  • tests/test_internal_ca.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_internal_ca.py
🔇 Additional comments (5)
nodeman/x509.py (1)

18-18: Ensure compatibility with Python versions below 3.10

The use of the | operator for type unions in type PrivateKey = ... requires Python 3.10 or newer. If your project needs to support earlier Python versions, consider using typing.Union instead.

Run the following script to verify the Python version specified in your project configurations:

nodeman/internal_ca.py (1)

4-4: Ensure compatibility of typing.Self usage with Python versions below 3.11

The Self type annotation was introduced in Python 3.11. If your project needs to support earlier Python versions, consider replacing -> Self with -> InternalCertificateAuthority or using from __future__ import annotations to postpone evaluation of annotations.

Run the following script to check the Python version specified in your project configurations:

Also applies to: 62-67

✅ Verification successful

Let me search for any other files that might specify Python version requirements, such as requirements.txt, tox.ini, or any other configuration files.


Let me check the project's dependency management files more thoroughly, particularly the poetry.lock and pyproject.toml files, as we've seen poetry.lock references.


Python version requirement is compatible with typing.Self usage

The project explicitly requires Python 3.12 (python = "^3.12" in pyproject.toml), which is newer than Python 3.11 where typing.Self was introduced. Therefore, there are no compatibility concerns with using this feature.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the project specifies Python 3.11 or newer.

# Search for Python version requirements in setup.py or pyproject.toml
rg "python_requires.*" setup.py pyproject.toml || echo "Python version requirement not specified."

Length of output: 190


Script:

#!/bin/bash
# Search for Python version specifications in various configuration files
echo "=== Checking requirements.txt ==="
rg "^python[>=<]" requirements*.txt || echo "No Python version in requirements.txt"

echo -e "\n=== Checking tox.ini ==="
rg "^python.*=" tox.ini || echo "No tox.ini found or no Python version specified"

echo -e "\n=== Checking any .python-version files ==="
fd -g ".python-version" -x cat {}

echo -e "\n=== Checking for runtime version checks ==="
rg "sys\.version_info|python_version" -g "!*.pyc"

echo -e "\n=== Checking CI configuration files ==="
fd -e yml -e yaml | xargs rg "python.*version|python:.*3\." || echo "No Python version in CI configs"

Length of output: 1429


Script:

#!/bin/bash
# Check poetry configuration and dependencies
echo "=== Checking pyproject.toml for Python version constraints ==="
cat pyproject.toml || echo "No pyproject.toml found"

echo -e "\n=== Checking actual usage of Self type ==="
rg "Self" -A 5 -B 5 nodeman/internal_ca.py

echo -e "\n=== Checking typing_extensions dependency ==="
rg "typing-extensions" pyproject.toml poetry.lock

Length of output: 2931

tests/test_api.py (3)

4-4: LGTM: Import changes align with new CA implementation

The new imports support the transition to using InternalCertificateAuthority and provide necessary utilities for certificate generation and validation.

Also applies to: 14-14, 21-21, 26-26


52-52: LGTM: Clean integration of new CA client

The test client setup correctly integrates the new CA client implementation.


246-246: Verify RSA_EXPONENT value for security considerations

The code now uses the centralized RSA_EXPONENT constant. Let's verify its value meets security standards.

✅ Verification successful

RSA exponent value meets security standards

The RSA_EXPONENT is set to 65537 (0x10001) which is the recommended value for RSA key generation. This is a Fermat prime commonly used in practice as it provides a good balance between security and performance.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check RSA_EXPONENT value in x509.py
# Common secure values are typically 65537 (0x10001)

rg -A 1 "RSA_EXPONENT\s*=" 

Length of output: 177

nodeman/x509.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
Makefile (1)

53-55: Consider adding clean target for internal CA files

The internal CA files should be included in the clean target to ensure proper cleanup.

Add the following to the clean target:

 clean:
-	rm -f $(STEP_CA_FILES) $(CLIENT_FILES)
+	rm -f $(STEP_CA_FILES) $(CLIENT_FILES) internal_ca_*.pem
 	rm -f $(BUILDINFO) $(OPENAPI)
nodeman/internal_ca.py (2)

44-59: Consider using settings.validity instead of validity_days

The initialization accepts both validity_days and validity parameters, which could lead to confusion. Consider standardizing on settings.validity (in seconds) to align with the rest of the codebase.


95-132: Consider adding certificate policy and CRL distribution points extensions

The sign_csr method could be enhanced with optional certificate policy and CRL distribution points extensions for better PKI management.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8b72d5 and ec76439.

📒 Files selected for processing (8)
  • Makefile (1 hunks)
  • nodeman/client.py (1 hunks)
  • nodeman/internal_ca.py (1 hunks)
  • nodeman/server.py (2 hunks)
  • nodeman/x509.py (4 hunks)
  • tests/test_internal_ca.py (1 hunks)
  • tests/test_step_ca.py (2 hunks)
  • tests/test_x509.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_internal_ca.py
🔇 Additional comments (9)
tests/test_x509.py (2)

11-11: LGTM: Function import renamed consistently

The import statement has been updated to use the new function name verify_x509_csr_data, maintaining consistency with the function rename in the codebase.


36-36: LGTM: Test cases updated consistently

All test cases have been properly updated to use the renamed function verify_x509_csr_data while maintaining their original test logic and error handling.

Let's verify that all occurrences of the old function name have been updated:

Also applies to: 42-42, 48-48, 54-54, 60-60

✅ Verification successful

Function renaming verification complete - all instances properly updated

The codebase search shows consistent usage of two distinct functions:

  • verify_x509_csr_data: Used in test files and main code for data validation
  • verify_x509_csr_signature: Used for signature verification

All test cases are correctly using verify_x509_csr_data, and there are no remaining instances of any old function name that needs updating. The changes are consistent across the entire codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining instances of the old function name
rg "verify_x509_csr[^_]"

Length of output: 26


Script:

#!/bin/bash
# Let's first check for both old and new function names to understand the changes
rg "verify_x509_csr" -A 2

Length of output: 1980


Script:

#!/bin/bash
# Let's also check the function definition to understand the change
ast-grep --pattern 'def verify_x509_csr$$$'

Length of output: 45


Script:

#!/bin/bash
# Let's see the test file content to verify the changes
cat tests/test_x509.py

Length of output: 2088

tests/test_step_ca.py (1)

10-10: LGTM: StepCA test updated consistently

The import statement and function call have been properly updated to use verify_x509_csr_data, maintaining consistency with the function rename while preserving the test's integration with the StepCA client.

Also applies to: 49-49

Makefile (1)

53-55: ⚠️ Potential issue

Security: Enhance protection for CA private key and certificate

The current implementation has several security concerns:

  1. The private key is created without password protection (--no-password)
  2. The --insecure flag bypasses security checks
  3. No specific file permissions are set for sensitive files

Consider the following improvements:

 internal_ca:
-	step certificate create root-ca internal_ca_certificate.pem internal_ca_private_key.pem --profile root-ca --insecure --no-password
+	step certificate create root-ca internal_ca_certificate.pem internal_ca_private_key.pem --profile root-ca
+	chmod 600 internal_ca_private_key.pem
+	chmod 644 internal_ca_certificate.pem

Let's verify the current file permissions of any existing CA files:

nodeman/server.py (2)

67-73: ⚠️ Potential issue

Add validation for mutually exclusive CA settings

The code silently prefers internal_ca over step when both are configured. This should be explicitly validated.

+        if self.settings.internal_ca and self.settings.step:
+            raise ValueError("Cannot configure both internal_ca and step settings")
         if self.settings.internal_ca:
             self.ca_client = self.get_internal_ca_client(self.settings.internal_ca)
         elif self.settings.step:
             self.ca_client = self.get_step_client(self.settings.step)
         else:
             self.ca_client = None

Likely invalid or redundant comment.


74-83: 🛠️ Refactor suggestion

Add error handling for file operations in get_internal_ca_client

The method should handle file operation errors gracefully with proper logging.

     @staticmethod
     def get_internal_ca_client(settings: InternalCaSettings) -> InternalCertificateAuthority:
-        res = InternalCertificateAuthority.load(
-            issuer_ca_certificate_file=settings.issuer_ca_certificate,
-            issuer_ca_private_key_file=settings.issuer_ca_private_key,
-            root_ca_certificate_file=settings.root_ca_certificate,
-            validity_days=settings.validity_days,
-        )
-        logger.info("Configured Internal CA (%s)", res.ca_fingerprint)
-        return res
+        try:
+            res = InternalCertificateAuthority.load(
+                issuer_ca_certificate_file=settings.issuer_ca_certificate,
+                issuer_ca_private_key_file=settings.issuer_ca_private_key,
+                root_ca_certificate_file=settings.root_ca_certificate,
+                validity_days=settings.validity_days,
+            )
+            logger.info("Configured Internal CA (%s)", res.ca_fingerprint)
+            return res
+        except OSError as exc:
+            logger.error("Failed to load Internal CA: %s", exc)
+            raise

Likely invalid or redundant comment.

nodeman/x509.py (1)

126-130: ⚠️ Potential issue

Fix incorrect signature verification parameters

The current implementation incorrectly uses signature_algorithm_parameters for padding and signature algorithm.

     if isinstance(public_key, RSAPublicKey):
-        verify_kwargs = {"algorithm": csr.signature_hash_algorithm, "padding": csr.signature_algorithm_parameters}
+        verify_kwargs = {
+            "padding": padding.PKCS1v15(),
+            "algorithm": csr.signature_hash_algorithm
+        }
     elif isinstance(public_key, EllipticCurvePublicKey):
-        verify_kwargs = {"signature_algorithm": csr.signature_algorithm_parameters}
+        verify_kwargs = {
+            "signature_algorithm": ec.ECDSA(csr.signature_hash_algorithm)
+        }

Likely invalid or redundant comment.

nodeman/client.py (2)

240-241: LGTM! Good security improvement.

Using the key type and curve from the existing data key ensures cryptographic consistency during certificate renewal, preventing potential key type mismatches.


240-241: Verify error handling for all possible key types.

Let's ensure proper error handling for all possible key types that could be present in the data key.

✅ Verification successful

Based on the results, I can now generate the final response as we have comprehensive information about the error handling for all key types.

Error handling for key types is properly implemented

The generate_x509_key function has exhaustive pattern matching that handles all key types used in the codebase:

  • Explicitly rejects RSA keys with a clear error message
  • Supports EC keys with P-256 and P-384 curves
  • Supports OKP keys with Ed25519 and Ed448 curves

The implementation aligns perfectly with all key types used across the codebase, as evidenced by the JWK key generation patterns in tests and client code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check supported key types in generate_x509_key function and data key usage

# Find all key type handling in generate_x509_key
echo "Checking key type handling in generate_x509_key:"
rg -A 1 "case \(" nodeman/client.py

# Find all places where JWK keys are created to identify possible key types
echo "Checking JWK key creation:"
rg "JWK\.generate|JWK\.from_" 

Length of output: 1986

nodeman/internal_ca.py Outdated Show resolved Hide resolved
nodeman/internal_ca.py Outdated Show resolved Hide resolved
nodeman/x509.py Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
nodeman/internal_ca.py (4)

50-52: Simplify validity period configuration

The constructor accepts both validity_days and validity parameters, which could lead to confusion. Consider:

  1. Using only validity: timedelta
  2. Adding validation to ensure the validity period is reasonable
-        validity_days: int = 1,
-        validity: timedelta | None = None,
+        validity: timedelta = timedelta(days=1),

57-58: Document the rationale for time_skew and validity defaults

The default values for time_skew (10 minutes) and validity period (1 day) should be documented to explain their purpose and why these specific values were chosen.


70-75: Enhance error handling for file operations

The file operations should be wrapped in try-except blocks to provide more informative error messages when files are missing or corrupted.

-        with open(issuer_ca_certificate_file, "rb") as fp:
-            issuer_ca_certificate = x509.load_pem_x509_certificate(fp.read())
+        try:
+            with open(issuer_ca_certificate_file, "rb") as fp:
+                issuer_ca_certificate = x509.load_pem_x509_certificate(fp.read())
+        except FileNotFoundError as e:
+            raise ValueError(f"CA certificate file not found: {e.filename}")
+        except Exception as e:
+            raise ValueError(f"Failed to load CA certificate: {e}")

97-99: Consider adding more detailed logging

The debug log could include more information about the CSR, such as the public key algorithm and requested extensions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec76439 and 84a1842.

📒 Files selected for processing (2)
  • nodeman/internal_ca.py (1 hunks)
  • tests/test_internal_ca.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_internal_ca.py
🔇 Additional comments (5)
nodeman/internal_ca.py (5)

1-21: LGTM! Well-organized imports and proper logger setup.

The imports are comprehensive and logically grouped, covering all necessary cryptographic functionality. The logger setup follows best practices.


26-36: Consider adding key_encipherment to KEY_USAGE for TLS certificates

The current KEY_USAGE flags might be too restrictive for TLS certificates. For TLS client and server authentication, key_encipherment is typically required, especially when using RSA keys.


73-74: Add type checking for private key in load method

The load method should verify that issuer_ca_private_key is an instance of PrivateKey before returning.


90-93: LGTM! Secure implementation of certificate fingerprint.

The implementation correctly uses SHA256 for fingerprinting and properly formats the output.


126-130: LGTM! Proper certificate chain construction.

The logic for building the certificate chain correctly handles both root and intermediate CA scenarios.

nodeman/internal_ca.py Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
nodeman/x509.py (2)

57-70: ⚠️ Potential issue

Add key size and curve validations for security.

While the function correctly handles all supported key types, it should include validations for key sizes and curves.

Apply this diff to add the necessary validations:

 def generate_similar_key(key: PrivateKey) -> PrivateKey:
     """Generate similar new private key"""
 
     if isinstance(key, rsa.RSAPrivateKey):
+        if key.key_size < 2048:
+            raise ValueError("RSA key size must be at least 2048 bits")
         return rsa.generate_private_key(public_exponent=RSA_EXPONENT, key_size=key.key_size)
     elif isinstance(key, ec.EllipticCurvePrivateKey):
+        if not isinstance(key.curve, (ec.SECP256R1, ec.SECP384R1, ec.SECP521R1)):
+            raise ValueError("Unsupported curve, must be NIST P-256, P-384, or P-521")
         return ec.generate_private_key(curve=key.curve)

120-147: ⚠️ Potential issue

Fix incorrect verification parameters in CSR signature verification.

The current implementation may not correctly verify the CSR signature due to improper use of csr.signature_algorithm_parameters.

Apply this diff to correct the verification parameters:

+from cryptography.hazmat.primitives.asymmetric import padding, ec

     if isinstance(public_key, RSAPublicKey):
-        verify_kwargs = {
-            "algorithm": csr.signature_hash_algorithm,
-            "padding": csr.signature_algorithm_parameters,
-        }
+        verify_kwargs = {
+            "padding": padding.PKCS1v15(),
+            "algorithm": csr.signature_hash_algorithm
+        }
     elif isinstance(public_key, EllipticCurvePublicKey):
-        verify_kwargs = {
-            "signature_algorithm": ec.ECDSA(csr.signature_hash_algorithm),
-        }
+        verify_kwargs = {
+            "signature_algorithm": ec.ECDSA(csr.signature_hash_algorithm)
+        }
🧹 Nitpick comments (1)
nodeman/x509.py (1)

152-183: LGTM! Consider enhancing error logging.

The changes improve error handling and logging with good use of structured logging fields. However, consider including more details in the error log.

Apply this diff to enhance error logging:

     try:
         ca_response = request.app.ca_client.sign_csr(csr, name)
     except Exception as exc:
-        logger.error("Failed to process CSR for %s", name)
+        logger.error("Failed to process CSR for %s: %s", name, str(exc), exc_info=exc)
         raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error issuing certificate") from exc
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84a1842 and 648fdec.

📒 Files selected for processing (2)
  • nodeman/internal_ca.py (1 hunks)
  • nodeman/x509.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • nodeman/internal_ca.py
🔇 Additional comments (2)
nodeman/x509.py (2)

4-18: LGTM! Well-structured imports and type definitions.

The imports are properly organized, and the RSA_EXPONENT value of 65537 (Fermat F4 number) is the standard choice for RSA key generation. The PrivateKey type alias correctly encompasses all supported key types.


Line range hint 88-118: LGTM! Comprehensive CSR data validation.

The function performs thorough validation of the CSR data with proper error handling and logging. The specific exception types help in precise error identification.

@jschlyter jschlyter requested a review from morkrost December 17, 2024 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant