Skip to content

Commit 2b0c88e

Browse files
committed
TLSOwnCerts fix for path concatenation
1 parent f5e0fb0 commit 2b0c88e

File tree

6 files changed

+99
-41
lines changed

6 files changed

+99
-41
lines changed

client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ func TestClients(t *testing.T) {
927927
h2cClient := NewH2CClient()
928928

929929
wg.Wait()
930-
time.Sleep(10 * time.Millisecond) // wg does not ensure that servers are ready, only that the listening may be started.
930+
time.Sleep(100 * time.Millisecond) // wg does not ensure that servers are ready, only that the listening may be started.
931931

932932
tests := []struct {
933933
name string

client_tls.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,20 @@ func (c *Client) TLSRootCerts(path string, loadSystemCerts bool) *Client {
153153
return c
154154
}
155155

156+
// TLSOwnCertOpts allows specifying custom file paths for client certificate and private key.
156157
type TLSOwnCertOpts struct {
157158
Certificate string
158159
PrivateKey string
159160
}
160161

162+
func catDirFile(dir, file string) string {
163+
dirEndsWithSlash := strings.HasSuffix(dir, "/")
164+
if dir != "" && !dirEndsWithSlash && !strings.HasPrefix(file, "/") {
165+
return dir + "/" + file
166+
}
167+
return dir + file
168+
}
169+
161170
// TLSOwnCerts loads PEM certificate + key from given directory and sets TLS config accordingly.
162171
// That allows the client to authenticate itself using mutual TLS (mTLS).
163172
//
@@ -178,15 +187,14 @@ func (c *Client) TLSOwnCerts(dir string, opts ...TLSOwnCertOpts) *Client {
178187
if opts[0].Certificate == "" {
179188
opts[0].Certificate = "tls.crt"
180189
}
181-
certificateFile = dir + opts[0].Certificate
182-
190+
certificateFile = catDirFile(dir, opts[0].Certificate)
183191
if opts[0].PrivateKey == "" {
184192
opts[0].PrivateKey = "tls.key"
185193
}
186-
privateKeyFile = dir + opts[0].PrivateKey
194+
privateKeyFile = catDirFile(dir, opts[0].PrivateKey)
187195
} else {
188-
certificateFile = dir + "/tls.crt"
189-
privateKeyFile = dir + "/tls.key"
196+
certificateFile = catDirFile(dir, "tls.crt")
197+
privateKeyFile = catDirFile(dir, "tls.key")
190198
}
191199

192200
cert, err := tls.LoadX509KeyPair(certificateFile, privateKeyFile)

client_tls_test.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ func TestHTTPSMTLS(t *testing.T) {
5353
srv.URL = strings.ReplaceAll(srv.URL, "127.0.0.1", "localhost")
5454
defer srv.Close()
5555

56-
assert.NoError(NewClient().Root(srv.URL).TLSRootCerts("test_certs", false).TLSOwnCerts("test_certs").Get(context.Background(), "/NEF", nil)) // Own cert set
57-
assert.Error(NewClient().Root(srv.URL).TLSRootCerts("test_certs", false).Get(context.Background(), "/NEF", nil)) // Own cert not set
56+
assert.NoError(NewClient().Root(srv.URL).TLSRootCerts("test_certs", false).TLSOwnCerts("test_certs").Get(context.Background(), "/NEF", nil)) // Own cert set
57+
assert.NoError(NewClient().Root(srv.URL).TLSRootCerts("test_certs", false).TLSOwnCerts("test_certs", TLSOwnCertOpts{Certificate: "tls.crt", PrivateKey: "tls.key"}).Get(context.Background(), "/NEF", nil)) // Own cert set
58+
assert.Error(NewClient().Root(srv.URL).TLSRootCerts("test_certs", false).Get(context.Background(), "/NEF", nil)) // Own cert not set
5859
}
5960

6061
func copyFile(src, dst string, t *testing.T) {
@@ -210,3 +211,50 @@ func TestAppendCert(t *testing.T) {
210211
appendCert("kutyafüle", nil)
211212
appendCert("client_tls_test.go", nil)
212213
}
214+
215+
func TestCatDirFile(t *testing.T) {
216+
tests := []struct {
217+
name string
218+
dir string
219+
file string
220+
expected string
221+
}{
222+
{
223+
name: "dir without slash, file without slash",
224+
dir: "/etc/certs",
225+
file: "tls.crt",
226+
expected: "/etc/certs/tls.crt",
227+
},
228+
{
229+
name: "dir with slash, file without slash",
230+
dir: "/etc/certs/",
231+
file: "tls.crt",
232+
expected: "/etc/certs/tls.crt",
233+
},
234+
{
235+
name: "dir without slash, file with slash",
236+
dir: "/etc/certs",
237+
file: "/tls.crt",
238+
expected: "/etc/certs/tls.crt",
239+
},
240+
{
241+
name: "dir with slash, file with slash (sad)",
242+
dir: "/etc/certs/",
243+
file: "/tls.crt",
244+
expected: "/etc/certs//tls.crt",
245+
},
246+
{
247+
name: "empty dir (relative)",
248+
dir: "",
249+
file: "tls.crt",
250+
expected: "tls.crt",
251+
},
252+
}
253+
254+
for _, tt := range tests {
255+
t.Run(tt.name, func(t *testing.T) {
256+
result := catDirFile(tt.dir, tt.file)
257+
assert.Equal(t, tt.expected, result)
258+
})
259+
}
260+
}

doc/client.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,12 @@ client := restful.NewClient().TLSRootCerts("/etc/cacerts", false)
8787

8888
### Mutual TLS
8989

90-
Mutual TLS is an advanced security solution for private networks. The server authenticates the client, too.
90+
When Mutual TLS is used, the server authenticates the client, too.
91+
Most of the time the client certificate is signed by a private CA that is known to the server.
9192

9293
Load CA certs and load own `tls.key` and `tls.crt` files.
9394
File naming follows Kubernetes TLS secret solution, e.g., used at `kubectl create secret tls` command.
95+
You may use further options to define file directories and names.
9496

9597
```go
9698
client := restful.NewClient()
@@ -100,7 +102,7 @@ client.TLSOwnCerts("/etc/own_tls")
100102

101103
❗ Note that once the client loaded the key + cert, it is in the memory.
102104
Any update (e.g., cert-manager.io) will not affect that client.
103-
You may restart your app, or in the cloud, you may issue `kubectl rollout restart deploy/xxx`.
105+
You may restart your app, e.g. issue `kubectl rollout restart deploy xxx` when using K8s.
104106

105107
## MessagePack
106108

go.mod

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/nokia/restful
22

3-
go 1.25.2
3+
go 1.25.5
44

55
require (
66
github.com/go-playground/validator/v10 v10.28.0
@@ -15,15 +15,15 @@ require (
1515
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
1616
go.opentelemetry.io/otel/sdk v1.38.0
1717
go.opentelemetry.io/otel/trace v1.38.0
18-
golang.org/x/net v0.46.0
19-
golang.org/x/oauth2 v0.32.0
18+
golang.org/x/net v0.47.0
19+
golang.org/x/oauth2 v0.33.0
2020
)
2121

2222
require (
2323
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
2424
github.com/davecgh/go-spew v1.1.1 // indirect
2525
github.com/felixge/httpsnoop v1.0.4 // indirect
26-
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
26+
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
2727
github.com/go-logr/logr v1.4.3 // indirect
2828
github.com/go-logr/stdr v1.2.2 // indirect
2929
github.com/go-playground/locales v0.14.1 // indirect
@@ -36,13 +36,13 @@ require (
3636
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
3737
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
3838
go.opentelemetry.io/otel/metric v1.38.0 // indirect
39-
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
40-
golang.org/x/crypto v0.43.0 // indirect
41-
golang.org/x/sys v0.37.0 // indirect
42-
golang.org/x/text v0.30.0 // indirect
43-
google.golang.org/genproto/googleapis/api v0.0.0-20251020155222-88f65dc88635 // indirect
44-
google.golang.org/genproto/googleapis/rpc v0.0.0-20251020155222-88f65dc88635 // indirect
45-
google.golang.org/grpc v1.76.0 // indirect
39+
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
40+
golang.org/x/crypto v0.45.0 // indirect
41+
golang.org/x/sys v0.38.0 // indirect
42+
golang.org/x/text v0.31.0 // indirect
43+
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
44+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
45+
google.golang.org/grpc v1.77.0 // indirect
4646
google.golang.org/protobuf v1.36.10 // indirect
4747
gopkg.in/yaml.v3 v3.0.1 // indirect
4848
)

go.sum

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
66
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
77
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
8-
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
9-
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
8+
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
9+
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
1010
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
1111
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
1212
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -72,29 +72,29 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6
7272
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
7373
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
7474
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
75-
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
76-
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
75+
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
76+
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
7777
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
7878
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
79-
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
80-
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
81-
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
82-
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
83-
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
84-
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
79+
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
80+
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
81+
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
82+
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
83+
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
84+
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
8585
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
86-
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
87-
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
88-
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
89-
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
86+
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
87+
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
88+
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
89+
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
9090
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
9191
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
92-
google.golang.org/genproto/googleapis/api v0.0.0-20251020155222-88f65dc88635 h1:1wvBeYv+A2zfEbxROscJl69OP0m74S8wGEO+Syat26o=
93-
google.golang.org/genproto/googleapis/api v0.0.0-20251020155222-88f65dc88635/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
94-
google.golang.org/genproto/googleapis/rpc v0.0.0-20251020155222-88f65dc88635 h1:3uycTxukehWrxH4HtPRtn1PDABTU331ViDjyqrUbaog=
95-
google.golang.org/genproto/googleapis/rpc v0.0.0-20251020155222-88f65dc88635/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
96-
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
97-
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
92+
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
93+
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
94+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
95+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
96+
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
97+
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
9898
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
9999
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
100100
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)