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

feature: Redis server TLS support #104

Merged
merged 8 commits into from
Nov 20, 2024

Conversation

sicet7
Copy link
Contributor

@sicet7 sicet7 commented Nov 19, 2024

Reason for This PR

We are (at my employer ASG-Digital) using Azure's product Azure Cache for Redis and we were having some issues connecting to their default configuration which disables non-tls connections and uses their own CA to sign their certificates.
I implemented the required changes to be able to connect to Azure Cache for Redis default configuration without having to re-enable non-tls connections in the Azure control panel.

Description of Changes

I added a tls configuration section under the redis configuration to allow for configuration of the min_version and ca_file options.
The min_version option configures the minimum TLS version that can be used for connecting to the remote redis server. It accepts the following values: 1.0, 1.1, 1.2 and 1.3, it defaults to 1.3 if only the ca_file option is provided.
The ca_file is a path either global or relative, to a file containing one or more PEM certificates, that should be used to verify the server certificate.

These changes are made so that existing configurations should still work without any changes needed.
The TLS configuration is only applied to the connection configuration if one or more of the configuration options min_version or ca_file is provided.

Snipped from the RoadRunner configuration i used for testing

#Cache
kv:
    redis:
        driver: redis
        config:
            addrs:
                - "${REDIS_HOST:-127.0.0.1}:${REDIS_PORT:-6379}"
            username: "${REDIS_USERNAME}"
            password: "${REDIS_PASSWORD}"
            db: "${REDIS_DB:-0}"
            tls:
                ca_file: ../.docker/certs/Azure.crt.pem

License Acceptance

By submitting this pull request, I confirm that my contribution is made under
the terms of the MIT license.

PR Checklist

[Author TODO: Meet these criteria.]
[Reviewer TODO: Verify that these criteria are met. Request changes if not]

  • All commits in this PR are signed (git commit -s).
  • The reason for this PR is clearly provided (issue no. or explanation).
  • The description of changes is clear and encompassing.
  • Any required documentation changes (code and docs) are included in this PR.
    I was not able to find any documentation in need of updating in this repo.
  • Any user-facing changes are mentioned in CHANGELOG.md.
    I was not able to find the mentioned file in this repo.
  • All added/changed functionality is tested.

Summary by CodeRabbit

  • New Features

    • Introduced TLS configuration options for enhanced security in Redis connections.
    • Added support for specifying a CA file for certificate handling.
    • New TLSConfig struct created to manage TLS settings, including certificate paths.
    • Enhanced NewRedisDriver function to utilize TLS settings for secure connections.
  • Bug Fixes

    • Improved error handling for TLS configuration, ensuring better feedback on certificate issues.

Copy link

coderabbitai bot commented Nov 19, 2024

Walkthrough

The changes introduce a new TLSConfig struct to the kv package, enhancing TLS configuration capabilities. The Config struct is updated to include this new TLSConfig field, which is mapped to the key "tls". The kv/driver.go file is modified to implement TLS configuration in the Redis driver, including error handling for certificate files and the integration of the new TLS settings. Additionally, a new file tlsconfig.go is created to define the TLSConfig struct and its associated initialization functions.

Changes

File Change Summary
kv/config.go Added TLSConfig field to Config struct.
kv/driver.go Enhanced NewRedisDriver function to incorporate TLS configuration for Redis. Improved error handling for TLS setup.
kv/tlsconfig.go Introduced TLSConfig struct and functions for TLS configuration management: tlsConfig, defaultTLSConfig, and getClientCertificate.
go.mod Added direct dependency on golang.org/x/sys v0.27.0 and removed its indirect requirement.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RedisDriver
    participant TLSConfig

    Client->>RedisDriver: Initialize with TLSConfig
    RedisDriver->>TLSConfig: Read RootCa
    TLSConfig-->>RedisDriver: Return TLS settings
    RedisDriver->>Client: Establish secure connection
Loading

🐰 "In the land of code where the rabbits play,
New TLS settings hop in, brightening the day.
With configs so snug, and errors kept at bay,
Our Redis driver dances, come what may!
Hooray for the changes, let’s celebrate and say,
Secure connections are here, hip-hip-hooray!" 🐇


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6f6e25f and c63b869.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod (1 hunks)
  • kv/driver.go (2 hunks)
  • kv/tlsconfig.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • go.mod
  • kv/driver.go
  • kv/tlsconfig.go

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 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.

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
kv/config.go (2)

