Skip to content

Commit

Permalink
add TLS integration test, update Client.DialContext docs
Browse files Browse the repository at this point in the history
Turns out we already have the necessary hook (DialContext), which was
added in #158.

Updates #1212

Signed-off-by: Brad Fitzpatrick <brad@danga.com>
  • Loading branch information
bradfitz committed Sep 5, 2023
1 parent 21f1bbe commit 2d43463
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 2 deletions.
62 changes: 62 additions & 0 deletions memcache/fakecerts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package memcache

import "strings"

// Copied from Go's net/http/internal/testcert package.

// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
// generated from src/crypto/tls:
// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
+tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
WkBKOclmOV2xlTVuPw==
-----END CERTIFICATE-----`)

// LocalhostKey is the private key for LocalhostCert.
var LocalhostKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoZtrm0dXV0Aqi
4Bpc7f95sNRTiu/AJSD8I1onY9PnEsPg3VVxvytsVJbYdcqr4w99V3AgpH/UNzMS
gAZ/8lZBNbsSDOVesJ3euVqMRfYPvd9pYl6QPRRpSDPm+2tNdn3QFAvta9EgJ3sW
URnoU85w+W6aLI2bNSq3AaE771p3VbkGolpEjo9h+i42TBHo1rhPNKPkGupR8/QX
AOLMpInRdeaHyDwb2a3DE5I3dG7VAVzrVfJ6W6Q84YoFX+rpEE2SVM17SAjy6xQy
VjKgLvK2mk0xbtfa+h0B6VK7bmODHZqeP18NVm6HsBcXn7iclLgAC3SfWU1jucZK
x1lqzw9tAgMBAAECggEABWzxS1Y2wckblnXY57Z+sl6YdmLV+gxj2r8Qib7g4ZIk
lIlWR1OJNfw7kU4eryib4fc6nOh6O4AWZyYqAK6tqNQSS/eVG0LQTLTTEldHyVJL
dvBe+MsUQOj4nTndZW+QvFzbcm2D8lY5n2nBSxU5ypVoKZ1EqQzytFcLZpTN7d89
EPj0qDyrV4NZlWAwL1AygCwnlwhMQjXEalVF1ylXwU3QzyZ/6MgvF6d3SSUlh+sq
XefuyigXw484cQQgbzopv6niMOmGP3of+yV4JQqUSb3IDmmT68XjGd2Dkxl4iPki
6ZwXf3CCi+c+i/zVEcufgZ3SLf8D99kUGE7v7fZ6AQKBgQD1ZX3RAla9hIhxCf+O
3D+I1j2LMrdjAh0ZKKqwMR4JnHX3mjQI6LwqIctPWTU8wYFECSh9klEclSdCa64s
uI/GNpcqPXejd0cAAdqHEEeG5sHMDt0oFSurL4lyud0GtZvwlzLuwEweuDtvT9cJ
Wfvl86uyO36IW8JdvUprYDctrQKBgQDycZ697qutBieZlGkHpnYWUAeImVA878sJ
w44NuXHvMxBPz+lbJGAg8Cn8fcxNAPqHIraK+kx3po8cZGQywKHUWsxi23ozHoxo
+bGqeQb9U661TnfdDspIXia+xilZt3mm5BPzOUuRqlh4Y9SOBpSWRmEhyw76w4ZP
OPxjWYAgwQKBgA/FehSYxeJgRjSdo+MWnK66tjHgDJE8bYpUZsP0JC4R9DL5oiaA
brd2fI6Y+SbyeNBallObt8LSgzdtnEAbjIH8uDJqyOmknNePRvAvR6mP4xyuR+Bv
m+Lgp0DMWTw5J9CKpydZDItc49T/mJ5tPhdFVd+am0NAQnmr1MCZ6nHxAoGABS3Y
LkaC9FdFUUqSU8+Chkd/YbOkuyiENdkvl6t2e52jo5DVc1T7mLiIrRQi4SI8N9bN
/3oJWCT+uaSLX2ouCtNFunblzWHBrhxnZzTeqVq4SLc8aESAnbslKL4i8/+vYZlN
s8xtiNcSvL+lMsOBORSXzpj/4Ot8WwTkn1qyGgECgYBKNTypzAHeLE6yVadFp3nQ
Ckq9yzvP/ib05rvgbvrne00YeOxqJ9gtTrzgh7koqJyX1L4NwdkEza4ilDWpucn0
xiUZS4SoaJq6ZvcBYS62Yr1t8n09iG47YL8ibgtmH3L+svaotvpVxVK+d7BLevA/
ZboOWVe3icTy64BT3OQhmg==
-----END RSA TESTING KEY-----`))

