envoy: implement policy TLS options (#724)

* envoy: implement policy TLS options

* fix tests

* log which CAs are being used
This commit is contained in:
Caleb Doxsey 2020-05-18 16:52:51 -06:00 committed by GitHub
parent e24e026ffc
commit e854cfe83b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 258 additions and 161 deletions

View file

@ -2,12 +2,16 @@ package config
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"os"
"time"
"github.com/cespare/xxhash/v2"
"github.com/mitchellh/hashstructure"
"github.com/pomerium/pomerium/internal/cryptutil"
"github.com/pomerium/pomerium/internal/urlutil"
)
@ -21,8 +25,8 @@ type Policy struct {
AllowedGroups []string `mapstructure:"allowed_groups" yaml:"allowed_groups,omitempty" json:"allowed_groups,omitempty"`
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
Source *StringURL `yaml:",omitempty" json:"source,omitempty"`
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty"`
Source *StringURL `yaml:",omitempty" json:"source,omitempty" hash:"ignore"`
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty" hash:"ignore"`
// Additional route matching options
Prefix string `mapstructure:"prefix" yaml:"prefix,omitempty" json:"prefix,omitempty"`
@ -60,9 +64,8 @@ type Policy struct {
// TLSCustomCA defines the root certificate to use with a given
// route when verifying server certificates.
TLSCustomCA string `mapstructure:"tls_custom_ca" yaml:"tls_custom_ca,omitempty"`
TLSCustomCAFile string `mapstructure:"tls_custom_ca_file" yaml:"tls_custom_ca_file,omitempty"`
RootCAs *x509.CertPool `yaml:",omitempty"`
TLSCustomCA string `mapstructure:"tls_custom_ca" yaml:"tls_custom_ca,omitempty"`
TLSCustomCAFile string `mapstructure:"tls_custom_ca_file" yaml:"tls_custom_ca_file,omitempty"`
// Contains the x.509 client certificate to present to the downstream
// host.
@ -70,7 +73,7 @@ type Policy struct {
TLSClientKey string `mapstructure:"tls_client_key" yaml:"tls_client_key,omitempty"`
TLSClientCertFile string `mapstructure:"tls_client_cert_file" yaml:"tls_client_cert_file,omitempty"`
TLSClientKeyFile string `mapstructure:"tls_client_key_file" yaml:"tls_client_key_file,omitempty"`
ClientCertificate *tls.Certificate `yaml:",omitempty"`
ClientCertificate *tls.Certificate `yaml:",omitempty" hash:"ignore"`
// SetRequestHeaders adds a collection of headers to the downstream request
// in the form of key value pairs. Note bene, this will overwrite the
@ -127,19 +130,28 @@ func (p *Policy) Validate() error {
}
if p.TLSCustomCA != "" {
p.RootCAs, err = cryptutil.CertPoolFromBase64(p.TLSCustomCA)
_, err := base64.StdEncoding.DecodeString(p.TLSCustomCA)
if err != nil {
return fmt.Errorf("config: couldn't decode custom ca %w", err)
return fmt.Errorf("config: couldn't decode custom ca: %w", err)
}
} else if p.TLSCustomCAFile != "" {
p.RootCAs, err = cryptutil.CertPoolFromFile(p.TLSCustomCAFile)
_, err := os.Stat(p.TLSCustomCAFile)
if err != nil {
return fmt.Errorf("config: couldn't load custom ca file %w", err)
return fmt.Errorf("config: couldn't load client ca file: %w", err)
}
}
return nil
}
// Checksum returns the xxhash hash for the policy.
func (p *Policy) Checksum() uint64 {
cs, _ := hashstructure.Hash(p, &hashstructure.HashOptions{
Hasher: xxhash.New(),
})
return cs
}
func (p *Policy) String() string {
if p.Source == nil || p.Destination == nil {
return fmt.Sprintf("%s → %s", p.From, p.To)

View file

@ -34,15 +34,20 @@ func main() {
err = http.ListenAndServe(bindAddr, http.HandlerFunc(handle))
}
if err != nil {
fmt.Fprintf(os.Stderr, "failed to listen and serve: %v", err)
fmt.Fprintf(os.Stderr, "failed to listen and serve: %v\n", err)
os.Exit(1)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
conn, err := (&websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}).Upgrade(w, r, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "error upgrading websocket connection: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()

View file

@ -43,7 +43,7 @@ func bootstrapCerts(ctx context.Context) (*TLSCertsBundle, error) {
name string
}{
{&bundle.Trusted, filepath.Join(wd, "trusted"), true, "*.localhost.pomerium.io"},
{&bundle.WronglyNamed, filepath.Join(wd, "wrongly-named"), true, "*.localhost.notpomerium.io"},
{&bundle.WronglyNamed, filepath.Join(wd, "trusted"), true, "*.localhost.notpomerium.io"},
{&bundle.Untrusted, filepath.Join(wd, "untrusted"), false, "*.localhost.pomerium.io"},
}

View file

@ -185,8 +185,6 @@ func TestWebsocket(t *testing.T) {
}
func TestTLSSkipVerify(t *testing.T) {
t.SkipNow()
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
@ -221,13 +219,11 @@ func TestTLSSkipVerify(t *testing.T) {
}
defer res.Body.Close()
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
})
}
func TestTLSServerName(t *testing.T) {
t.SkipNow()
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
@ -262,13 +258,11 @@ func TestTLSServerName(t *testing.T) {
}
defer res.Body.Close()
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
})
}
func TestTLSCustomCA(t *testing.T) {
t.SkipNow()
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
@ -303,13 +297,11 @@ func TestTLSCustomCA(t *testing.T) {
}
defer res.Body.Close()
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
})
}
func TestTLSClientCert(t *testing.T) {
t.SkipNow()
ctx := mainCtx
ctx, clearTimeout := context.WithTimeout(ctx, time.Second*30)
defer clearTimeout()
@ -343,7 +335,7 @@ func TestTLSClientCert(t *testing.T) {
}
defer res.Body.Close()
assert.Equal(t, http.StatusBadGateway, res.StatusCode)
assert.Contains(t, []int{http.StatusBadGateway, http.StatusServiceUnavailable}, res.StatusCode)
})
}