33-36: Add field documentation and consider path validation

While the struct is well-structured, consider these improvements:

  1. Add GoDoc comments describing the struct and its fields, including:
    • Valid values for MinVersion
    • Expected format for CaFile
  2. Consider validating the CaFile path early

Add documentation like this:

+// TLSConfig contains TLS/SSL configuration for Redis connections
 type TLSConfig struct {
+    // MinVersion specifies the minimum TLS version. Valid values: "1.0", "1.1", "1.2", "1.3" (default)
     MinVersion string `mapstructure:"min_version"`
+    // CaFile specifies the path to a PEM file containing CA certificates
     CaFile     string `mapstructure:"ca_file"`
 }

45-56: Consider enhancing version handling robustness

While the implementation is functionally correct, consider these improvements for better maintainability and clarity:

+// TLS version string constants
+const (
+    TLSVersion10 = "1.0"
+    TLSVersion11 = "1.1"
+    TLSVersion12 = "1.2"
+    TLSVersion13 = "1.3"
+)

 func (t *TLSConfig) TLSVersion() uint16 {
+    // Return default if MinVersion is empty
+    if t.MinVersion == "" {
+        return tls.VersionTLS13
+    }
     switch t.MinVersion {
-    case "1.0":
+    case TLSVersion10:
         return tls.VersionTLS10
-    case "1.1":
+    case TLSVersion11:
         return tls.VersionTLS11
-    case "1.2":
+    case TLSVersion12:
         return tls.VersionTLS12
     default:
         return tls.VersionTLS13
     }
 }
kv/driver.go (1)

107-110: Assign RootCAs to tlsConfig when using TLS

Currently, tlsConfig.RootCAs is set only if CaFile or MinVersion is specified. However, to ensure the default system root CAs are used even when CaFile is not provided, consider assigning rootCAs unconditionally when TLS is in use.

Modify the condition to check for TLS usage instead:

-if d.cfg.TLSConfig.CaFile != "" || d.cfg.TLSConfig.MinVersion != "" {
+if d.cfg.TLSConfig != nil {
     tlsConfig.RootCAs = rootCAs
     redisOptions.TLSConfig = tlsConfig
 }

Alternatively, always assign RootCAs when TLS is configured:

 tlsConfig.RootCAs = rootCAs
 redisOptions.TLSConfig = tlsConfig

This ensures that the system's root certificates are available for server certificate verification.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between da4a4e0 and c00a4ed.

📒 Files selected for processing (2)
  • kv/config.go (3 hunks)
  • kv/driver.go (3 hunks)
🧰 Additional context used
🪛 golangci-lint
kv/driver.go

[high] 82-82: G402: TLS MinVersion too low.

(gosec)

🔇 Additional comments (3)
kv/config.go (3)

4-4: LGTM: Required import for TLS support

The crypto/tls import is necessary for TLS version constants and configuration.


30-30: LGTM: Clean addition of TLS configuration field

The TLSConfig field is properly integrated into the existing Config struct with consistent mapstructure tag formatting.


Line range hint 39-43: Verify if InitDefaults needs TLS initialization

The InitDefaults method doesn't initialize TLS fields. This is likely correct as TLS should be opt-in, but please verify this is the intended behavior for Azure Redis Cache scenarios.

✅ Verification successful

Let me gather more context about the TLS-related fields and their usage.


TLS initialization in InitDefaults is correctly implemented as opt-in

The current implementation is correct. The TLS configuration is intentionally not initialized with defaults because:

  1. TLS fields (MinVersion and CaFile) are optional and only used when explicitly set:
    • MinVersion defaults to TLS 1.3 when provided but empty
    • CaFile is only required for custom CA certificates
  2. The driver.go implementation properly handles both TLS and non-TLS scenarios, only applying TLS configuration when either field is set
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any default TLS settings in the codebase
rg -l "TLS" . | xargs rg "InitDefaults|default"

Length of output: 333


Script:

#!/bin/bash
# Check TLS-related fields and their usage in the config
ast-grep --pattern 'type Config struct {
  $$$
}'

# Check for any TLS configuration patterns in tests or examples
rg -A 5 "TLS" .

Length of output: 5421

kv/driver.go Outdated Show resolved Hide resolved
kv/driver.go Outdated Show resolved Hide resolved
kv/driver.go Outdated Show resolved Hide resolved
kv/driver.go Outdated Show resolved Hide resolved
@rustatian
Copy link
Member