func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
16 changes: 16 additions & 0 deletions memcache/fakeserver_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
Copyright 2023 The gomemcache AUTHORS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package memcache

import (
Expand Down
8 changes: 6 additions & 2 deletions memcache/memcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,12 @@ func NewFromSelector(ss ServerSelector) *Client {
// Client is a memcache client.
// It is safe for unlocked use by multiple concurrent goroutines.
type Client struct {
// DialContext connects to the address on the named network
// using the provided context
// DialContext connects to the address on the named network using the
// provided context.
//
// To connect to servers using TLS (memcached running with "--enable-ssl"),
// use a DialContext func that uses tls.Dialer.DialContext. See this
// package's tests as an example.
DialContext func(ctx context.Context, network, address string) (net.Conn, error)

// Timeout specifies the socket read/write timeout.
Expand Down
79 changes: 79 additions & 0 deletions memcache/memcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,25 @@ package memcache

import (
"bufio"
"bytes"
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
)

var debug = flag.Bool("debug", false, "be more verbose")

const localhostTCPAddr = "localhost:11211"

func TestLocalhost(t *testing.T) {
Expand Down Expand Up @@ -81,6 +89,77 @@ func TestFakeServer(t *testing.T) {
testWithClient(t, New(ln.Addr().String()))
}

func TestTLS(t *testing.T) {
t.Parallel()
td := t.TempDir()

// Test whether our memcached binary has TLS support. We --enable-ssl first,
// before --version, as memcached evaluates the flags in the order provided
// and we want it to fail if it's built without TLS support (as it is in
// Debian, but not Ubuntu or Homebrew).
out, err := exec.Command("memcached", "--enable-ssl", "--version").CombinedOutput()
if err != nil {
t.Skipf("skipping test; couldn't find memcached or no TLS support in binary: %v, %s", err, out)
}
t.Logf("version: %s", bytes.TrimSpace(out))

if err := os.WriteFile(filepath.Join(td, "/cert.pem"), LocalhostCert, 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(td, "/key.pem"), LocalhostKey, 0644); err != nil {
t.Fatal(err)
}

// Find some unused port. This is racy but we hope for the best and hope the kernel
// doesn't reassign our ephemeral port to somebody in the tiny race window.
ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
port := ln.Addr().(*net.TCPAddr).Port
ln.Close()

cmd := exec.Command("memcached",
"--port="+strconv.Itoa(port),
"--listen=127.0.0.1",
"--enable-ssl",
"-o", "ssl_chain_cert=cert.pem",
"-o", "ssl_key=key.pem")
cmd.Dir = td
if *debug {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start memcached: %v", err)
}
defer cmd.Wait()
defer cmd.Process.Kill()

// Wait a bit for the server to be running.
for i := 0; i < 10; i++ {
nc, err := net.Dial("tcp", "localhost:"+strconv.Itoa(port))
if err == nil {
t.Logf("localhost:%d is up.", port)
nc.Close()
break
}
t.Logf("waiting for localhost:%d to be up...", port)
time.Sleep(time.Duration(25*i) * time.Millisecond)
}

c := New(net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
c.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
var td tls.Dialer
td.Config = &tls.Config{
InsecureSkipVerify: true,
}
return td.DialContext(ctx, network, addr)

}
testWithClient(t, c)
}

func mustSetF(t *testing.T, c *Client) func(*Item) {
return func(it *Item) {
if err := c.Set(it); err != nil {
Expand Down

0 comments on commit 2d43463

Please sign in to comment.