Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add SetCertificateFromFile and SetCertificateFromString #941

Merged
merged 1 commit into from
Jan 4, 2025
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
18 changes: 18 additions & 0 deletions .testdata/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC+jCCAeKgAwIBAgIRAJce5ewsoW44j0qvSABmq7owDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yNTAxMDQwNzA3MTNaFw0yNjAxMDQwNzA3
MTNaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCYkTN1g/0Z3KkS3w0lX9yhZkwiA0obXCeFs7hpRP0p4WlW3uADyXQ5
h2MaYx8OCA7oGU7/dWOPhtE3rgFEz7IwLxcP5d02ukLGlFD69D6KLyTXwCFmvOWQ
5fbOq4s73WTNDfYSTYNzeujDCjeu/Bk0OVhdxbyZdyrpdm+UBfH8uIDoGeCRXnji
nqG9HNOQx6r/S6FqC5j/7PrVl1i66WlqRzKEJB94uejfujrHq8RjQm/wzEutU5df
C39zEEEx75qQt7Jc0asm1AqAKSq34xn4rVajWrBZ/WudUUizHfaBDP61uPFvPyKW
JDvTSdeoM9TPX0y0cjo6AwSrdLl7flrRAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC
CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAdHvPQe3EJ4/X6K/bklJUhIfM
KBauH8VMBfri7xLawleKssm7GdiFivSA0g1pArkl8SALBlPqhrx7rwlyyivLTZaR
VFvXaQ9eU0zGnSnDnKVz6CX/zn3TKfcgZPEBclayh0ldm7A8xSJWaWbRZ+s9e9x1
XcQTn2KkMZfBDMnGEWQ3KZrClvO5ZfkqSiyzEm9+eF0m0E7ujTyfSVMsPdyldA6U
pHG8omQTyOzJl2I4z7DlS0AEsL0TJHV4iKr9rDei2xQz/wtful5qU/taYp2Y6zMH
8ytnDldJhmcCwmvtqvK5p6CbkatE7TFyw2CxQJHnQef+Y4W94sSZWg9CGRKDIQ==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions .testdata/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCYkTN1g/0Z3KkS
3w0lX9yhZkwiA0obXCeFs7hpRP0p4WlW3uADyXQ5h2MaYx8OCA7oGU7/dWOPhtE3
rgFEz7IwLxcP5d02ukLGlFD69D6KLyTXwCFmvOWQ5fbOq4s73WTNDfYSTYNzeujD
Cjeu/Bk0OVhdxbyZdyrpdm+UBfH8uIDoGeCRXnjinqG9HNOQx6r/S6FqC5j/7PrV
l1i66WlqRzKEJB94uejfujrHq8RjQm/wzEutU5dfC39zEEEx75qQt7Jc0asm1AqA
KSq34xn4rVajWrBZ/WudUUizHfaBDP61uPFvPyKWJDvTSdeoM9TPX0y0cjo6AwSr
dLl7flrRAgMBAAECggEAJPTPNUEilxgncGXNZmdBJ2uDN536XoRFIpL1MbK/bFyo
yp00QFaVK7ZK4EJwbFKxYbF3vFOwKT0sAsPIlOWGsTtG59fzbOVTdYzJzPBLEef3
kbd9n8hUB3RdA5T0Ji0r1Kv0FlzmYZu9NDmOYXm5lTfq2tQiKj5+i4zf3EhQZLng
4wVxBT7yQUQcstJv5K1L6HVzunSYtbHx8ZVxmw+tJ4lMCK23KPlvncZZTT8chWdT
3GOp5nYIHk9E5jQnBnj7p73sxZUCZlb8uhLtdcgAXc4scptEVO+7n5zOaXIv40Oz
yfkESgHcZWAMDvnkxdySHlD38Z2LIKDGbqR6O9wcwQKBgQDBO6fFPXO41nsxdVCB
nhCgL2hsGjaxJzGBLaOJNVMMFRASN3Yqvs4N1Hn7lawRI/FRRffxjLkZfNGEBSF2
OipdvX19Oe2hCZxvwHPoe5sb/Dh6KE7If1hRLOCXg/8E7ADBtAp94dam1WF4Kh6N
Va6+n2YKif2rqye1YtRoUU46iQKBgQDKH/eMcMRUe9IySxHLogidOUwa0X7WrxF/
PkXGpPbHQtMOJF5cVzh+L+foUKXNM60lgmCH0438GKU7kirC/dVtD/bwE598/XFZ
vnjPV7Adf9vBz9NN8cS/4uEfQYbvTRmrnrQK+ZhOe8hmwjapxqdWrVHNUtvx18vL
qBwR4YjsCQKBgCycMx1MFJ1FludSKCXkcf4pM7hRTPMVE065VJnmn6eYbT9nYnZ3
2mZC+W5lnXXPkHSs7JLtZAZIVK5f6Nu8je9aQdBZQUz+RQlfquKvNp39WqSJDbcn
/yGudKNGK+fc/Ee74vgw3Tdi57+wKaGDeHY1on8oYFHzj5VGnbb/nknRAoGBAK2Z
hyQ4NmfZcU+A6mfbY0qmS5c9F5OMCZsgAQ374XiDDIK4+dKVlw/KVYRSwBTerXfp
4r7GFMzQ3hmsEM4o9YYWkCDiubjAdPp/fYOX7MtpZXWw6euoGzQzyObvgNVHgyTD
yh8jAI1oA1c+t3RaCp+HfRq8b+vnTEI+wN0auF8BAoGBAJmw+GgHCZGpw2XPNu+X
8kuVGbQYAjTOXhBM4WzZyhfH1TWKLGn7C9YixhE2AW0UWKDvy+6OqPhe8q3KVms3
8YZ1W+vbUNEZNGE0XrB5ZMXfePiqisCz0jgP9OAuT+ii4aI3MAm3zgCEC6UTMvLq
gNBu3Tcy6udxnUf7czzJDRtE
-----END PRIVATE KEY-----
12 changes: 7 additions & 5 deletions cert_watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ func TestClient_SetRootCertificateWatcher(t *testing.T) {
// Make sure that TLS handshake happens for all request
// (otherwise, test may succeed because 1st TLS session is re-used)
DisableKeepAlives: true,
}).SetRootCertificateWatcher(paths.RootCACert, &CertWatcherOptions{
PoolInterval: poolingInterval,
}).SetClientRootCertificateWatcher(paths.RootCACert, &CertWatcherOptions{
PoolInterval: poolingInterval,
}).SetDebug(false)
}).SetRootCertificatesWatcher(
&CertWatcherOptions{PoolInterval: poolingInterval},
paths.RootCACert,
).SetClientRootCertificatesWatcher(
&CertWatcherOptions{PoolInterval: poolingInterval},
paths.RootCACert,
).SetDebug(false)