Hey @sicet7 👋
Thank you for the PR 👍👍 This is indeed a cool functionality you've added 👍

Just a few notes:

  1. TLS min version should be 1.2, and it is not needed to expose this configuration option, because even for the Microsoft, TLS 1.0 and 1.1 are deprecated: link.
  2. It'd be nice to have unified options across the RR configuration, for example, these are TLS options: link, you may check validation mechanism and tls.Config creation: link, and you may generally have some inspiration from this code: link
  3. It would be nice if you'd be able to add tests, they're located in the tests folder. For TLS, I usually use mkcert in the GH Actions: link

@rustatian rustatian self-requested a review November 19, 2024 16:07
@rustatian rustatian added the enhancement New feature or request label Nov 19, 2024
@rustatian rustatian changed the title Implemented verification of redis server certificates. feature: Redis server TLS support Nov 19, 2024
Copy link

@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: 2

🧹 Outside diff range and nitpick comments (1)
kv/config.go (1)

29-29: Add documentation for the TLS configuration field

Please add a comment explaining the purpose and usage of the TLS configuration.

+// TLSConfig contains TLS/SSL configuration for Redis connections
 TLSConfig        TLSConfig     `mapstructure:"tls"`
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c00a4ed and f656464.

📒 Files selected for processing (2)
  • kv/config.go (1 hunks)
  • kv/driver.go (3 hunks)
🔇 Additional comments (3)
kv/config.go (1)

29-33: Verify TLS configuration alignment with RoadRunner standards

As mentioned in the PR comments, the TLS configuration should be unified across RoadRunner. Let's verify the existing TLS configuration patterns.

✅ Verification successful

Let me gather more information about TLS configurations across the codebase to ensure a comprehensive verification.


Let me try one more search to find any TLS-related configurations in other parts of the codebase, particularly focusing on the root and config directories.


Based on the gathered information, I can now provide the final response:

TLS configuration is appropriately implemented

The current TLS configuration implementation in the KV plugin follows secure practices:

  • Uses TLS 1.2 as minimum version
  • Properly handles CA certificates
  • Maintains a simple, focused configuration structure that matches the requirements

The implementation is sufficient for the KV plugin's specific needs, and there's no evidence of conflicting TLS configuration patterns in the codebase that would require unification.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other TLS configurations in the codebase
echo "Searching for TLS configuration patterns..."
rg -A 10 'type.*TLS.*Config.*struct' --type go

echo "Searching for TLS version handling..."
rg -A 5 'MinVersion.*tls\.' --type go

Length of output: 892


Script:

#!/bin/bash
# Search for TLS-related configurations in other parts of the codebase
echo "Searching for TLS-related imports and usages..."
rg 'tls\.' --type go -B 2 -A 2

echo "Searching for TLS configuration struct fields..."
rg 'tls.*mapstructure' --type go -B 2 -A 2

echo "Searching for CA file configurations..."
rg 'ca_file.*mapstructure' --type go -B 2 -A 2

Length of output: 607


Script:

#!/bin/bash
# Search for files that might contain TLS configurations
echo "Searching for potential config files..."
fd -e go -e yaml -e json . | grep -i "config\|tls"

echo "Searching for TLS-related code in config files..."
rg -l "tls" --type go $(fd -e go . | grep -i "config")

echo "Checking imports in the driver file..."
rg '^import \($' -A 10 kv/driver.go

Length of output: 638

kv/driver.go (2)

Line range hint 59-80: LGTM: Redis configuration options are well-structured

The Redis configuration options are comprehensive and include all necessary connection parameters.


82-91: LGTM: Secure TLS configuration with proper error handling

The implementation:

  • Sets a secure default of TLS 1.2
  • Properly handles system certificate pool errors
  • Provides clear error messages

kv/config.go Outdated Show resolved Hide resolved
kv/driver.go Outdated Show resolved Hide resolved
kv/config.go Outdated
@@ -26,6 +26,11 @@ type Config struct {
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
IdleCheckFreq time.Duration `mapstructure:"idle_check_freq"`
ReadOnly bool `mapstructure:"read_only"`
TLSConfig TLSConfig `mapstructure:"tls"`
Copy link
Member

Choose a reason for hiding this comment

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

Should be a pointer: *TLSConfig to distinguish between empty and non set values.

kv/config.go Outdated
}

