Files
f25343d281 chore(tests): Add comprehensive TLS tests and example (#3681)
* chore(tests): add tls tests

* fix: Use ShardPicker for keyless commands instead of random slot

- cmdSlot now returns -1 for keyless commands instead of RandomSlot()
- This allows cmdNodeWithShardPicker to use the configured ShardPicker
  (default: RoundRobinPicker) for keyless command routing
- Updated mapCmdsByNode to handle slot == -1 by using ShardPicker
- Updated test to verify round robin distribution works correctly

* chore(image): update clients-test-lib image

* chore(tests): enable tls user auth test

* chore(docker): use proper envs for test image

* chore(image): use latest build

* chore(image): use latest build

* fix(tests): use the correct root cert

* feat(tls): add example app

* chore(docker): address pr comments

* chore(tests): address pr comments

* chore(doc): add README.md in the example app

* fix(tests): do not skip tests

* fix(tests): skip only because of redis version

* Extracts default image name in a var (including osscluster-tls)

Signed-off-by: Elena Kolevska <[email protected]>

* fix(tlstests): remove redundent test

---------

Signed-off-by: Elena Kolevska <[email protected]>
Co-authored-by: Elena Kolevska <[email protected]>
2026-02-12 15:26:00 +02:00

117 lines
3.2 KiB
Go

// TLS Certificate Authentication Example
//
// This example demonstrates how to use TLS client certificates for
// automatic authentication with Redis 8.6+.
//
// When Redis is configured with `tls-auth-clients-user CN`, it uses
// the Common Name (CN) field from the client certificate as the username,
// eliminating the need for password-based authentication.
//
// Prerequisites:
// - Redis 8.6+ with TLS enabled
// - Redis configured with: tls-auth-clients-user CN
// - Client certificate with CN matching a Redis ACL user
// - The ACL user must exist
//
// To run with the go-redis test environment:
//
// docker compose --profile standalone up -d
// go run main.go
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"os"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
// Configuration - adjust paths as needed
certDir := "../../dockers/standalone/tls"
username := "testcertuser" // Must match CN in certificate and ACL user
tlsPort := "6666"
nonTLSPort := "6379"
// Step 1: First, ensure the ACL user exists (using non-TLS connection)
setupClient := redis.NewClient(&redis.Options{
Addr: "localhost:" + nonTLSPort,
})
defer setupClient.Close()
// Create the ACL user if it doesn't exist
err := setupClient.ACLSetUser(ctx,
username,
"on", // Enable the user
"nopass", // No password - will use cert auth
"~*", // Access all keys
"+@all", // All commands (adjust as needed)
).Err()
if err != nil {
log.Printf("Note: Could not create ACL user (may already exist): %v", err)
}
// Step 2: Load CA certificate
caCert, err := os.ReadFile(certDir + "/ca.crt")
if err != nil {
log.Fatalf("Failed to load CA certificate: %v", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Step 3: Load client certificate (CN must match the Redis ACL username)
clientCert, err := tls.LoadX509KeyPair(
certDir+"/"+username+".crt",
certDir+"/"+username+".key",
)
if err != nil {
log.Fatalf("Failed to load client certificate: %v", err)
}
// Step 4: Create TLS config
tlsConfig := &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{clientCert},
ServerName: "localhost",
InsecureSkipVerify: true, // Only for self-signed certs in testing
}
// Step 5: Connect to Redis with TLS - NO username/password needed!
client := redis.NewClient(&redis.Options{
Addr: "localhost:" + tlsPort,
TLSConfig: tlsConfig,
// Note: No Username or Password fields - auth happens via certificate
})
defer client.Close()
// Step 6: Verify authentication
whoami, err := client.ACLWhoAmI(ctx).Result()
if err != nil {
log.Fatalf("Failed to get current user: %v", err)
}
fmt.Printf("āœ… Authenticated as: %s (via TLS certificate CN)\n", whoami)
// Step 7: Test some commands
err = client.Set(ctx, "tls-auth-example", "hello from cert auth!", 0).Err()
if err != nil {
log.Fatalf("SET failed: %v", err)
}
val, err := client.Get(ctx, "tls-auth-example").Result()
if err != nil {
log.Fatalf("GET failed: %v", err)
}
fmt.Printf("āœ… SET/GET successful: %s\n", val)
// Cleanup
client.Del(ctx, "tls-auth-example")
fmt.Println("\nšŸŽ‰ TLS certificate authentication working!")
}