View file

@ -1,20 +1,30 @@
package controlplane
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
"github.com/pomerium/pomerium/config"
xxhash "github.com/cespare/xxhash/v2"
envoy_config_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_extensions_access_loggers_grpc_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3"
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
)
func (srv *Server) buildDiscoveryResponse(version string, typeURL string, options *config.Options) (*envoy_service_discovery_v3.DiscoveryResponse, error) {
@ -117,3 +127,109 @@ func inlineBytes(bs []byte) *envoy_config_core_v3.DataSource {
},
}
}
func inlineBytesAsFilename(name string, bs []byte) *envoy_config_core_v3.DataSource {
ext := filepath.Ext(name)
name = fmt.Sprintf("%s-%x%s", name[:len(ext)], xxhash.Sum64(bs), ext)
cacheDir, err := os.UserCacheDir()
if err != nil {
cacheDir = filepath.Join(os.TempDir())
}
cacheDir = filepath.Join(cacheDir, "pomerium", "envoy", "files")
if err = os.MkdirAll(cacheDir, 0755); err != nil {
log.Error().Err(err).Msg("error creating cache directory, falling back to inline bytes")
return inlineBytes(bs)
}
fp := filepath.Join(cacheDir, name)
if _, err = os.Stat(fp); os.IsNotExist(err) {
err = ioutil.WriteFile(fp, bs, 0600)
if err != nil {
log.Error().Err(err).Msg("error writing cache file, falling back to inline bytes")
return inlineBytes(bs)
}
} else if err != nil {
log.Error().Err(err).Msg("error reading cache file, falling back to inline bytes")
return inlineBytes(bs)
}
return inlineFilename(fp)
}
func inlineFilename(name string) *envoy_config_core_v3.DataSource {
return &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_Filename{
Filename: name,
},
}
}
func getPolicyName(policy *config.Policy) string {
return fmt.Sprintf("policy-%x", policy.Checksum())
}
func envoyTLSCertificateFromGoTLSCertificate(cert *tls.Certificate) *envoy_extensions_transport_sockets_tls_v3.TlsCertificate {
envoyCert := &envoy_extensions_transport_sockets_tls_v3.TlsCertificate{}
var chain bytes.Buffer
for _, cbs := range cert.Certificate {
_ = pem.Encode(&chain, &pem.Block{
Type: "CERTIFICATE",
Bytes: cbs,
})
break
}
envoyCert.CertificateChain = inlineBytesAsFilename("tls-crt.pem", chain.Bytes())
if cert.OCSPStaple != nil {
envoyCert.OcspStaple = inlineBytes(cert.OCSPStaple)
}
if bs, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey); err == nil {
envoyCert.PrivateKey = inlineBytesAsFilename("tls-key.pem", pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: bs,
},
))
} else {
log.Warn().Err(err).Msg("failed to marshal private key for tls config")
}
for _, scts := range cert.SignedCertificateTimestamps {
envoyCert.SignedCertificateTimestamp = append(envoyCert.SignedCertificateTimestamp,
inlineBytes(scts))
}
return envoyCert
}
var rootCABundle string
func init() {
// from https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ssl#arch-overview-ssl-enabling-verification
knownRootLocations := []string{
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem",
"/usr/local/etc/ssl/cert.pem",
"/etc/ssl/cert.pem",
}
for _, path := range knownRootLocations {
if _, err := os.Stat(path); err == nil {
rootCABundle = path
break
}
}
if rootCABundle == "" {
log.Error().Strs("known-locations", knownRootLocations).
Msgf("no root certificates were found in any of the known locations")
} else {
log.Info().Msgf("using %s as the system root certificate authority bundle", rootCABundle)
}
}
func getRootCertificateAuthority() (string, error) {
if rootCABundle == "" {
return "", fmt.Errorf("root certificates not found")
}
return rootCABundle, nil
}