type TLSConfig struct {
CaFile string `mapstructure:"ca_file"`
Copy link
Member

Choose a reason for hiding this comment

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

Also, please, follow the standard RR configuration naming for the TLS options, example: https://github.com/roadrunner-server/grpc/blob/master/tests/configs/.rr-grpc-rq-tls-rootca.yaml#L20

kv/driver.go Outdated
})
}

if d.cfg.TLSConfig.CaFile != "" {
Copy link
Member

Choose a reason for hiding this comment

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

Check for nil

kv/driver.go Outdated
Comment on lines 87 to 108
rootCAs, sysCertErr := x509.SystemCertPool()
if sysCertErr != nil {
rootCAs = x509.NewCertPool()
d.log.Warn("unable to load system certificate pool, using empty pool", zap.Error(sysCertErr))
}

if _, crtExistErr := os.Stat(d.cfg.TLSConfig.CaFile); crtExistErr != nil {
return nil, errors.E(op, crtExistErr)
}

bytes, crtReadErr := os.ReadFile(d.cfg.TLSConfig.CaFile)
if crtReadErr != nil {
return nil, errors.E(op, crtReadErr)
}

if !rootCAs.AppendCertsFromPEM(bytes) {
return nil, errors.E(op, errors.Errorf("failed to parse certificates from PEM file '%s'. Please ensure the file contains valid PEM-encoded certificates", d.cfg.TLSConfig.CaFile))
}

tlsConfig.RootCAs = rootCAs
redisOptions.TLSConfig = tlsConfig
}
Copy link
Member

Choose a reason for hiding this comment

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

It is better to move TLS configuration to the separate package

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry i am new to go, when you say new package do you mean in another repo or simply in another directory?? 😅

Copy link
Member

Choose a reason for hiding this comment

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

Sure, np, I'll help you 😃
Just a new file called tlsconfig.go or similar :)

@sicet7
Copy link
Contributor Author

sicet7 commented Nov 19, 2024

@rustatian Some thing like this?
https://github.com/ASG-Digital/rr-go-plugin-redis/pull/1
Above is just a temp PR, that i wont merge but cherry-pick to keep proper commit history.
I simply made it to show you before i push :-)

@rustatian
Copy link
Member

Yeah 👍
If you don't mind, I'd jump into your PR and correct some things 😃

@sicet7
Copy link
Contributor Author

sicet7 commented Nov 19, 2024

Yeah 👍 If you don't mind, I'd jump into your PR and correct some things 😃

I don't mind at all, go right ahead :-)

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
kv/tlsconfig.go (3)

3-9: Consider organizing imports

While functionally correct, consider organizing imports into groups: standard library, external packages, and internal packages.

import (
	"crypto/tls"
	"crypto/x509"
	"os"

	"github.com/roadrunner-server/errors"
	"go.uber.org/zap"
)
🧰 Tools
🪛 golangci-lint

5-5: File is not goimports-ed

(goimports)


28-30: Improve error handling specificity

The error handling could be more specific to help users identify configuration issues.

