Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/run-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ runs:
- name: Set up Docker Compose environment with redis ${{ inputs.redis-version }}
run: |
make docker.start
sleep 5
shell: bash
- name: Run tests
env:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-redis-enterprise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ jobs:
REDIS_VERSION: "7.4"
run: |
go test \
-skip="^TestTLS" \
--ginkgo.skip-file="ring_test.go" \
--ginkgo.skip-file="sentinel_test.go" \
--ginkgo.skip-file="osscluster_test.go" \
--ginkgo.skip-file="pubsub_test.go" \
--ginkgo.skip-file="tls_test.go" \
--ginkgo.skip-file="tls_cluster_test.go" \
--ginkgo.label-filter='!NonRedisEnterprise'
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ tmp/*

# maintenanceNotifications upgrade documentation (temporary)
maintenanceNotifications/docs/

# Docker-generated files (TLS certificates, cluster data, etc.)
dockers/*/tls/
dockers/osscluster-tls/
42 changes: 37 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
---

x-default-image: &default-image ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:custom-21860421418-debian-amd64}

services:
redis:
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:custom-21860421418-debian-amd64}
image: *default-image
platform: linux/amd64
container_name: redis-standalone
environment:
- TLS_ENABLED=yes
- TLS_CLIENT_CNS=testcertuser
- TLS_AUTH_CLIENTS_USER=CN
- REDIS_CLUSTER=no
- PORT=6379
- TLS_PORT=6666
Expand All @@ -23,7 +27,7 @@ services:
- all

osscluster:
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:custom-21860421418-debian-amd64}
image: *default-image
platform: linux/amd64
container_name: redis-osscluster
environment:
Expand All @@ -39,14 +43,40 @@ services:
- all-stack
- all

osscluster-tls:
image: *default-image
platform: linux/amd64
container_name: redis-osscluster-tls
environment:
- NODES=6
- PORT=6430
- TLS_PORT=5430
- TLS_ENABLED=yes
- TLS_CLIENT_CNS=testcertuser
- TLS_AUTH_CLIENTS_USER=CN
Comment thread
ofekshenawa marked this conversation as resolved.
- REDIS_CLUSTER=yes
- REPLICAS=1
command: "--tls-auth-clients optional --cluster-announce-ip 127.0.0.1"
ports:
- "6430-6435:6430-6435" # Regular ports
- "5430-5435:5430-5435" # TLS ports (set via TLS_PORT env var)
- "16430-16435:16430-16435" # Cluster bus ports (PORT + 10000)
volumes:
- "./dockers/osscluster-tls:/redis/work"
profiles:
- cluster-tls
- all

sentinel-cluster:
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:custom-21860421418-debian-amd64}
image: *default-image
platform: linux/amd64
container_name: redis-sentinel-cluster
network_mode: "host"
environment:
- NODES=3
- TLS_ENABLED=yes
- TLS_CLIENT_CNS=testcertuser
- TLS_AUTH_CLIENTS_USER=CN
- REDIS_CLUSTER=no
- PORT=9121
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save ""}
Expand All @@ -60,7 +90,7 @@ services:
- all

sentinel:
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:custom-21860421418-debian-amd64}
image: *default-image
platform: linux/amd64
container_name: redis-sentinel
depends_on:
Expand All @@ -84,12 +114,14 @@ services:
- all

ring-cluster:
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:custom-21860421418-debian-amd64}
image: *default-image
platform: linux/amd64
container_name: redis-ring-cluster
environment:
- NODES=3
- TLS_ENABLED=yes
- TLS_CLIENT_CNS=testcertuser
- TLS_AUTH_CLIENTS_USER=CN
- REDIS_CLUSTER=no
- PORT=6390
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save ""}
Expand Down
77 changes: 77 additions & 0 deletions example/tls-cert-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 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 and have `nopass` set

## How It Works

1. **Load CA certificate** - Used to verify the Redis server's certificate
2. **Load client certificate** - The CN field must match a Redis ACL username
3. **Connect with TLS** - No username/password needed in the connection options
4. **Redis authenticates automatically** - Based on the certificate's CN field

## Running the Example

```bash
# Start Redis with TLS (from the go-redis root directory)
docker compose --profile standalone up -d

# Run the example
cd example/tls-cert-auth
go run main.go
Comment thread
elena-kolevska marked this conversation as resolved.
```

## Expected Output

```
✅ Authenticated as: testcertuser (via TLS certificate CN)
✅ SET/GET successful: hello from cert auth!

🎉 TLS certificate authentication working!
```

## Docker Configuration

The go-redis test environment is configured with these environment variables:

```yaml
environment:
- TLS_ENABLED=yes
- TLS_CLIENT_CNS=testcertuser # Generates testcertuser.{crt,key}
- TLS_AUTH_CLIENTS_USER=CN # Enables CN-based authentication
```

## Key Code

```go
// Load client certificate (CN must match Redis ACL username)
clientCert, err := tls.LoadX509KeyPair(
"testcertuser.crt",
"testcertuser.key",
)

// Create TLS config
tlsConfig := &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{clientCert},
}

// Connect - NO username/password needed!
client := redis.NewClient(&redis.Options{
Addr: "localhost:6666",
TLSConfig: tlsConfig,
})
```

## Fallback Behavior

If the certificate CN doesn't match any existing ACL user, Redis falls back to the `default` user. See `tls_cert_auth_test.go` for tests covering both scenarios.

13 changes: 13 additions & 0 deletions example/tls-cert-auth/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/redis/go-redis/example/tls-cert-auth

go 1.21

replace github.com/redis/go-redis/v9 => ../..

require github.com/redis/go-redis/v9 v9.18.0-beta.2

require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
go.uber.org/atomic v1.11.0 // indirect
)
20 changes: 20 additions & 0 deletions example/tls-cert-auth/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
116 changes: 116 additions & 0 deletions example/tls-cert-auth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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
Comment thread Dismissed
}

// 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!")
}
Loading
Loading