Skip to content

Commit 901e91e

Browse files
committed
BUG/MEDIUM: runtime: get the real storage name of certificates
When calling the runtime command "show ssl cert", the returned field "Filename" is not always the filename of the certificate. If you pass in an alias, "Filename" will be the alias, not the corresponding filename. So how can we obtain the true storage name of a certificate? Recent versions of HAProxy now return 2 new fields for that: "Crt filename" and "Key filename". This patch uses the "Crt filename" as the storage name when it is provided, and falls back to "Filename". Note that storing the private key in a separate file is not supported by client-native and the other projects depending on it. The function `ShowCertificate` will return an error if "Crt filename" and "Key filename" are different.
1 parent 44677dc commit 901e91e

File tree

2 files changed

+96
-4
lines changed

2 files changed

+96
-4
lines changed

runtime/certs.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package runtime
22

33
import (
4+
"errors"
45
"fmt"
56
"strings"
67
"time"
@@ -87,9 +88,9 @@ func (s *SingleRuntime) ShowCertEntry(storageName string) (*models.SslCertEntry,
8788
return parseCertEntry(response)
8889
}
8990

90-
// parseCertEntry parses one entry in one CrtList file/runtime and returns it structured
91+
// parseCertEntry parses one entry in one CrtList file and returns it structured
9192
// example:
92-
// Filename: /etc/ssl/cert-2.pem
93+
// Filename: /etc/ssl/cert-2.pem <= this is just the same name you gave as certificate name, could be an alias
9394
// Status: Used
9495
// Serial: 0D933C1B1089BF660AE5253A245BB388
9596
// notBefore: Sep 9 00:00:00 2020 GMT
@@ -101,12 +102,18 @@ func (s *SingleRuntime) ShowCertEntry(storageName string) (*models.SslCertEntry,
101102
// Issuer: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
102103
// Chain Subject: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
103104
// Chain Issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
105+
// *** undocumented, only in recent versions, "Key filename" is optional ***
106+
// Crt filename: /etc/ssl/cert-2.pem
107+
// Key filename: /etc/ssl/cert-2.key
104108
func parseCertEntry(response string) (*models.SslCertEntry, error) {
105109
if response == "" || strings.HasPrefix(strings.TrimSpace(response), "#") {
106110
return nil, native_errors.ErrNotFound
107111
}
108112

109113
c := &models.SslCertEntry{}
114+
var crtFilename string
115+
var keyFilename string
116+
110117
parts := strings.SplitSeq(response, "\n")
111118
for p := range parts {
112119
before, after, found := strings.Cut(p, ":")
@@ -143,9 +150,27 @@ func parseCertEntry(response string) (*models.SslCertEntry, error) {
143150
c.ChainSubject = valueString
144151
case "Chain Issuer":
145152
c.ChainIssuer = valueString
153+
case "Crt filename":
154+
crtFilename = valueString
155+
case "Key filename":
156+
keyFilename = valueString
146157
}
147158
}
148159

160+
if crtFilename != "" {
161+
c.StorageName = crtFilename
162+
}
163+
164+
// We currently do not support storing the key in a separate file.
165+
if keyFilename != "" && keyFilename != crtFilename {
166+
return nil, fmt.Errorf("failed to parse certificate info for %s: storing the private key in a separate file is not supported", c.StorageName)
167+
}
168+
169+
// This should be impossible.
170+
if c.StorageName == "" {
171+
return nil, errors.New("failed to parse certificate info: empty filename")
172+
}
173+
149174
return c, nil
150175
}
151176

runtime/certs_test.go

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func TestSingleRuntime_ShowCertEntry(t *testing.T) {
186186
name: "Simple show certs, should return a cert",
187187
fields: fields{socketPath: haProxy.Addr().String()},
188188
args: args{
189-
storageName: "/etc/ssl/cert-0.pem",
189+
storageName: "cert-0",
190190
},
191191
want: &models.SslCertEntry{
192192
StorageName: "/etc/ssl/cert-0.pem",
@@ -206,7 +206,72 @@ func TestSingleRuntime_ShowCertEntry(t *testing.T) {
206206
ChainIssuer: "/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA",
207207
},
208208
socketResponse: map[string]string{
209-
"show ssl cert /etc/ssl/cert-0.pem\n": ` Filename: /etc/ssl/cert-0.pem
209+
"show ssl cert cert-0\n": ` Filename: cert-0
210+
Status: Used
211+
Serial: 0D933C1B1089BF660AE5253A245BB388
212+
notBefore: Sep 9 00:00:00 2020 GMT
213+
notAfter: Sep 14 12:00:00 2021 GMT
214+
Subject Alternative Name: DNS:*.platform.domain.com, DNS:uaa.platform.domain.com
215+
Algorithm: RSA4096
216+
SHA1 FingerPrint: 59242F1838BDEF3E7DAFC83FFE4DD6C03B88805C
217+
Subject: /C=DE/ST=Baden-Württemberg/L=Walldorf/O=ORG SE/CN=*.platform.domain.com
218+
Issuer: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
219+
Chain Subject: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
220+
Chain Issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
221+
Crt filename: /etc/ssl/cert-0.pem
222+
Key filename: /etc/ssl/cert-0.pem
223+
`,
224+
},
225+
},
226+
{
227+
name: "A certificate without a 'Crt filename'",
228+
fields: fields{socketPath: haProxy.Addr().String()},
229+
args: args{
230+
storageName: "cert-1",
231+
},
232+
want: &models.SslCertEntry{
233+
StorageName: "cert-1",
234+
Status: "Used",
235+
Serial: "0D933C1B1089BF660AE5253A245BB388",
236+
NotBefore: strfmt.Date(notBefore),
237+
NotAfter: strfmt.Date(notAfter),
238+
SubjectAlternativeNames: []string{
239+
"DNS:*.platform.domain.com",
240+
"DNS:uaa.platform.domain.com",
241+
},
242+
Algorithm: "RSA4096",
243+
Sha1FingerPrint: "59242F1838BDEF3E7DAFC83FFE4DD6C03B88805C",
244+
Subject: "/C=DE/ST=Baden-Württemberg/L=Walldorf/O=ORG SE/CN=*.platform.domain.com",
245+
Issuer: "/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA",
246+
ChainSubject: "/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA",
247+
ChainIssuer: "/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA",
248+
},
249+
socketResponse: map[string]string{
250+
"show ssl cert cert-1\n": ` Filename: cert-1
251+
Status: Used
252+
Serial: 0D933C1B1089BF660AE5253A245BB388
253+
notBefore: Sep 9 00:00:00 2020 GMT
254+
notAfter: Sep 14 12:00:00 2021 GMT
255+
Subject Alternative Name: DNS:*.platform.domain.com, DNS:uaa.platform.domain.com
256+
Algorithm: RSA4096
257+
SHA1 FingerPrint: 59242F1838BDEF3E7DAFC83FFE4DD6C03B88805C
258+
Subject: /C=DE/ST=Baden-Württemberg/L=Walldorf/O=ORG SE/CN=*.platform.domain.com
259+
Issuer: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
260+
Chain Subject: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
261+
Chain Issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
262+
`,
263+
},
264+
},
265+
{
266+
name: "A certificate with a separate key file",
267+
fields: fields{socketPath: haProxy.Addr().String()},
268+
args: args{
269+
storageName: "cert-2",
270+
},
271+
want: nil,
272+
wantErr: true,
273+
socketResponse: map[string]string{
274+
"show ssl cert cert-2\n": ` Filename: cert-2
210275
Status: Used
211276
Serial: 0D933C1B1089BF660AE5253A245BB388
212277
notBefore: Sep 9 00:00:00 2020 GMT
@@ -218,6 +283,8 @@ func TestSingleRuntime_ShowCertEntry(t *testing.T) {
218283
Issuer: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
219284
Chain Subject: /C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
220285
Chain Issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
286+
Crt filename: /etc/ssl/cert-2.crt
287+
Key filename: /etc/ssl/cert-2.key
221288
`,
222289
},
223290
},

0 commit comments

Comments
 (0)