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

V4 add client unsecure skip verify #271

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ go 1.13

require (
github.com/newrelic/infrastructure-agent v0.0.0-20201127092132-00ac7efc0cc6
github.com/stretchr/testify v1.5.1
github.com/stretchr/testify v1.7.0
)
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ github.com/newrelic/infrastructure-agent v0.0.0-20201127092132-00ac7efc0cc6 h1:o
github.com/newrelic/infrastructure-agent v0.0.0-20201127092132-00ac7efc0cc6/go.mod h1:OC9Em8HnZsHI3JQzMHFqfB4B7OBzQ2+gBffhS0Ip+pk=
github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.2-0.20181029102219-09950c5fb1bb/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -65,8 +64,9 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q
github.com/sirupsen/logrus v1.6.1-0.20200528085638-6699a89a232f/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
golang.org/dl v0.0.0-20200901180525-35ca1c5c19fb/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down Expand Up @@ -115,8 +115,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.1-0.20181123051433-bcbf6e613274+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
213 changes: 136 additions & 77 deletions http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,116 +6,175 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)

// New creates a new http.Client with a custom certificate, which can be loaded from the passed CA Bundle file and/or
var (
// ErrInvalidTransportType defines the error to be returned if the transport defined for the client is incorrect
ErrInvalidTransportType = errors.New("roundTripper transport invalid, should be http type")
// ErrEmptyArg defines the error to be returned if an option is defined with an empty argument
ErrEmptyArg = errors.New("argument is empty")
)

// ClientOption defines the format of the client option functions
// Note that many options defined in this package rely on the Transport for the http.Client being of type *http.Transport.
// Using a different Transport will cause these options to fail.
type ClientOption func(*http.Client) error