-	if _, crtExistErr := os.Stat(c.RootCa); crtExistErr != nil {
-		return nil, crtExistErr
+	if _, crtExistErr := os.Stat(c.CAFile); crtExistErr != nil {
+		return nil, errors.Errorf("CA file not accessible: %v", crtExistErr)
 	}

-	bytes, crtReadErr := os.ReadFile(c.RootCa)
+	bytes, crtReadErr := os.ReadFile(c.CAFile)
 	if crtReadErr != nil {
-		return nil, crtReadErr
+		return nil, errors.Errorf("failed to read CA file: %v", crtReadErr)
 	}

Also applies to: 32-35


37-39: Add logging for certificate parsing

Consider adding debug logging to help troubleshoot certificate issues.

 	if !rootCAs.AppendCertsFromPEM(bytes) {
+		log.Debug("failed to parse certificates", zap.String("file", c.CAFile))
 		return nil, errors.Errorf("failed to parse certificates from PEM file '%s'. Please ensure the file contains valid PEM-encoded certificates", c.RootCa)
 	}
+	log.Debug("successfully loaded certificates", zap.String("file", c.CAFile))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f656464 and 4b64f14.

📒 Files selected for processing (3)
  • kv/config.go (1 hunks)
  • kv/driver.go (2 hunks)
  • kv/tlsconfig.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • kv/config.go
  • kv/driver.go
🧰 Additional context used
🪛 golangci-lint
kv/tlsconfig.go

5-5: File is not goimports-ed

(goimports)

kv/tlsconfig.go Outdated Show resolved Hide resolved
kv/tlsconfig.go Outdated Show resolved Hide resolved
kv/tlsconfig.go Outdated Show resolved Hide resolved
kv/tlsconfig.go Outdated Show resolved Hide resolved
@rustatian
Copy link
Member

A few updates: d90dce6

Copy link

@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

🧹 Outside diff range and nitpick comments (1)
kv/tlsconfig.go (1)

1-131: Consider architectural improvements

To enhance the robustness and maintainability of the TLS implementation:

  1. Add comprehensive test coverage:

    • Unit tests for TLS configuration parsing
    • Integration tests with Redis TLS connections
    • Test cases for various certificate scenarios
  2. Implement proper configuration validation:

    • Validate certificate file paths
    • Validate TLS version values
    • Add configuration examples in documentation
  3. Security considerations:

    • Document security implications of TLS settings
    • Add logging for TLS-related events
    • Consider adding certificate expiration monitoring

Would you like assistance in implementing any of these improvements?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b64f14 and 6f6e25f.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod (1 hunks)
  • kv/driver.go (2 hunks)
  • kv/tlsconfig.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • kv/driver.go
🔇 Additional comments (2)
go.mod (1)

15-15: LGTM! Verify TLS version compatibility.

The addition of golang.org/x/sys as a direct dependency is appropriate for implementing TLS support. This version should support all required TLS functionality.

Since there was a recommendation in the PR comments to set minimum TLS version to 1.2 (as 1.0 and 1.1 are deprecated by Microsoft), please verify that this version of the sys package fully supports TLS 1.2 and above.

kv/tlsconfig.go (1)

117-131: Verify requirement for client certificate support

The client certificate functionality wasn't mentioned in the PR objectives. While the implementation is correct, please clarify:

  1. Is client certificate support a requirement for Azure Cache for Redis?
  2. If not required, consider removing this functionality to reduce attack surface
  3. If required, ensure proper security review of certificate reloading mechanism

kv/tlsconfig.go Show resolved Hide resolved
Comment on lines +11 to +15
type TLSConfig struct {
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
RootCa string `mapstructure:"root_ca"`
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Align struct with PR objectives and add documentation

The TLSConfig struct needs to be updated to match the PR objectives and documentation requirements:

  1. Field names should match configuration keys
  2. Missing min_version field
  3. Missing documentation for struct and fields
  4. Unexplained addition of cert and key fields
+// TLSConfig contains TLS/SSL configuration for Redis connections
 type TLSConfig struct {
-    Cert   string `mapstructure:"cert"`
-    Key    string `mapstructure:"key"`
-    RootCa string `mapstructure:"root_ca"`
+    // MinVersion specifies minimum TLS version (1.0, 1.1, 1.2, 1.3)
+    MinVersion string `mapstructure:"min_version"`
+    // CAFile is the path to PEM file containing CA certificates
+    CAFile     string `mapstructure:"ca_file"`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type TLSConfig struct {
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
RootCa string `mapstructure:"root_ca"`
}
// TLSConfig contains TLS/SSL configuration for Redis connections
type TLSConfig struct {
// MinVersion specifies minimum TLS version (1.0, 1.1, 1.2, 1.3)
MinVersion string `mapstructure:"min_version"`
// CAFile is the path to PEM file containing CA certificates
CAFile string `mapstructure:"ca_file"`
}

Comment on lines +17 to +53
func tlsConfig(conf *TLSConfig) (*tls.Config, error) {
if conf == nil {
return nil, nil
}

tlsConfig := defaultTLSConfig(conf)
if conf.RootCa != "" {
// error is always nil here
certPool, err := x509.SystemCertPool()
if err != nil {
// error is always nil here
return nil, err
}

if certPool == nil {
certPool = x509.NewCertPool()
}

// we already checked this file in the config.go
rca, err := os.ReadFile(conf.RootCa)
if err != nil {
return nil, err
}

if ok := certPool.AppendCertsFromPEM(rca); !ok {
return nil, err
}

tlsConfig.RootCAs = certPool
}

if _, crtExistErr := os.Stat(conf.RootCa); crtExistErr != nil {
return nil, crtExistErr
}

return tlsConfig, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix critical issues in TLS configuration logic

Several critical issues need to be addressed:

  1. Certificate existence check is performed after reading the file
  2. Undefined error variable used in return statement
  3. Missing TLS version configuration support
  4. Unnecessary error comments
 func tlsConfig(conf *TLSConfig) (*tls.Config, error) {
     if conf == nil {
         return nil, nil
     }
 
+    // Check certificate existence first
+    if conf.CAFile != "" {
+        if _, err := os.Stat(conf.CAFile); err != nil {
+            return nil, fmt.Errorf("ca_file error: %w", err)
+        }
+    }
+
     tlsConfig := defaultTLSConfig(conf)
-    if conf.RootCa != "" {
-        // error is always nil here
+
+    // Configure TLS version
+    if conf.MinVersion != "" {
+        version, err := parseTLSVersion(conf.MinVersion)
+        if err != nil {
+            return nil, err
+        }
+        tlsConfig.MinVersion = version
+    }
+
+    if conf.CAFile != "" {
         certPool, err := x509.SystemCertPool()
         if err != nil {
-            // error is always nil here
             return nil, err
         }
 
@@ -31,12 +31,11 @@
             certPool = x509.NewCertPool()
         }
 
-        // we already checked this file in the config.go
-        rca, err := os.ReadFile(conf.RootCa)
+        rca, err := os.ReadFile(conf.CAFile)
         if err != nil {
             return nil, err
         }
 
         if ok := certPool.AppendCertsFromPEM(rca); !ok {
-            return nil, err
+            return nil, fmt.Errorf("failed to append CA certificate")
         }
 
         tlsConfig.RootCAs = certPool
     }
 
-    if _, crtExistErr := os.Stat(conf.RootCa); crtExistErr != nil {
-        return nil, crtExistErr
-    }
-
     return tlsConfig, nil
 }

Committable suggestion skipped: line range outside the PR's diff.

@rustatian
Copy link
Member

Have you tried testing this PR on your Azure Cache for Redis instance?

@sicet7
Copy link
Contributor Author

sicet7 commented Nov 20, 2024

ahh...
I pushed to the wrong branch 😅
But no,
currently it is not working, but i just started debugging i get the following error:

app-1            | panic: runtime error: invalid memory address or nil pointer dereference [recovered]
app-1            |      panic: runtime error: invalid memory address or nil pointer dereference [recovered]
app-1            |      panic: runtime error: invalid memory address or nil pointer dereference
app-1            | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x68254d]
app-1            |
app-1            | goroutine 136 [running]:
app-1            | go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End.deferwrap1()
app-1            |      go.opentelemetry.io/otel/[email protected]/trace/span.go:422 +0x25
app-1            | go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End(0xc0007fe000, {0x0, 0x0, 0xc00079ac40?})
app-1            |      go.opentelemetry.io/otel/[email protected]/trace/span.go:455 +0xaf4
app-1            | panic({0x19dcf20?, 0x2f536d0?})
app-1            |      runtime/panic.go:785 +0x132
app-1            | go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End.deferwrap1()
app-1            |      go.opentelemetry.io/otel/[email protected]/trace/span.go:422 +0x25
app-1            | go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End(0xc0007fe1e0, {0x0, 0x0, 0x0?})
app-1            |      go.opentelemetry.io/otel/[email protected]/trace/span.go:455 +0xaf4
app-1            | panic({0x19dcf20?, 0x2f536d0?})
app-1            |      runtime/panic.go:785 +0x132
app-1            | crypto/tls.(*clientHandshakeStateTLS13).sendClientCertificate(0xc0007f8578)
app-1            |      crypto/tls/handshake_client_tls13.go:787 +0x16d
app-1            | crypto/tls.(*clientHandshakeStateTLS13).handshake(0xc0007f8578)
app-1            |      crypto/tls/handshake_client_tls13.go:148 +0x785
app-1            | crypto/tls.(*Conn).clientHandshake(0xc000532388, {0x205bfd0, 0xc000f8a4b0})
app-1            |      crypto/tls/handshake_client.go:366 +0x845
app-1            | crypto/tls.(*Conn).handshakeContext(0xc000532388, {0x205c040, 0xc0009a4620})
app-1            |      crypto/tls/conn.go:1568 +0x3a6
app-1            | crypto/tls.(*Conn).HandshakeContext(...)
app-1            |      crypto/tls/conn.go:1508
app-1            | crypto/tls.dial({0x205bf28?, 0x2fc8020?}, 0xc000a389e8, {0x1c96b58, 0x3}, {0xc000046ff0, 0x2d}, 0xc000924540)
app-1            |      crypto/tls/tls.go:159 +0x3a5
app-1            | crypto/tls.DialWithDialer(0xc000a38a18?, {0x1c96b58?, 0x203ca98?}, {0xc000046ff0?, 0xc00091a060?}, 0x0?)
app-1            |      crypto/tls/tls.go:119 +0x3d
app-1            | github.com/redis/go-redis/v9.(*Options).init.NewDialer.func1({0x205bf98?, 0xc000923c80?}, {0x1c96b58?, 0x0?}, {0xc000046ff0?, 0x0?})
app-1            |      github.com/redis/go-redis/[email protected]/options.go:243 +0x9e
app-1            | github.com/redis/go-redis/v9.(*baseClient).dial(...)
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:397
app-1            | github.com/redis/go-redis/extra/redisotel/v9.(*tracingHook).DialHook.func1({0x205bf98?, 0xc000923c50}, {0x1c96b58, 0x3}, {0xc000046ff0, 0x2d})
app-1            |      github.com/redis/go-redis/extra/redisotel/[email protected]/tracing.go:97 +0x124
app-1            | github.com/redis/go-redis/extra/redisotel/v9.(*metricsHook).DialHook.func1({0x205bf98, 0xc000923c50}, {0x1c96b58, 0x3}, {0xc000046ff0, 0x2d})
app-1            |      github.com/redis/go-redis/extra/redisotel/[email protected]/metrics.go:194 +0xc3
app-1            | github.com/redis/go-redis/v9.(*hooksMixin).dialHook(0x0?, {0x205bf98?, 0xc000923c50?}, {0x1c96b58?, 0x45b01f?}, {0xc000046ff0?, 0xc000a38e18?})
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:181 +0x119
app-1            | github.com/redis/go-redis/v9.newConnPool.func1({0x205bf98?, 0xc000923c50?})
app-1            |      github.com/redis/go-redis/[email protected]/options.go:516 +0x35
app-1            | github.com/redis/go-redis/v9/internal/pool.(*ConnPool).dialConn(0xc000a00510, {0x205bf98?, 0xc000923c50?}, 0x1)
app-1            |      github.com/redis/go-redis/[email protected]/internal/pool/pool.go:213 +0xbc
app-1            | github.com/redis/go-redis/v9/internal/pool.(*ConnPool).newConn(0xc000a00510, {0x205bf98, 0xc000923c50}, 0x1)
app-1            |      github.com/redis/go-redis/[email protected]/internal/pool/pool.go:178 +0x178
app-1            | github.com/redis/go-redis/v9/internal/pool.(*ConnPool).Get(0xc000a00510, {0x205bf98, 0xc000923c50})
app-1            |      github.com/redis/go-redis/[email protected]/internal/pool/pool.go:293 +0x167
app-1            | github.com/redis/go-redis/v9.(*baseClient)._getConn(0xc00100e580, {0x205bf98, 0xc000923c50})
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:260 +0x32
app-1            | github.com/redis/go-redis/v9.(*baseClient).getConn(0xc00100e580, {0x205bf98?, 0xc000923c50?})
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:248 +0x65
app-1            | github.com/redis/go-redis/v9.(*baseClient).withConn(0xc00100e580, {0x205bf98, 0xc000923c50}, 0xc0007f9088)
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:381 +0x49
app-1            | github.com/redis/go-redis/v9.(*baseClient)._process(0xc00100e580, {0x205bf98, 0xc000923c50}, {0x206d3c0, 0xc0009a45b0}, 0xc000538738?)
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:436 +0xd0
app-1            | github.com/redis/go-redis/v9.(*baseClient).process(0xc00100e580, {0x205bf98, 0xc000923c50}, {0x206d3c0, 0xc0009a45b0})
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:405 +0x76
app-1            | github.com/redis/go-redis/extra/redisotel/v9.(*tracingHook).ProcessHook.func1({0x205bf98, 0xc000923bf0}, {0x206d3c0, 0xc0009a45b0})
app-1            |      github.com/redis/go-redis/extra/redisotel/[email protected]/tracing.go:128 +0x81e
app-1            | github.com/redis/go-redis/extra/redisotel/v9.(*metricsHook).ProcessHook.func1({0x205bf98, 0xc000923bf0}, {0x206d3c0, 0xc0009a45b0})
app-1            |      github.com/redis/go-redis/extra/redisotel/[email protected]/metrics.go:211 +0x9f
app-1            | github.com/redis/go-redis/v9.(*hooksMixin).processHook(...)
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:185
app-1            | github.com/redis/go-redis/v9.(*Client).Process(0xc000a39528?, {0x205bf98?, 0xc000923bf0?}, {0x206d3c0, 0xc0009a45b0})
app-1            |      github.com/redis/go-redis/[email protected]/redis.go:697 +0x38
app-1            | github.com/redis/go-redis/v9.cmdable.Get(0xc0010156e0, {0x205bf98, 0xc000923bf0}, {0xc000f8e620, 0x4})
app-1            |      github.com/redis/go-redis/[email protected]/string_commands.go:54 +0x10c
app-1            | github.com/roadrunner-server/redis/v5/kv.(*Driver).MGet(0xc0009236e0, {0xc0009a2b80, 0x1, 0x5?})
app-1            |      github.com/roadrunner-server/redis/[email protected]/kv/driver.go:169 +0x403
app-1            | github.com/roadrunner-server/kv/v5.(*rpc).MGet(0xc0008f11e0, 0xc000518af0, 0xc000d3a9c0)
app-1            |      github.com/roadrunner-server/kv/[email protected]/rpc.go:109 +0x5f0
app-1            | reflect.Value.call({0xc000926480?, 0xc0004fbf50?, 0x13?}, {0x1c9745d, 0x4}, {0xc000957ef8, 0x3, 0x3?})
app-1            |      reflect/value.go:581 +0xca6
app-1            | reflect.Value.Call({0xc000926480?, 0xc0004fbf50?, 0xc00097a6a0?}, {0xc00097a6f8?, 0x10?, 0x1aceea0?})
app-1            |      reflect/value.go:365 +0xb9
app-1            | net/rpc.(*service).call(0xc0008fe580, 0xc0008fe380, 0xc000f8e508, 0xc000f8e560, 0xc000928700, 0xc00100e7c0, {0x1b27a00?, 0xc000518af0?, 0x205c4e8?}, {0x1addba0, ...}, ...)
app-1            |      net/rpc/server.go:381 +0x209
app-1            | created by net/rpc.(*Server).ServeCodec in goroutine 135
app-1            |      net/rpc/server.go:478 +0x3d7

@rustatian
Copy link
Member

Ah, you may try to temporarily comment GetClientCertificate TLSConfig option. It should be nil if not provided any cert and key.

@sicet7
Copy link
Contributor Author

sicet7 commented Nov 20, 2024

I made it work with these changes:
https://github.com/ASG-Digital/rr-go-plugin-redis/commit/cde1077a4e8cc6041824b4b390a61cd69f6cd03a
But i have no idea if that is the correct way of doing it 😅

Signed-off-by: Valery Piashchynski <[email protected]>
Signed-off-by: Martin René Sørensen <[email protected]>
@rustatian
Copy link
Member

Yeah, this is a correct change. Because TLSConfig should receive nil function (the same as commenting out that option or just not providing it) instead of receiving the nil certificate on non-nil function 👍

@sicet7
Copy link
Contributor Author

sicet7 commented Nov 20, 2024

I have cherry picked it in now, i tried to force push with a new cherry pick for your commit to see if i could get it to not complain about the commit being "Unverified" 😅

@rustatian
Copy link
Member

Not a problem 😃
It would be nice if you'd be able to make a PR into docs roadrunner-server/docs repository with the updated configuration and new functionality.
And at the same time, I'll try to push another change, to test this new functionality in the CI.

@sicet7
Copy link
Contributor Author

sicet7 commented Nov 20, 2024

Will do, is there any perticular branch that i should make the merge towards on the roadrunner-server/docs repo?
Or is master fine?

@rustatian
Copy link
Member

Sorry for the late response, yeah, master is ok, thank you 👍

@rustatian rustatian merged commit 211c70a into roadrunner-server:master Nov 20, 2024
1 check passed
@rustatian
Copy link
Member

Thank you @sicet7 👍
I'll create anouther PR with tests update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

2 participants