url := strings.Replace(ts.URL, "127.0.0.1", "localhost", 1)
t.Log("Test URL:", url)
Expand Down
227 changes: 155 additions & 72 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1488,7 +1488,52 @@ func (c *Client) RemoveProxy() *Client {
return c
}

// SetCertificates method helps to conveniently set client certificates into Resty.
// SetCertificateFromString method helps to set client certificates into Resty
// from cert and key files to perform SSL client authentication
//
// client.SetCertificateFromFile("certs/client.pem", "certs/client.key")
func (c *Client) SetCertificateFromFile(certFilePath, certKeyFilePath string) *Client {
cert, err := tls.LoadX509KeyPair(certFilePath, certKeyFilePath)
if err != nil {
c.Logger().Errorf("client certificate/key parsing error: %v", err)
return c
}
c.SetCertificates(cert)
return c
}

// SetCertificateFromString method helps to set client certificates into Resty
// from string to perform SSL client authentication
//
// myClientCertStr := `-----BEGIN CERTIFICATE-----
// ... cert content ...
// -----END CERTIFICATE-----`
//
// myClientCertKeyStr := `-----BEGIN PRIVATE KEY-----
// ... cert key content ...
// -----END PRIVATE KEY-----`
//
// client.SetCertificateFromString(myClientCertStr, myClientCertKeyStr)
func (c *Client) SetCertificateFromString(certStr, certKeyStr string) *Client {
cert, err := tls.X509KeyPair([]byte(certStr), []byte(certKeyStr))
if err != nil {
c.Logger().Errorf("client certificate/key parsing error: %v", err)
return c
}
c.SetCertificates(cert)
return c
}

