feat(client): add mTLS config (#665)

* feat: add mtls config to client

* feat: add mtls config to client

* Rework client tls configuration

* Rebase (#3)

* chore(deps): bump codecov/codecov-action from 3.1.6 to 4.0.1 (#671)

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.6 to 4.0.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.1.6...v4.0.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(client): enhance HTTP client configuration with proxy support (#668)

* feat: enhance HTTP client configuration with proxy support

- Add `ProxyURL` field to `Config` struct with YAML tag
- Implement proxy URL parsing and setting in `getHTTPClient` method
- Add test case for `getHTTPClient` method with custom proxy URL setting
- Include `net/url` package in both `config.go` and `config_test.go` files

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* docs: enhance README with Proxy and OAuth2 Docs

- Remove empty lines from README.md
- Add documentation for proxy configuration in client examples
- Include YAML examples for client using a proxy, custom DNS resolver, OAuth2, and identity-aware proxy configurations in README.md

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* docs: add proxy client

Signed-off-by: appleboy <appleboy.tw@gmail.com>

* Update client/config.go

* Update README.md

* Update client/config_test.go

---------

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: TwiN <twin@linux.com>

* chore: Update Go to 1.21

* chore(deps): bump github.com/prometheus/client_golang from 1.17.0 to 1.18.0 (#658)

chore(deps): bump github.com/prometheus/client_golang

Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump github.com/gofiber/fiber/v2 from 2.49.2 to 2.52.1 (#682)

Bumps [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber) from 2.49.2 to 2.52.1.
- [Release notes](https://github.com/gofiber/fiber/releases)
- [Commits](https://github.com/gofiber/fiber/compare/v2.49.2...v2.52.1)

---
updated-dependencies:
- dependency-name: github.com/gofiber/fiber/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs(alerting): Fix wrong gitlab terminology (alert key vs. PAT) (#694)

Fix wrong term (alert key vs. PAT)

* chore(deps): bump github.com/TwiN/deepmerge from 0.2.0 to 0.2.1 (#684)

* chore(deps): bump github.com/TwiN/deepmerge from 0.2.0 to 0.2.1

Bumps [github.com/TwiN/deepmerge](https://github.com/TwiN/deepmerge) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/TwiN/deepmerge/releases)
- [Commits](https://github.com/TwiN/deepmerge/compare/v0.2.0...v0.2.1)

---
updated-dependencies:
- dependency-name: github.com/TwiN/deepmerge
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build: Add `go mod tidy` in Dockerfile

* ci: Update Go to 1.20

* Update go.mod

* Update test.yml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: TwiN <twin@linux.com>

* chore(deps): bump golang.org/x/oauth2 from 0.13.0 to 0.18.0 (#701)

Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.13.0 to 0.18.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.13.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: add mtls config to client

feat: add mtls config to client

Rework client tls configuration

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: TwiN <twin@linux.com>
Co-authored-by: Salim B <git@salim.space>

* Rebase (#4)

* chore(deps): bump codecov/codecov-action from 3.1.6 to 4.0.1 (#671)

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.6 to 4.0.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3.1.6...v4.0.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(client): enhance HTTP client configuration with proxy support (#668)

* feat: enhance HTTP client configuration with proxy support

- Add `ProxyURL` field to `Config` struct with YAML tag
- Implement proxy URL parsing and setting in `getHTTPClient` method
- Add test case for `getHTTPClient` method with custom proxy URL setting
- Include `net/url` package in both `config.go` and `config_test.go` files

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* docs: enhance README with Proxy and OAuth2 Docs

- Remove empty lines from README.md
- Add documentation for proxy configuration in client examples
- Include YAML examples for client using a proxy, custom DNS resolver, OAuth2, and identity-aware proxy configurations in README.md

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* docs: add proxy client

Signed-off-by: appleboy <appleboy.tw@gmail.com>

* Update client/config.go

* Update README.md

* Update client/config_test.go

---------

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: TwiN <twin@linux.com>

* chore: Update Go to 1.21

* chore(deps): bump github.com/prometheus/client_golang from 1.17.0 to 1.18.0 (#658)

chore(deps): bump github.com/prometheus/client_golang

Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump github.com/gofiber/fiber/v2 from 2.49.2 to 2.52.1 (#682)

Bumps [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber) from 2.49.2 to 2.52.1.
- [Release notes](https://github.com/gofiber/fiber/releases)
- [Commits](https://github.com/gofiber/fiber/compare/v2.49.2...v2.52.1)

---
updated-dependencies:
- dependency-name: github.com/gofiber/fiber/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs(alerting): Fix wrong gitlab terminology (alert key vs. PAT) (#694)

Fix wrong term (alert key vs. PAT)

* chore(deps): bump github.com/TwiN/deepmerge from 0.2.0 to 0.2.1 (#684)

* chore(deps): bump github.com/TwiN/deepmerge from 0.2.0 to 0.2.1

Bumps [github.com/TwiN/deepmerge](https://github.com/TwiN/deepmerge) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/TwiN/deepmerge/releases)
- [Commits](https://github.com/TwiN/deepmerge/compare/v0.2.0...v0.2.1)

---
updated-dependencies:
- dependency-name: github.com/TwiN/deepmerge
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build: Add `go mod tidy` in Dockerfile

* ci: Update Go to 1.20

* Update go.mod

* Update test.yml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: TwiN <twin@linux.com>

* chore(deps): bump golang.org/x/oauth2 from 0.13.0 to 0.18.0 (#701)

Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.13.0 to 0.18.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.13.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: add mtls config to client

* feat: add mtls config to client

* Rework client tls configuration

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: TwiN <twin@linux.com>
Co-authored-by: Salim B <git@salim.space>

* Rebase (#6)

* feat(tls): add mtls config to client (#189)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: TwiN <twin@linux.com>
Co-authored-by: Salim B <git@salim.space>
This commit is contained in:
Chris Smith
2024-04-09 18:41:37 -04:00
committed by GitHub
parent f54c45e20e
commit f93cebe715
4 changed files with 205 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package client
import (
"bytes"
"crypto/tls"
"io"
"net/http"
"testing"
@ -290,3 +291,46 @@ func TestQueryWebSocket(t *testing.T) {
t.Error("expected an error due to the target not being websocket-friendly")
}
}
func TestTlsRenegotiation(t *testing.T) {
tests := []struct {
name string
cfg TLSConfig
expectedConfig tls.RenegotiationSupport
}{
{
name: "default",
cfg: TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"},
expectedConfig: tls.RenegotiateNever,
},
{
name: "never",
cfg: TLSConfig{RenegotiationSupport: "never", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"},
expectedConfig: tls.RenegotiateNever,
},
{
name: "once",
cfg: TLSConfig{RenegotiationSupport: "once", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"},
expectedConfig: tls.RenegotiateOnceAsClient,
},
{
name: "freely",
cfg: TLSConfig{RenegotiationSupport: "freely", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"},
expectedConfig: tls.RenegotiateFreelyAsClient,
},
{
name: "not-valid-and-broken",
cfg: TLSConfig{RenegotiationSupport: "invalid", CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"},
expectedConfig: tls.RenegotiateNever,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tls := &tls.Config{}
tlsConfig := configureTLS(tls, test.cfg)
if tlsConfig.Renegotiation != test.expectedConfig {
t.Errorf("expected tls renegotiation to be %v, but got %v", test.expectedConfig, tls.Renegotiation)
}
})
}
}

View File

@ -26,6 +26,7 @@ var (
ErrInvalidDNSResolverPort = errors.New("invalid DNS resolver port")
ErrInvalidClientOAuth2Config = errors.New("invalid oauth2 configuration: must define all fields for client credentials flow (token-url, client-id, client-secret, scopes)")
ErrInvalidClientIAPConfig = errors.New("invalid Identity-Aware-Proxy configuration: must define all fields for Google Identity-Aware-Proxy programmatic authentication (audience)")
ErrInvalidClientTLSConfig = errors.New("invalid TLS configuration: certificate-file and private-key-file must be specified")
defaultConfig = Config{
Insecure: false,
@ -72,6 +73,9 @@ type Config struct {
// Network (ip, ip4 or ip6) for the ICMP client
Network string `yaml:"network"`
// TLS configuration (optional)
TLS *TLSConfig `yaml:"tls,omitempty"`
}
// DNSResolverConfig is the parsed configuration from the DNSResolver config string.
@ -94,6 +98,17 @@ type IAPConfig struct {
Audience string `yaml:"audience"` // e.g. "toto.apps.googleusercontent.com"
}
// TLSConfig is the configuration for mTLS configurations
type TLSConfig struct {
// CertificateFile is the public certificate for TLS in PEM format.
CertificateFile string `yaml:"certificate-file,omitempty"`
// PrivateKeyFile is the private key file for TLS in PEM format.
PrivateKeyFile string `yaml:"private-key-file,omitempty"`
RenegotiationSupport string `yaml:"renegotiation,omitempty"`
}
// ValidateAndSetDefaults validates the client configuration and sets the default values if necessary
func (c *Config) ValidateAndSetDefaults() error {
if c.Timeout < time.Millisecond {
@ -111,6 +126,11 @@ func (c *Config) ValidateAndSetDefaults() error {
if c.HasIAPConfig() && !c.IAPConfig.isValid() {
return ErrInvalidClientIAPConfig
}
if c.HasTlsConfig() {
if err := c.TLS.isValid(); err != nil {
return err
}
}
return nil
}
@ -156,6 +176,11 @@ func (c *Config) HasIAPConfig() bool {
return c.IAPConfig != nil
}
// HasTlsConfig returns true if the client has client certificate parameters
func (c *Config) HasTlsConfig() bool {
return c.TLS != nil && len(c.TLS.CertificateFile) > 0 && len(c.TLS.PrivateKeyFile) > 0
}
// isValid() returns true if the IAP configuration is valid
func (c *IAPConfig) isValid() bool {
return len(c.Audience) > 0
@ -166,8 +191,26 @@ func (c *OAuth2Config) isValid() bool {
return len(c.TokenURL) > 0 && len(c.ClientID) > 0 && len(c.ClientSecret) > 0 && len(c.Scopes) > 0
}
// isValid() returns nil if the client tls certificates are valid, otherwise returns an error
func (t *TLSConfig) isValid() error {
if len(t.CertificateFile) > 0 && len(t.PrivateKeyFile) > 0 {
_, err := tls.LoadX509KeyPair(t.CertificateFile, t.PrivateKeyFile)
if err != nil {
return err
}
return nil
}
return ErrInvalidClientTLSConfig
}
// GetHTTPClient return an HTTP client matching the Config's parameters.
func (c *Config) getHTTPClient() *http.Client {
tlsConfig := &tls.Config{
InsecureSkipVerify: c.Insecure,
}
if c.HasTlsConfig() && c.TLS.isValid() == nil {
tlsConfig = configureTLS(tlsConfig, *c.TLS)
}
if c.httpClient == nil {
c.httpClient = &http.Client{
Timeout: c.Timeout,
@ -175,9 +218,7 @@ func (c *Config) getHTTPClient() *http.Client {
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: c.Insecure,
},
TLSClientConfig: tlsConfig,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if c.IgnoreRedirect {
@ -281,3 +322,23 @@ func configureOAuth2(httpClient *http.Client, c OAuth2Config) *http.Client {
client.Timeout = httpClient.Timeout
return client
}
// configureTLS returns a TLS Config that will enable mTLS
func configureTLS(tlsConfig *tls.Config, c TLSConfig) *tls.Config {
clientTLSCert, err := tls.LoadX509KeyPair(c.CertificateFile, c.PrivateKeyFile)
if err != nil {
return nil
}
tlsConfig.Certificates = []tls.Certificate{clientTLSCert}
tlsConfig.Renegotiation = tls.RenegotiateNever
renegotionSupport := map[string]tls.RenegotiationSupport{
"once": tls.RenegotiateOnceAsClient,
"freely": tls.RenegotiateFreelyAsClient,
"never": tls.RenegotiateNever,
}
if val, ok := renegotionSupport[c.RenegotiationSupport]; ok {
tlsConfig.Renegotiation = val
}
return tlsConfig
}

View File

@ -106,3 +106,66 @@ func TestConfig_getHTTPClient_withCustomProxyURL(t *testing.T) {
t.Errorf("expected Config.ProxyURL to set the HTTP client's proxy to %s", proxyURL)
}
}
func TestConfig_TlsIsValid(t *testing.T) {
tests := []struct {
name string
cfg *Config
expectedErr bool
}{
{
name: "good-tls-config",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/cert.key"}},
expectedErr: false,
},
{
name: "missing-certificate-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "doesnotexist", PrivateKeyFile: "../testdata/cert.key"}},
expectedErr: true,
},
{
name: "bad-certificate-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/badcert.pem", PrivateKeyFile: "../testdata/cert.key"}},
expectedErr: true,
},
{
name: "no-certificate-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "", PrivateKeyFile: "../testdata/cert.key"}},
expectedErr: true,
},
{
name: "missing-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "doesnotexist"}},
expectedErr: true,
},
{
name: "no-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: ""}},
expectedErr: true,
},
{
name: "bad-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/cert.pem", PrivateKeyFile: "../testdata/badcert.key"}},
expectedErr: true,
},
{
name: "bad-certificate-and-private-key-file",
cfg: &Config{TLS: &TLSConfig{CertificateFile: "../testdata/badcert.pem", PrivateKeyFile: "../testdata/badcert.key"}},
expectedErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.cfg.TLS.isValid()
if (err != nil) != test.expectedErr {
t.Errorf("expected the existence of an error to be %v, got %v", test.expectedErr, err)
return
}
if !test.expectedErr {
if test.cfg.TLS.isValid() != nil {
t.Error("cfg.TLS.isValid() returned an error even though no error was expected")
}
}
})
}
}