View file

@ -1,23 +1,26 @@
package controlplane
import (
"encoding/base64"
"net"
"net/url"
"strings"
"time"
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_config_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_type_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/golang/protobuf/ptypes"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/urlutil"
)
func (srv *Server) buildClusters(options *config.Options) []*envoy_config_cluster_v3.Cluster {
grpcURL := &url.URL{
Scheme: "grpc",
Scheme: "http",
Host: srv.GRPCListener.Addr().String(),
}
httpURL := &url.URL{
@ -25,46 +28,116 @@ func (srv *Server) buildClusters(options *config.Options) []*envoy_config_cluste
Host: srv.HTTPListener.Addr().String(),
}
authzURL := &url.URL{
Scheme: strings.Replace(options.AuthorizeURL.Scheme, "http", "grpc", -1),
Scheme: options.AuthorizeURL.Scheme,
Host: options.AuthorizeURL.Host,
}
clusters := []*envoy_config_cluster_v3.Cluster{
srv.buildCluster("pomerium-control-plane-grpc", grpcURL),
srv.buildCluster("pomerium-control-plane-http", httpURL),
srv.buildCluster("pomerium-authz", authzURL),
srv.buildInternalCluster("pomerium-control-plane-grpc", grpcURL, true),
srv.buildInternalCluster("pomerium-control-plane-http", httpURL, false),
srv.buildInternalCluster("pomerium-authz", authzURL, true),
}
if config.IsProxy(options.Services) {
type clusterDestination struct {
name, scheme, hostport string
}
clusterDestinations := map[clusterDestination]struct{}{}
for _, policy := range options.Policies {
name, scheme, hostport := srv.getClusterDetails(policy.Destination)
clusterDestinations[clusterDestination{name, scheme, hostport}] = struct{}{}
}
for dst := range clusterDestinations {
name, scheme, hostport := dst.name, dst.scheme, dst.hostport
clusters = append(clusters, srv.buildCluster(name, &url.URL{
Scheme: scheme,
Host: hostport,
}))
clusters = append(clusters, srv.buildPolicyCluster(&policy))
}
}
return clusters
}
func (srv *Server) getClusterDetails(endpoint *url.URL) (name, scheme, hostport string) {
name = endpoint.Scheme + "-" + strings.Replace(endpoint.Host, ":", "--", -1)
return name, endpoint.Scheme, endpoint.Host
func (srv *Server) buildInternalCluster(name string, endpoint *url.URL, forceHTTP2 bool) *envoy_config_cluster_v3.Cluster {
var transportSocket *envoy_config_core_v3.TransportSocket
if endpoint.Scheme == "https" {
transportSocket = &envoy_config_core_v3.TransportSocket{
Name: "tls",
}
}
return srv.buildCluster(name, endpoint, transportSocket, forceHTTP2)
}
func (srv *Server) buildCluster(name string, endpoint *url.URL) *envoy_config_cluster_v3.Cluster {
func (srv *Server) buildPolicyCluster(policy *config.Policy) *envoy_config_cluster_v3.Cluster {
name := getPolicyName(policy)
return srv.buildCluster(name, policy.Destination, srv.buildPolicyTransportSocket(policy), false)
}
func (srv *Server) buildPolicyTransportSocket(policy *config.Policy) *envoy_config_core_v3.TransportSocket {
if policy.Destination.Scheme != "https" {
return nil
}
sni := policy.Destination.Hostname()
if policy.TLSServerName != "" {
sni = policy.TLSServerName
}
tlsContext := &envoy_extensions_transport_sockets_tls_v3.UpstreamTlsContext{
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
AlpnProtocols: []string{"http/1.1"},
ValidationContextType: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext_ValidationContext{
ValidationContext: srv.buildPolicyValidationContext(policy),
},
},
Sni: sni,
}
if policy.ClientCertificate != nil {
tlsContext.CommonTlsContext.TlsCertificates = append(tlsContext.CommonTlsContext.TlsCertificates,
envoyTLSCertificateFromGoTLSCertificate(policy.ClientCertificate))
}
tlsConfig, _ := ptypes.MarshalAny(tlsContext)
return &envoy_config_core_v3.TransportSocket{
Name: "tls",
ConfigType: &envoy_config_core_v3.TransportSocket_TypedConfig{
TypedConfig: tlsConfig,
},
}
}
func (srv *Server) buildPolicyValidationContext(policy *config.Policy) *envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext {
sni := policy.Destination.Hostname()
if policy.TLSServerName != "" {
sni = policy.TLSServerName
}
validationContext := &envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext{
MatchSubjectAltNames: []*envoy_type_matcher_v3.StringMatcher{{
MatchPattern: &envoy_type_matcher_v3.StringMatcher_Exact{
Exact: sni,
},
}},
}
if policy.TLSCustomCAFile != "" {
validationContext.TrustedCa = inlineFilename(policy.TLSCustomCAFile)
} else if policy.TLSCustomCA != "" {
bs, err := base64.StdEncoding.DecodeString(policy.TLSCustomCA)
if err != nil {
log.Error().Err(err).Msg("invalid custom CA certificate")
}
validationContext.TrustedCa = inlineBytesAsFilename("custom-ca.pem", bs)
} else {
rootCA, err := getRootCertificateAuthority()
if err != nil {
log.Error().Err(err).Msg("unable to enable certificate verification because no root CAs were found")
} else {
validationContext.TrustedCa = inlineFilename(rootCA)
}
}
if policy.TLSSkipVerify {
validationContext.TrustChainVerification = envoy_extensions_transport_sockets_tls_v3.CertificateValidationContext_ACCEPT_UNTRUSTED
}
return validationContext
}
func (srv *Server) buildCluster(
name string,
endpoint *url.URL,
transportSocket *envoy_config_core_v3.TransportSocket,
forceHTTP2 bool,
) *envoy_config_cluster_v3.Cluster {
defaultPort := 80
if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" {
if transportSocket != nil && transportSocket.Name == "tls" {
defaultPort = 443
}
@ -83,16 +156,13 @@ func (srv *Server) buildCluster(name string, endpoint *url.URL) *envoy_config_cl
}},
}},
},
RespectDnsTtl: true,
RespectDnsTtl: true,
TransportSocket: transportSocket,
}
if endpoint.Scheme == "grpc" {
cluster.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{}
}
if endpoint.Scheme == "https" || endpoint.Scheme == "grpcs" {
cluster.TransportSocket = &envoy_config_core_v3.TransportSocket{
Name: "tls",
if forceHTTP2 {
cluster.Http2ProtocolOptions = &envoy_config_core_v3.Http2ProtocolOptions{
AllowConnect: true,
}
}

View file

@ -1,9 +1,6 @@
package controlplane
import (
"bytes"
"crypto/x509"
"encoding/pem"
"sort"
"time"
@ -309,33 +306,7 @@ func (srv *Server) buildDownstreamTLSContext(options *config.Options, domain str
return nil
}
envoyCert := &envoy_extensions_transport_sockets_tls_v3.TlsCertificate{}
var chain bytes.Buffer
for _, cbs := range cert.Certificate {
_ = pem.Encode(&chain, &pem.Block{
Type: "CERTIFICATE",
Bytes: cbs,
})
}
envoyCert.CertificateChain = inlineBytes(chain.Bytes())
if cert.OCSPStaple != nil {
envoyCert.OcspStaple = inlineBytes(cert.OCSPStaple)
}
if bs, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey); err == nil {
envoyCert.PrivateKey = inlineBytes(pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: bs,
},
))
} else {
log.Warn().Err(err).Msg("failed to marshal private key for tls config")
}
for _, scts := range cert.SignedCertificateTimestamps {
envoyCert.SignedCertificateTimestamp = append(envoyCert.SignedCertificateTimestamp,
inlineBytes(scts))
}
envoyCert := envoyTLSCertificateFromGoTLSCertificate(cert)
return &envoy_extensions_transport_sockets_tls_v3.DownstreamTlsContext{
CommonTlsContext: &envoy_extensions_transport_sockets_tls_v3.CommonTlsContext{
TlsCertificates: []*envoy_extensions_transport_sockets_tls_v3.TlsCertificate{envoyCert},

View file

@ -121,7 +121,7 @@ func (srv *Server) buildPolicyRoutes(options *config.Options, domain string) []*
match.PathSpecifier = &envoy_config_route_v3.RouteMatch_Prefix{Prefix: "/"}
}
clusterName, _, _ := srv.getClusterDetails(policy.Destination)
clusterName := getPolicyName(&policy)
var requestHeadersToAdd []*envoy_config_core_v3.HeaderValueOption
for k, v := range policy.SetRequestHeaders {

View file

@ -11,7 +11,6 @@ import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"time"
@ -38,33 +37,6 @@ func CertificateFromFile(certFile, keyFile string) (*tls.Certificate, error) {
return &cert, err
}
// CertPoolFromBase64 takes a base64 encoded string and returns a new
// X509 certificate pool.
func CertPoolFromBase64(encPemCerts string) (*x509.CertPool, error) {
b, err := base64.StdEncoding.DecodeString(encPemCerts)
if err != nil {
return nil, fmt.Errorf("couldn't decode pem %v: %w", b, err)
}
return bytesToCertPool(b)
}
// CertPoolFromFile reads a file and returns an X509 certificate pool.
func CertPoolFromFile(pemFile string) (*x509.CertPool, error) {
b, err := ioutil.ReadFile(pemFile)
if err != nil {
return nil, err
}
return bytesToCertPool(b)
}
func bytesToCertPool(b []byte) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(b); !ok {
return nil, fmt.Errorf("could append certs from PEM")
}
return certPool, nil
}
// DecodePublicKey decodes a PEM-encoded ECDSA public key.
func DecodePublicKey(encodedKey []byte) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode(encodedKey)

View file

@ -64,47 +64,6 @@ func TestCertifcateFromBase64(t *testing.T) {
}
}
func TestCertPoolFromBase64(t *testing.T) {
tests := []struct {
name string
encPemCerts string
wantErr bool
}{
{"good", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lKQUszMmhoR0JIcmFtTUEwR0NTcUdTSWIzRFFFQkN3VUFNR0l4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIREExVFlXNGdSbkpoYm1OcApjMk52TVE4d0RRWURWUVFLREFaQ1lXUlRVMHd4RlRBVEJnTlZCQU1NRENvdVltRmtjM05zTG1OdmJUQWVGdzB4Ck9UQTJNVEl4TlRNeE5UbGFGdzB5TVRBMk1URXhOVE14TlRsYU1HSXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWUQKVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1ROHdEUVlEVlFRSwpEQVpDWVdSVFUwd3hGVEFUQmdOVkJBTU1EQ291WW1Ga2MzTnNMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1JRTdQaU03Z1RDczloUTFYQll6Sk1ZNjF5b2FFbXdJclg1bFo2eEt5eDIKUG16QVMyQk1UT3F5dE1BUGdMYXcrWExKaGdMNVhFRmRFeXQvY2NSTHZPbVVMbEEzcG1jY1lZejJRVUxGUnRNVwpoeWVmZE9zS25SRlNKaUZ6YklSTWVWWGswV3ZvQmoxSUZWS3RzeWpicXY5dS8yQ1ZTbmRyT2ZFazBURzIzVTNBCnhQeFR1VzFDcmJWOC9xNzFGZEl6U09jaWNjZkNGSHBzS09vM1N0L3FiTFZ5dEg1YW9oYmNhYkZYUk5zS0VxdmUKd3c5SGRGeEJJdUdhK1J1VDVxMGlCaWt1c2JwSkhBd25ucVA3aS9kQWNnQ3NrZ2paakZlRVU0RUZ5K2IrYTFTWQpRQ2VGeHhDN2MzRHZhUmhCQjBWVmZQbGtQejBzdzZsODY1TWFUSWJSeW9VQ0F3RUFBYU15TURBd0NRWURWUjBUCkJBSXdBREFqQmdOVkhSRUVIREFhZ2d3cUxtSmhaSE56YkM1amIyMkNDbUpoWkhOemJDNWpiMjB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFJaTV1OXc4bWdUNnBwQ2M3eHNHK0E5ZkkzVzR6K3FTS2FwaHI1bHM3MEdCS2JpWQpZTEpVWVpoUGZXcGgxcXRra1UwTEhGUG04M1ZhNTJlSUhyalhUMFZlNEt0TzFuMElBZkl0RmFXNjJDSmdoR1luCmp6dzByeXpnQzRQeUZwTk1uTnRCcm9QdS9iUGdXaU1nTE9OcEVaaGlneDRROHdmMVkvVTlzK3pDQ3hvSmxhS1IKTVhidVE4N1g3bS85VlJueHhvNk56NVpmN09USFRwTk9JNlZqYTBCeGJtSUFVNnlyaXc5VXJnaWJYZk9qM2o2bgpNVExCdWdVVklCMGJCYWFzSnNBTUsrdzRMQU52YXBlWjBET1NuT1I0S0syNEowT3lvRjVmSG1wNTllTTE3SW9GClFxQmh6cG1RVWd1bmVjRVc4QlRxck5wRzc5UjF1K1YrNHd3Y2tQYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", false},
{"bad base64", "!", true},
{"bad certificate", "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURlVENDQW1HZ0F3SUJBZ0lKQUszMmhoR0JIcmFtTUEwR0NTcUdTSWIzRFFFQkN3VUFNR0l4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIREExVFlXNGdSbkpoYm1OcApjMk52TVE4d0RRWURWUVFLREFaQ1lXUlRVMHd4RlRBVEJnTlZCQU1NRENvdVltRmtjM05zTG1OdmJUQWVGdzB4Ck9UQTJNVEl4TlRNeE5UbGFGdzB5TVRBMk1URXhOVE14TlRsYU1HSXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWUQKVlFRSURBcERZV3hwWm05eWJtbGhNUll3RkFZRFZRUUhEQTFUWVc0Z1JuSmhibU5wYzJOdk1ROHdEUVlEVlFRSwpEQVpDWVdSVFUwd3hGVEFUQmdOVkJBTU1EQ291WW1Ga2MzTnNMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCCkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1JRTdQaU03Z1RDczloUTFYQll6Sk1ZNjF5b2FFbXdJclg1bFo2eEt5eDIKUG16QVMyQk1UT3F5dE1BUGdMYXcrWExKaGdMNVhFRmRFeXQvY2NSTHZPbVVMbEEzcG1jY1lZejJRVUxGUnRNVwpoeWVmZE9zS25SRlNKaUZ6YklSTWVWWGswV3ZvQmoxSUZWS3RzeWpicXY5dS8yQ1ZTbmRyT2ZFazBURzIzVTNBCnhQeFR1VzFDcmJWOC9xNzFGZEl6U09jaWNjZkNGSHBzS09vM1N0L3FiTFZ5dEg1YW9oYmNhYkZYUk5zS0VxdmUKd3c5SGRGeEJJdUdhK1J1VDVxMGlCaWt1c2JwSkhBd25ucVA3aS9kQWNnQ3NrZ2paakZlRVU0RUZ5K2IrYTFTWQpRQ2VGeHhDN2MzRHZhUmhCQjBWVmZQbGtQejBzdzZsODY1TWFUSWJSeW9VQ0F3RUFBYU15TURBd0NRWURWUjBUCkJBSXdBREFqQmdOVkhSRUVIREFhZ2d3cUxtSmhaSE56YkM1amIyMkNDbUpoWkhOemJDNWpiMjB3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFJaTV1OXc4bWdUNnBwQ2M3eHNHK0E5ZkkzVzR6K3FTS2FwaHI1bHM3MEdCS2JpWQpZTEpVWVpoUGZXcGgxcXRra1UwTEhGUG04M1ZhNTJlSUhyalhUMFZlNEt0TzFuMElBZkl0RmFXNjJDSmdoR1luCmp6dzByeXpnQzRQeUZwTk1uTnRCcm9QdS9iUGdXaU1nTE9OcEVaaGlneDRROHdmMVkvVTlzK3pDQ3hvSmxhS1IKTVhidVE4N1g3bS85VlJueHhvNk56NVpmN09USFRwTk9JNlZqYTBCeGJtSUFVNnlyaXc5VXJnaWJYZk9qM2o2bgpNVExCdWdVVklCMGJCYWFzSnNBTUsrdzRMQU52YXBlWjBET1NuT1I0S0syNEowT3lvRjVmSG1wNTllTTE3SW9GClFxQmh6cG1RVWd1bmVjRVD4QlRxck5wRzc5UjF1K1YrNHd3Y2tQYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := CertPoolFromBase64(tt.encPemCerts)
if (err != nil) != tt.wantErr {
t.Errorf("CertPoolFromBase64() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestCertPoolFromFile(t *testing.T) {
tests := []struct {
name string
pemFile string
wantErr bool
}{
{"good", "./testdata/ca.pem", false},
{"bad doesn't exist", "./testdata/no-exist.pem", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := CertPoolFromFile(tt.pemFile)
if (err != nil) != tt.wantErr {
t.Errorf("CertPoolFromFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestCertificateFromFile(t *testing.T) {
cert, err := CertificateFromFile("testdata/example-cert.pem", "testdata/example-key.pem")
if err != nil {