// SetCertificates method helps to conveniently set client certificates into Resty
// to perform SSL client authentication
//
// cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
// if err != nil {
// log.Printf("ERROR client certificate/key parsing error: %v", err)
// return
// }
//
// client.SetCertificates(cert)
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
config, err := c.tlsConfig()
if err != nil {
Expand All @@ -1502,72 +1547,142 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
return c
}

// SetRootCertificate method helps to add one or more root certificates into the Resty client
// SetRootCertificates method helps to add one or more root certificates into the Resty client
//
// client.SetRootCertificate("/path/to/root/pemFile.pem")
func (c *Client) SetRootCertificate(pemFilePath string) *Client {
rootPemData, err := os.ReadFile(pemFilePath)
if err != nil {
c.Logger().Errorf("%v", err)
return c
// // one pem file path
// client.SetRootCertificates("/path/to/root/pemFile.pem")
//
// // one or more pem file path(s)
// client.SetRootCertificates(
// "/path/to/root/pemFile1.pem",
// "/path/to/root/pemFile2.pem"
// "/path/to/root/pemFile3.pem"
// )
//
// // if you happen to have string slices
// client.SetRootCertificates(certs...)
func (c *Client) SetRootCertificates(pemFilePaths ...string) *Client {
for _, fp := range pemFilePaths {
rootPemData, err := os.ReadFile(fp)
if err != nil {
c.Logger().Errorf("%v", err)
return c
}
c.handleCAs("root", rootPemData)
}
c.handleCAs("root", rootPemData)
return c
}

// SetRootCertificateWatcher enables dynamic reloading of one or more root certificates.
// SetRootCertificatesWatcher method enables dynamic reloading of one or more root certificates.
// It is designed for scenarios involving long-running Resty clients where certificates may be renewed.
// The caller is responsible for calling Close to stop the watcher.
//
// client.SetRootCertificateWatcher("root-ca.crt", &CertWatcherOptions{
// PoolInterval: time.Hour * 24,
// })
//
// defer client.Close()
func (c *Client) SetRootCertificateWatcher(pemFilePath string, options *CertWatcherOptions) *Client {
c.SetRootCertificate(pemFilePath)
c.initCertWatcher(pemFilePath, "root", options)
// client.SetRootCertificatesWatcher(
// &resty.CertWatcherOptions{
// PoolInterval: 24 * time.Hour,
// },
// "root-ca.crt",
// )
func (c *Client) SetRootCertificatesWatcher(options *CertWatcherOptions, pemFilePaths ...string) *Client {
c.SetRootCertificates(pemFilePaths...)
for _, fp := range pemFilePaths {
c.initCertWatcher(fp, "root", options)
}
return c
}

// SetRootCertificateFromString method helps to add one or more root certificates
// into the Resty client
//
// client.SetRootCertificateFromString("pem certs content")
// myRootCertStr := `-----BEGIN CERTIFICATE-----
// ... cert content ...
// -----END CERTIFICATE-----`
//
// client.SetRootCertificateFromString(myRootCertStr)
func (c *Client) SetRootCertificateFromString(pemCerts string) *Client {
c.handleCAs("root", []byte(pemCerts))
return c
}

// SetClientRootCertificate method helps to add one or more client's root
// SetClientRootCertificates method helps to add one or more client's root
// certificates into the Resty client
//
// client.SetClientRootCertificate("/path/to/root/pemFile.pem")
func (c *Client) SetClientRootCertificate(pemFilePath string) *Client {
rootPemData, err := os.ReadFile(pemFilePath)
if err != nil {
c.Logger().Errorf("%v", err)
return c
// // one pem file path
// client.SetClientCertificates("/path/to/client/pemFile.pem")
//
// // one or more pem file path(s)
// client.SetClientCertificates(
// "/path/to/client/pemFile1.pem",
// "/path/to/client/pemFile2.pem"
// "/path/to/client/pemFile3.pem"
// )
//
// // if you happen to have string slices
// client.SetClientCertificates(certs...)
func (c *Client) SetClientRootCertificates(pemFilePaths ...string) *Client {
for _, fp := range pemFilePaths {
pemData, err := os.ReadFile(fp)
if err != nil {
c.Logger().Errorf("%v", err)
return c
}
c.handleCAs("client-root", pemData)
}
c.handleCAs("client", rootPemData)
return c
}

// SetClientRootCertificateWatcher enables dynamic reloading of one or more client root certificates.
// SetClientRootCertificatesWatcher method enables dynamic reloading of one or more client root certificates.
// It is designed for scenarios involving long-running Resty clients where certificates may be renewed.
// The caller is responsible for calling Close to stop the watcher.
//
// client.SetClientRootCertificateWatcher("root-ca.crt", &CertWatcherOptions{
// PoolInterval: time.Hour * 24,
// })
// defer client.Close()
func (c *Client) SetClientRootCertificateWatcher(pemFilePath string, options *CertWatcherOptions) *Client {
c.SetClientRootCertificate(pemFilePath)
c.initCertWatcher(pemFilePath, "client", options)
// client.SetClientRootCertificatesWatcher(
// &resty.CertWatcherOptions{
// PoolInterval: 24 * time.Hour,
// },
// "root-ca.crt",
// )
func (c *Client) SetClientRootCertificatesWatcher(options *CertWatcherOptions, pemFilePaths ...string) *Client {
c.SetClientRootCertificates(pemFilePaths...)
for _, fp := range pemFilePaths {
c.initCertWatcher(fp, "client-root", options)
}
return c
}

// SetClientRootCertificateFromString method helps to add one or more clients
// root certificates into the Resty client
//
// myClientRootCertStr := `-----BEGIN CERTIFICATE-----
// ... cert content ...
// -----END CERTIFICATE-----`
//
// client.SetClientRootCertificateFromString(myClientRootCertStr)
func (c *Client) SetClientRootCertificateFromString(pemCerts string) *Client {
c.handleCAs("client-root", []byte(pemCerts))
return c
}

func (c *Client) handleCAs(scope string, permCerts []byte) {
config, err := c.tlsConfig()
if err != nil {
c.Logger().Errorf("%v", err)
return
}

c.lock.Lock()
defer c.lock.Unlock()
switch scope {
case "root":
if config.RootCAs == nil {
config.RootCAs = x509.NewCertPool()
}
config.RootCAs.AppendCertsFromPEM(permCerts)
case "client-root":
if config.ClientCAs == nil {
config.ClientCAs = x509.NewCertPool()
}
config.ClientCAs.AppendCertsFromPEM(permCerts)
}
}

func (c *Client) initCertWatcher(pemFilePath, scope string, options *CertWatcherOptions) {
tickerDuration := defaultWatcherPoolingInterval
if options != nil && options.PoolInterval > 0 {
Expand Down Expand Up @@ -1611,9 +1726,9 @@ func (c *Client) initCertWatcher(pemFilePath, scope string, options *CertWatcher

switch scope {
case "root":
c.SetRootCertificate(pemFilePath)
case "client":
c.SetClientRootCertificate(pemFilePath)
c.SetRootCertificates(pemFilePath)
case "client-root":
c.SetClientRootCertificates(pemFilePath)
}

c.debugf("Cert %s reloaded.", pemFilePath)
Expand All @@ -1622,38 +1737,6 @@ func (c *Client) initCertWatcher(pemFilePath, scope string, options *CertWatcher
}()
}

// SetClientRootCertificateFromString method helps to add one or more clients
// root certificates into the Resty client
//
// client.SetClientRootCertificateFromString("pem certs content")
func (c *Client) SetClientRootCertificateFromString(pemCerts string) *Client {
c.handleCAs("client", []byte(pemCerts))
return c
}

func (c *Client) handleCAs(scope string, permCerts []byte) {
config, err := c.tlsConfig()
if err != nil {
c.Logger().Errorf("%v", err)
return
}

c.lock.Lock()
defer c.lock.Unlock()
switch scope {
case "root":
if config.RootCAs == nil {
config.RootCAs = x509.NewCertPool()
}
config.RootCAs.AppendCertsFromPEM(permCerts)
case "client":
if config.ClientCAs == nil {
config.ClientCAs = x509.NewCertPool()
}
config.ClientCAs.AppendCertsFromPEM(permCerts)
}
}

// OutputDirectory method returns the output directory value from the client.
func (c *Client) OutputDirectory() string {
c.lock.RLock()
Expand Down
Loading
Loading