// New creates a new http.Client with the Passed Client Options
// that will have a custom certificate if it is loaded from the passed CA Bundle file and/or
// directory. If both CABundleFile and CABundleDir are empty arguments, it creates an unsecure HTTP client.
func New(CABundleFile, CABundleDir string, httpTimeout time.Duration) (*http.Client, error) {
return _new(CABundleFile, CABundleDir, httpTimeout, "")
}
func New(opts ...ClientOption) (*http.Client, error) {
t := &http.Transport{
TLSClientConfig: &tls.Config{},
}

// NewAcceptInvalidHostname new http.Client with ability to accept HTTPS certificates that don't
// match the hostname of the server they are connecting to.
func NewAcceptInvalidHostname(CABundleFile, CABundleDir string, httpTimeout time.Duration, hostname string) (*http.Client, error) {
return _new(CABundleFile, CABundleDir, httpTimeout, hostname)
client := &http.Client{
Transport: t,
}

for _, opt := range opts {
if err := opt(client); err != nil {
return nil, err
}
}

return client, nil
}

func _new(CABundleFile, CABundleDir string, httpTimeout time.Duration, acceptInvalidHostname string) (*http.Client, error) {
// go default http transport settings
t := &http.Transport{}
// WithCABundleFile adds the CABundleFile cert to the the client's certPool
func WithCABundleFile(CABundleFile string) ClientOption {
return func(c *http.Client) error {
if CABundleFile == "" {
return fmt.Errorf("applying CABundleFile: %w", ErrEmptyArg)
}

if CABundleFile != "" || CABundleDir != "" {
certs, err := getCertPool(CABundleFile, CABundleDir)
certPool, err := clientCertPool(c)
if err != nil {
return nil, err
return err
}
return addCert(CABundleFile, certPool)
}
}

t.TLSClientConfig = &tls.Config{
RootCAs: certs,
// WithCABundleDir adds the CABundleDir looks for pem certs in the
// CABundleDir and adds them to the the client's certPool.
func WithCABundleDir(CABundleDir string) ClientOption {
return func(c *http.Client) error {
if CABundleDir == "" {
return fmt.Errorf("applying CABundleDir: %w", ErrEmptyArg)
}

if acceptInvalidHostname != "" {
// Default validation is replaced with VerifyPeerCertificate.
// Note that when InsecureSkipVerify and VerifyPeerCertificate are in use,
// ConnectionState.VerifiedChains will be nil.
t.TLSClientConfig.InsecureSkipVerify = true
// While packages like net/http will implicitly set ServerName, the
// VerifyPeerCertificate callback can't access that value, so it has to be set
// explicitly here or in VerifyPeerCertificate on the client side. If in
// an http.Transport DialTLS callback, this can be obtained by passing
// the addr argument to net.SplitHostPort.
t.TLSClientConfig.ServerName = acceptInvalidHostname
// Approximately equivalent to what crypto/tls does normally:
// https://github.com/golang/go/commit/29cfb4d3c3a97b6f426d1b899234da905be699aa
t.TLSClientConfig.VerifyPeerCertificate = func(certificates [][]byte, _ [][]*x509.Certificate) error {
certs := make([]*x509.Certificate, len(certificates))
for i, asn1Data := range certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return errors.New("tls: failed to parse certificate from server: " + err.Error())
}
certs[i] = cert
}
certPool, err := clientCertPool(c)
if err != nil {
return err
}

opts := x509.VerifyOptions{
Roots: t.TLSClientConfig.RootCAs, // On the server side, use config.ClientCAs.
DNSName: acceptInvalidHostname,
Intermediates: x509.NewCertPool(),
// On the server side, set KeyUsages to ExtKeyUsageClientAuth. The
// default value is appropriate for clients side verification.
// KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
files, err := ioutil.ReadDir(CABundleDir)
if err != nil {
return err
}

for _, f := range files {
if strings.Contains(f.Name(), ".pem") {
caCertFilePath := filepath.Join(CABundleDir, f.Name())
if err := addCert(caCertFilePath, certPool); err != nil {
return err
}
_, err := certs[0].Verify(opts)
return err
}

}
return nil
}

return &http.Client{
Timeout: httpTimeout,
Transport: t,
}, nil
}

func getCertPool(certFile string, certDirectory string) (*x509.CertPool, error) {
caCertPool := x509.NewCertPool()
// WithAcceptInvalidHostname allows the client to call the acceptInvalidHostname host
// instead of the host from the certificates.
func WithAcceptInvalidHostname(acceptInvalidHostname string) ClientOption {
return func(c *http.Client) error {
if acceptInvalidHostname == "" {
return fmt.Errorf("applying AcceptInvalidHostname: %w", ErrEmptyArg)
}

if certFile != "" {
if err := addCert(filepath.Join(certDirectory, certFile), caCertPool); err != nil {
if os.IsNotExist(err) {
if err = addCert(certFile, caCertPool); err != nil {
return nil, err
transport, ok := c.Transport.(*http.Transport)
if !ok {
return ErrInvalidTransportType
}
// Default validation is replaced with VerifyPeerCertificate.
// Note that when InsecureSkipVerify and VerifyPeerCertificate are in use,
// ConnectionState.VerifiedChains will be nil.
transport.TLSClientConfig.InsecureSkipVerify = true
// While packages like net/http will implicitly set ServerName, the
// VerifyPeerCertificate callback can't access that value, so it has to be set
// explicitly here or in VerifyPeerCertificate on the client side. If in
// an http.Transport DialTLS callback, this can be obtained by passing
// the addr argument to net.SplitHostPort.
transport.TLSClientConfig.ServerName = acceptInvalidHostname
// Approximately equivalent to what crypto/tls does normally:
// https://github.com/golang/go/commit/29cfb4d3c3a97b6f426d1b899234da905be699aa
transport.TLSClientConfig.VerifyPeerCertificate = func(certificates [][]byte, _ [][]*x509.Certificate) error {
certs := make([]*x509.Certificate, len(certificates))
for i, asn1Data := range certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return errors.New("tls: failed to parse certificate from server: " + err.Error())
}
certs[i] = cert
}

opts := x509.VerifyOptions{
Roots: transport.TLSClientConfig.RootCAs, // On the server side, use config.ClientCAs.
DNSName: acceptInvalidHostname,
Intermediates: x509.NewCertPool(),
// On the server side, set KeyUsages to ExtKeyUsageClientAuth. The
// default value is appropriate for clients side verification.
// KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := certs[0].Verify(opts)
return err
}
return nil
}
}

if certDirectory != "" {
files, err := ioutil.ReadDir(certDirectory)
if err != nil {
return nil, err
// WithTLSInsecureSkipVerify allows the client to call any host without checking the certificates.
func WithTLSInsecureSkipVerify() ClientOption {
return func(c *http.Client) error {
transport, ok := c.Transport.(*http.Transport)
if !ok {
return ErrInvalidTransportType
}

for _, f := range files {
if strings.Contains(f.Name(), ".pem") {
caCertFilePath := filepath.Join(certDirectory, f.Name())
if err := addCert(caCertFilePath, caCertPool); err != nil {
return nil, err
}
}
}
transport.TLSClientConfig.InsecureSkipVerify = true
return nil
}
}

// WithTimeout sets the timeout for the client, if not called the timeout will be 0 (no timeout).
func WithTimeout(httpTimeout time.Duration) ClientOption {
return func(c *http.Client) error {
c.Timeout = httpTimeout
return nil
}
}

func clientCertPool(c *http.Client) (*x509.CertPool, error) {
transport, ok := c.Transport.(*http.Transport)
if !ok {
return nil, ErrInvalidTransportType
}

if transport.TLSClientConfig.RootCAs == nil {
transport.TLSClientConfig.RootCAs = x509.NewCertPool()
}
return caCertPool, nil
return transport.TLSClientConfig.RootCAs, nil
}

func addCert(certFile string, caCertPool *x509.CertPool) error {
Expand Down
Loading