Skip to content

Commit ce27c69

Browse files
authored
Integration Tests for TLS (#270)
* Add TLS Integration tests Signed-off-by: Adam Fowler <[email protected]> * Add CI for TLS integration tests Signed-off-by: Adam Fowler <[email protected]> * Set VALKEY_EXTRA_FLAGS env with extra parameters Signed-off-by: Adam Fowler <[email protected]> * Add database name Signed-off-by: Adam Fowler <[email protected]> * Move environment var setting Signed-off-by: Adam Fowler <[email protected]> * Add GH workspace to volume Signed-off-by: Adam Fowler <[email protected]> * Make key files readable by all Signed-off-by: Adam Fowler <[email protected]> * Re-instate all unit-tests Signed-off-by: Adam Fowler <[email protected]> * Fix ShellCheck error Signed-off-by: Adam Fowler <[email protected]> * Add extra help text to ensure users run generate-test-certs.sh Signed-off-by: Adam Fowler <[email protected]> * Add REDIS_EXTRA_FLAGS env Signed-off-by: Adam Fowler <[email protected]> * Disable TLS tests for Redis Signed-off-by: Adam Fowler <[email protected]> * Re-enable valkey Signed-off-by: Adam Fowler <[email protected]> --------- Signed-off-by: Adam Fowler <[email protected]>
1 parent 17c5c6f commit ce27c69

File tree

6 files changed

+225
-9
lines changed

6 files changed

+225
-9
lines changed

.devcontainer/docker-compose-cluster.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ services:
3030
valkey:
3131
image: 'valkey/valkey:latest'
3232
network_mode: "host"
33-
command: valkey-server --port 6379
33+
ports:
34+
- 6379:6379
35+
- 6380:6380
36+
volumes:
37+
- ./valkey:/valkey
38+
command: valkey-server --tls-port 6380 --tls-cert-file /valkey/certs/server.crt --tls-key-file /valkey/certs/server.key --tls-ca-cert-file /valkey/certs/ca.crt
3439

3540
valkey_cluster_1:
3641
image: 'valkey/valkey:latest'

.github/workflows/ci.yml

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ concurrency:
99
group: ${{ github.workflow }}-${{ github.ref }}-ci
1010
cancel-in-progress: true
1111

12-
env:
13-
VALKEY_HOSTNAME: database
14-
REDIS_HOSTNAME: database
1512
jobs:
1613
unit-tests:
1714
runs-on: ubuntu-latest
@@ -25,14 +22,42 @@ jobs:
2522
services:
2623
database:
2724
image: ${{ matrix.database }}
25+
options: --name database
26+
env:
27+
VALKEY_EXTRA_FLAGS: "--tls-port 6380 --tls-cert-file /valkey/certs/server.crt --tls-key-file /valkey/certs/server.key --tls-ca-cert-file /valkey/certs/ca.crt"
28+
ports:
29+
- 6379:6379
30+
- 6380:6380
31+
volumes:
32+
- ${{ github.workspace }}/valkey:/valkey
2833
steps:
2934
- name: Install jemalloc
3035
run: |
3136
apt-get update
3237
apt-get install -y libjemalloc-dev
38+
apt-get install curl
3339
- name: Checkout
34-
uses: actions/checkout@v4
35-
- name: Test
40+
uses: actions/checkout@v6
41+
- name: Generate certificates
42+
run: ./dev/generate-test-certs.sh
43+
- name: Restart Docker
44+
# The valkey service container is started *before* the certificates are
45+
# generated. Restarting the container after the generate step is needed for the
46+
# container to see the generated certificates.
47+
uses: docker://docker
48+
with:
49+
args: docker restart database
50+
- name: Test Redis
51+
if: ${{ matrix.database == 'redis:latest' }}
52+
env:
53+
DISABLE_TLS_TESTS: true
54+
VALKEY_HOSTNAME: database
55+
run: |
56+
swift test --enable-code-coverage
57+
- name: Test Valkey
58+
if: ${{ matrix.database != 'redis:latest' }}
59+
env:
60+
VALKEY_HOSTNAME: database
3661
run: |
3762
swift test --enable-code-coverage
3863
- name: Convert coverage files

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ Package.resolved
1212
.benchmarkBaselines/
1313
.swift-version
1414
.docc-build
15+
valkey
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// This source file is part of the valkey-swift project
3+
// Copyright (c) 2025 the valkey-swift project authors
4+
//
5+
// See LICENSE.txt for license information
6+
// SPDX-License-Identifier: Apache-2.0
7+
//
8+
import Foundation
9+
import Logging
10+
import NIOCore
11+
import NIOSSL
12+
import Testing
13+
import Valkey
14+
15+
@testable import Valkey
16+
17+
@Suite(
18+
"TLS Integration Tests",
19+
.disabled(if: disableTLSTests != nil)
20+
)
21+
struct TLSIntegratedTests {
22+
let valkeyHostname = ProcessInfo.processInfo.environment["VALKEY_HOSTNAME"] ?? "localhost"
23+
24+
@available(valkeySwift 1.0, *)
25+
func withKey<Value>(connection: some ValkeyClientProtocol, _ operation: (ValkeyKey) async throws -> Value) async throws -> Value {
26+
let key = ValkeyKey(UUID().uuidString)
27+
let value: Value
28+
do {
29+
value = try await operation(key)
30+
} catch {
31+
_ = try? await connection.del(keys: [key])
32+
throw error
33+
}
34+
_ = try await connection.del(keys: [key])
35+
return value
36+
}
37+
38+
@available(valkeySwift 1.0, *)
39+
func withValkeyClient(
40+
_ address: ValkeyServerAddress,
41+
configuration: ValkeyClientConfiguration = .init(),
42+
logger: Logger,
43+
operation: @escaping @Sendable (ValkeyClient) async throws -> Void
44+
) async throws {
45+
try await withThrowingTaskGroup(of: Void.self) { group in
46+
let client = ValkeyClient(address, configuration: configuration, logger: logger)
47+
group.addTask {
48+
await client.run()
49+
}
50+
group.addTask {
51+
try await operation(client)
52+
}
53+
try await group.next()
54+
group.cancelAll()
55+
}
56+
}
57+
58+
@Test
59+
@available(valkeySwift 1.0, *)
60+
func testSetGet() async throws {
61+
var logger = Logger(label: "Valkey")
62+
logger.logLevel = .trace
63+
let tlsConfiguration = try Self.getTLSConfiguration()
64+
try await withValkeyClient(
65+
.hostname(valkeyHostname, port: 6380),
66+
configuration: .init(tls: .enable(tlsConfiguration, tlsServerName: "Server-only")),
67+
logger: logger
68+
) { connection in
69+
try await withKey(connection: connection) { key in
70+
try await connection.set(key, value: "Hello")
71+
let response = try await connection.get(key).map { String(buffer: $0) }
72+
#expect(response == "Hello")
73+
let response2 = try await connection.get("sdf65fsdf").map { String(buffer: $0) }
74+
#expect(response2 == nil)
75+
}
76+
}
77+
}
78+
79+
static let rootPath = #filePath
80+
.split(separator: "/", omittingEmptySubsequences: false)
81+
.dropLast(3)
82+
.joined(separator: "/")
83+
84+
@available(valkeySwift 1.0, *)
85+
static func getTLSConfiguration() throws -> TLSConfiguration {
86+
do {
87+
let rootCertificate = try NIOSSLCertificate.fromPEMFile(Self.rootPath + "/valkey/certs/ca.crt")
88+
let certificate = try NIOSSLCertificate.fromPEMFile(Self.rootPath + "/valkey/certs/client.crt")
89+
let privateKey = try NIOSSLPrivateKey(file: Self.rootPath + "/valkey/certs/client.key", format: .pem)
90+
var tlsConfiguration = TLSConfiguration.makeClientConfiguration()
91+
tlsConfiguration.trustRoots = .certificates(rootCertificate)
92+
tlsConfiguration.certificateChain = certificate.map { .certificate($0) }
93+
tlsConfiguration.privateKey = .privateKey(privateKey)
94+
return tlsConfiguration
95+
} catch NIOSSLError.failedToLoadCertificate {
96+
fatalError("Run script ./dev/generate-test-certs.sh to generate test certificates and restart your valkey server.")
97+
} catch NIOSSLError.failedToLoadPrivateKey {
98+
fatalError("Run script ./dev/generate-test-certs.sh to generate test certificates and restart your valkey server.")
99+
}
100+
}
101+
}
102+
103+
private let disableTLSTests: String? = ProcessInfo.processInfo.environment["DISABLE_TLS_TESTS"]

dev/generate-test-certs.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/bin/bash
2+
##
3+
## This source file is part of the valkey-swift project
4+
## Copyright (c) 2025 the valkey-swift project authors
5+
##
6+
## See LICENSE.txt for license information
7+
## SPDX-License-Identifier: Apache-2.0
8+
##
9+
10+
# This is a copy of the gen-test-certs.sh script from https://github/valkey-io/valkey
11+
# BSD 3-Clause License
12+
#
13+
# Copyright (c) 2024-present, Valkey contributors
14+
# All rights reserved.
15+
#
16+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
17+
#
18+
# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
19+
# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
20+
# * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
21+
#
22+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23+
24+
# Generate some test certificates which are used by the regression test suite:
25+
#
26+
# valkey/certs/ca.{crt,key} Self signed CA certificate.
27+
# valkey/certs/client.{crt,key} A certificate restricted for SSL client usage.
28+
# valkey/certs/server.{crt,key} A certificate restricted for SSL server usage.
29+
# valkey/certs/valkey.dh DH Params file.
30+
31+
generate_cert() {
32+
local name=$1
33+
local cn="$2"
34+
local opts="$3"
35+
36+
local keyfile=valkey/certs/${name}.key
37+
local certfile=valkey/certs/${name}.crt
38+
39+
[ -f "$keyfile" ] || openssl genrsa -out "$keyfile" 2048
40+
# shellcheck disable=SC2086
41+
openssl req \
42+
-new -sha256 \
43+
-subj "/O=Valkey Test/CN=$cn" \
44+
-key "$keyfile" | \
45+
openssl x509 \
46+
-req -sha256 \
47+
-CA valkey/certs/ca.crt \
48+
-CAkey valkey/certs/ca.key \
49+
-CAserial valkey/certs/ca.txt \
50+
-CAcreateserial \
51+
-days 365 \
52+
$opts \
53+
-out "$certfile"
54+
# Set all users to read private keys files so CI works
55+
chmod a+r "$keyfile"
56+
}
57+
58+
mkdir -p valkey/certs
59+
[ -f valkey/certs/ca.key ] || openssl genrsa -out valkey/certs/ca.key 4096
60+
openssl req \
61+
-x509 -new -nodes -sha256 \
62+
-key valkey/certs/ca.key \
63+
-days 3650 \
64+
-subj '/O=Valkey Test/CN=Certificate Authority' \
65+
-out valkey/certs/ca.crt
66+
67+
cat > valkey/certs/openssl.cnf <<_END_
68+
[ server_cert ]
69+
keyUsage = digitalSignature, keyEncipherment
70+
nsCertType = server
71+
72+
[ client_cert ]
73+
keyUsage = digitalSignature, keyEncipherment
74+
nsCertType = client
75+
_END_
76+
77+
generate_cert server "Server-only" "-extfile valkey/certs/openssl.cnf -extensions server_cert"
78+
generate_cert client "Client-only" "-extfile valkey/certs/openssl.cnf -extensions client_cert"

docker-compose.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
# This runs a valkey server with both non-TLS and TLS endpoints.
2+
# You need to run the script .dev/generate-test-certs.sh to generate the certificates needed for TLS
3+
14
services:
25
valkey:
3-
image: valkey/valkey:8.0
6+
image: valkey/valkey:9.0
47
ports:
58
- 6379:6379
6-
healthcheck:
7-
test: ["CMD", "valkey-cli", "--raw", "incr", "ping"]
9+
- 6380:6380
810
volumes:
11+
- ./valkey:/valkey
912
- valkey_data:/data
13+
command: valkey-server --tls-port 6380 --tls-cert-file /valkey/certs/server.crt --tls-key-file /valkey/certs/server.key --tls-ca-cert-file /valkey/certs/ca.crt
1014

1115
volumes:
1216
valkey_data:

0 commit comments

Comments
 (0)