webhooks, aggregation: add metrics to count certs with SHA1 signatures

Extends the certificate attribute deprecation RoundTrippers wrapper with
a checker that counts certificates with sha-1 signatures in server responses.

Non-root non-self-signed SHA-1 certificate signatures were deprecated in
Golang 1.18.
This commit is contained in:
Stanislav Laznicka 2022-03-24 01:08:02 -04:00
parent b2c6de170b
commit 499ee65a9b
No known key found for this signature in database
GPG Key ID: C98C414936B1A7F3
9 changed files with 674 additions and 139 deletions

View File

@ -67,34 +67,6 @@ koodzngbUuyaGFI8QISUAhU+pbt6npb69LlhJHC0icDXCc4uTsd+SDsBOorZDuUL
HsNKnE0CSPZ65ENVNfJjB1fVEYbbpjr8kizOCDE=
-----END CERTIFICATE-----`)
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzOC/5s1eAjTsIGOodGJ7/ZHcS2m7t5UL38u4Gh74qlS6ezev
YjHRsqDVUiMMt2J1wWYCrH+pKLRJZ0S0B89apagsjoZViYFRnNwkcDEywkksilUI
hmxC36TgPPQQIbAH9DnTUSz2ESJMdlJec83xdI7R/2basg1PfJYJr5TIyXGiUgkC
RiVqNT8FTzEg5xIoAJoTaI0q/euz7qog7t2k/6O1RYj9W2N23k9Jjv5ZSXnKHhap
MyQwp8CidLK8LHPIxvUch7dMA3R1O6U3890B73HuosnHTzCO7a/l4GHDBCfRk70U
880T/jjSfBqdSYm1AGHjqG2AiY+bPePZVC9EtwIDAQABAoIBACapNq1IDbhe4jwO
ckhcGUe1UZvNfQXck1wM5lXPiF8kE/iSfn8KJacOoJKaWJj2dIAlTtXcVUCAHOXm
2g4rO9DM1nOit9t10r/F7v5Y2lo9UrFb8HrlTQ1E4Ke/6pdgz29vxgeoc1qyXTlq
u7Ygo7K6DLikYRp+VoSND5DZg7Y3a3/W7xecdrV1gH3XOn1fyw4IZ3u1GaufA6Jx
iJqA2RRIhHWbL9nXeRQJyACf2kYnQK8nyRhtHGiqWdLdE/T7StyqtELA41zwBip4
vUK8l71FUD121AC7MXBGJGxmPHSat7CMmn0rtm7gksKCHjdJA3f631CVEj1OmMqT
zgULsckCgYEA+qfZpJZAg+g/dtGb26oNAxiY+Q2DGF3qZQQGk8sK8ZiIEIDL4Zul
+F9N3IK0RFqojjL2p8ktvOnspPTqoSqBjRDr2YzDsCbXR0P1Q9N/9mAvJDSrtywF
V6Ru4XkcGLOOCU5/8eXrd6e/40I0Q25amAcVAW42cL5xAkc59O5dWmMCgYEA0T8I
FAuFy8kM7hPE6mSnvFpXzj0NpWVDqwCjxy7DnnOOo4ttUfZciSq3kQ7b9WK1BeHi
C2MWALGz5Qw21bj+TgLNO39+Ah/nCTGRMtaciy3yq5IX0pV+A3Frz5X6m/PBqmT8
p6TdYBZ84AKCKuEEShm7BgzVB2w2VrLLvtRysp0CgYEA8oBj631GS5ftlpnybVIX
JrCshv/Qnkl2VWbQqjodi2Hj+ftxxQLeTu59mOKHXz5KptCdm/TiIEw9G9asDWLf
VchSNfae+JSLYLJZ3tYHjii+Uwv2OdAIFyPJ9rBCQ4+r0ks1M6Ya6nfMCSVrBPYG
BeGMLAch+m/1S5v3cYUPojsCgYArTPnk/AVyCGbulZS4VKAJcECfYy8BvRTlvDoo
K9s7XPp1iZLT1UGM+RQHpqWKACUp1HasmJKjDiMGESL/00p85kOGPnnbArMSyfkc
JiE7BAUl0BOx9lGfcMc4q/aycxzun/tQzeMp0T2CNcKuEOaMVwrG07z5zkDobBOR
p/EcWQKBgCV2nO7Z/oyqaD00VBBm118/ZqbZG0Znxm/oxI4bG+OGRWtJc0nCVYDT
bZP4QR5baZ7KOtKAP+ksyZpyKftAgNH+TrbTTg1gJcv3gfeTivbmXyoMjwbFlu8H
iQRXj801qb2ME3+HlT0Ti8Qf3BEPrDnGf8qVSkQPjHkIbKr/3/Zv
-----END RSA PRIVATE KEY-----`)
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIUDr7CjUg6evYtl4uqdgyu94ACVOAwDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMjAzMjUxNTMzMjla

View File

@ -148,8 +148,11 @@ func (cm *ClientManager) HookClient(cc ClientConfig) (*rest.RESTClient, error) {
cfg.ContentConfig.ContentType = runtime.ContentTypeJSON
// Add a transport wrapper that allows detection of TLS connections to
// servers without SAN extension in their serving certificates
cfg.Wrap(x509metrics.NewMissingSANRoundTripperWrapperConstructor(x509MissingSANCounter))
// servers with serving certificates with deprecated characteristics
cfg.Wrap(x509metrics.NewDeprecatedCertificateRoundTripperWrapperConstructor(
x509MissingSANCounter,
x509InsecureSHA1Counter,
))
client, err := rest.UnversionedRESTClientFor(cfg)
if err == nil {

View File

@ -34,6 +34,19 @@ var x509MissingSANCounter = metrics.NewCounter(
},
)
var x509InsecureSHA1Counter = metrics.NewCounter(
&metrics.CounterOpts{
Subsystem: "webhooks",
Namespace: "apiserver",
Name: "x509_insecure_sha1_total",
Help: "Counts the number of requests to servers with insecure SHA1 signatures " +
"in their serving certificate OR the number of connection failures " +
"due to the insecure SHA1 signatures (either/or, based on the runtime environment)",
StabilityLevel: metrics.ALPHA,
},
)
func init() {
legacyregistry.MustRegister(x509MissingSANCounter)
legacyregistry.MustRegister(x509InsecureSHA1Counter)
}

View File

@ -85,7 +85,10 @@ func NewGenericWebhook(scheme *runtime.Scheme, codecFactory serializer.CodecFact
codec := codecFactory.LegacyCodec(groupVersions...)
clientConfig.ContentConfig.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
clientConfig.Wrap(x509metrics.NewMissingSANRoundTripperWrapperConstructor(x509MissingSANCounter))
clientConfig.Wrap(x509metrics.NewDeprecatedCertificateRoundTripperWrapperConstructor(
x509MissingSANCounter,
x509InsecureSHA1Counter,
))
restClient, err := rest.UnversionedRESTClientFor(clientConfig)
if err != nil {

View File

@ -334,11 +334,12 @@ func TestMissingKubeConfigFile(t *testing.T) {
func TestTLSConfig(t *testing.T) {
invalidCert := []byte("invalid")
tests := []struct {
test string
clientCert, clientKey, clientCA []byte
serverCert, serverKey, serverCA []byte
errRegex string
increaseSANWarnCounter bool
test string
clientCert, clientKey, clientCA []byte
serverCert, serverKey, serverCA []byte
errRegex string
increaseSANWarnCounter bool
increaseSHA1SignatureWarnCounter bool
}{
{
test: "invalid server CA",
@ -402,8 +403,23 @@ func TestTLSConfig(t *testing.T) {
errRegex: "x509: certificate relies on legacy Common Name field",
increaseSANWarnCounter: true,
},
{
test: "server cert with SHA1 signature",
clientCA: caCert,
serverCert: append(append(sha1ServerCertInter, byte('\n')), caCertInter...), serverKey: serverKey,
errRegex: "x509: cannot verify signature: insecure algorithm SHA1-RSA \\(temporarily override with GODEBUG=x509sha1=1\\)",
increaseSHA1SignatureWarnCounter: true,
},
{
test: "server cert signed by an intermediate CA with SHA1 signature",
clientCA: caCert,
serverCert: append(append(serverCertInterSHA1, byte('\n')), caCertInterSHA1...), serverKey: serverKey,
errRegex: "x509: cannot verify signature: insecure algorithm SHA1-RSA \\(temporarily override with GODEBUG=x509sha1=1\\)",
increaseSHA1SignatureWarnCounter: true,
},
}
lastSHA1SigCounter := 0
for _, tt := range tests {
// Use a closure so defer statements trigger between loop iterations.
func() {
@ -483,6 +499,20 @@ func TestTLSConfig(t *testing.T) {
t.Errorf("expected the x509_common_name_error_count to be 1, but it's %d", errorCounter)
}
}
if tt.increaseSHA1SignatureWarnCounter {
errorCounter := getSingleCounterValueFromRegistry(t, legacyregistry.DefaultGatherer, "apiserver_webhooks_x509_insecure_sha1_total")
if errorCounter == -1 {
t.Errorf("failed to get the apiserver_webhooks_x509_insecure_sha1_total metrics: %v", err)
}
if int(errorCounter) != lastSHA1SigCounter+1 {
t.Errorf("expected the apiserver_webhooks_x509_insecure_sha1_total counter to be 1, but it's %d", errorCounter)
}
lastSHA1SigCounter++
}
}()
}
}

View File

@ -20,64 +20,139 @@ import (
"crypto/x509"
"errors"
"net/http"
"reflect"
"strings"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/component-base/metrics"
"k8s.io/klog/v2"
)
var _ utilnet.RoundTripperWrapper = &x509MissingSANErrorMetricsRTWrapper{}
var _ utilnet.RoundTripperWrapper = &x509DeprecatedCertificateMetricsRTWrapper{}
type x509MissingSANErrorMetricsRTWrapper struct {
type x509DeprecatedCertificateMetricsRTWrapper struct {
rt http.RoundTripper
counter *metrics.Counter
checkers []deprecatedCertificateAttributeChecker
}
// NewMissingSANRoundTripperWrapperConstructor returns a RoundTripper wrapper that's usable
// within ClientConfig.Wrap that increases the `metricCounter` whenever:
type deprecatedCertificateAttributeChecker interface {
// CheckRoundTripError returns true if the err is an error specific
// to this deprecated certificate attribute
CheckRoundTripError(err error) bool
// CheckPeerCertificates returns true if the deprecated attribute/value pair
// was found in a given certificate in the http.Response.TLS.PeerCertificates bundle
CheckPeerCertificates(certs []*x509.Certificate) bool
// IncreaseCounter increases the counter internal to this interface
// Use the req to derive and log information useful for troubleshooting the certificate issue
IncreaseMetricsCounter(req *http.Request)
}
// counterRaiser is a helper structure to include in certificate deprecation checkers.
// It implements the IncreaseMetricsCounter() method so that, when included in the checker,
// it does not have to be reimplemented.
type counterRaiser struct {
counter *metrics.Counter
reason string
}
func (c *counterRaiser) IncreaseMetricsCounter(req *http.Request) {
if req != nil && req.URL != nil {
if hostname := req.URL.Hostname(); len(hostname) > 0 {
klog.Infof("invalid certificate detected while connecting to %q: %s", req.URL.Hostname(), c.reason)
}
}
c.counter.Inc()
}
// NewDeprecatedCertificateRoundTripperWrapperConstructor returns a RoundTripper wrapper that's usable within ClientConfig.Wrap.
//
// It increases the `missingSAN` counter whenever:
// 1. we get a x509.HostnameError with string `x509: certificate relies on legacy Common Name field`
// which indicates an error caused by the deprecation of Common Name field when veryfing remote
// hostname
// 2. the server certificate in response contains no SAN. This indicates that this binary run
// with the GODEBUG=x509ignoreCN=0 in env
func NewMissingSANRoundTripperWrapperConstructor(metricCounter *metrics.Counter) func(rt http.RoundTripper) http.RoundTripper {
//
// It increases the `sha1` counter whenever:
// 1. we get a x509.InsecureAlgorithmError with string `SHA1`
// which indicates an error caused by an insecure SHA1 signature
// 2. the server certificate in response contains a SHA1WithRSA or ECDSAWithSHA1 signature.
// This indicates that this binary run with the GODEBUG=x509sha1=1 in env
func NewDeprecatedCertificateRoundTripperWrapperConstructor(missingSAN, sha1 *metrics.Counter) func(rt http.RoundTripper) http.RoundTripper {
return func(rt http.RoundTripper) http.RoundTripper {
return &x509MissingSANErrorMetricsRTWrapper{
rt: rt,
counter: metricCounter,
return &x509DeprecatedCertificateMetricsRTWrapper{
rt: rt,
checkers: []deprecatedCertificateAttributeChecker{
NewSANDeprecatedChecker(missingSAN),
NewSHA1SignatureDeprecatedChecker(sha1),
},
}
}
}
func (w *x509MissingSANErrorMetricsRTWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
func (w *x509DeprecatedCertificateMetricsRTWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := w.rt.RoundTrip(req)
checkForHostnameError(err, w.counter)
checkRespForNoSAN(resp, w.counter)
if err != nil {
for _, checker := range w.checkers {
if checker.CheckRoundTripError(err) {
checker.IncreaseMetricsCounter(req)
}
}
} else if resp != nil {
if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 {
for _, checker := range w.checkers {
if checker.CheckPeerCertificates(resp.TLS.PeerCertificates) {
checker.IncreaseMetricsCounter(req)
}
}
}
}
return resp, err
}
func (w *x509MissingSANErrorMetricsRTWrapper) WrappedRoundTripper() http.RoundTripper {
func (w *x509DeprecatedCertificateMetricsRTWrapper) WrappedRoundTripper() http.RoundTripper {
return w.rt
}
// checkForHostnameError increases the metricCounter when we're running w/o GODEBUG=x509ignoreCN=0
// and the client reports a HostnameError about the legacy CN fields
func checkForHostnameError(err error, metricCounter *metrics.Counter) {
if err != nil && errors.As(err, &x509.HostnameError{}) && strings.Contains(err.Error(), "x509: certificate relies on legacy Common Name field") {
// increase the count of registered failures due to Go 1.15 x509 cert Common Name deprecation
metricCounter.Inc()
var _ deprecatedCertificateAttributeChecker = &missingSANChecker{}
type missingSANChecker struct {
counterRaiser
}
func NewSANDeprecatedChecker(counter *metrics.Counter) *missingSANChecker {
return &missingSANChecker{
counterRaiser: counterRaiser{
counter: counter,
reason: "relies on a legacy Common Name field instead of the SAN extension for subject validation",
},
}
}
// checkRespForNoSAN increases the metricCounter when the server response contains
// CheckRoundTripError returns true when we're running w/o GODEBUG=x509ignoreCN=0
// and the client reports a HostnameError about the legacy CN fields
func (c *missingSANChecker) CheckRoundTripError(err error) bool {
if err != nil && errors.As(err, &x509.HostnameError{}) && strings.Contains(err.Error(), "x509: certificate relies on legacy Common Name field") {
// increase the count of registered failures due to Go 1.15 x509 cert Common Name deprecation
return true
}
return false
}
// CheckPeerCertificates returns true when the server response contains
// a leaf certificate w/o the SAN extension
func checkRespForNoSAN(resp *http.Response, metricCounter *metrics.Counter) {
if resp != nil && resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 {
if serverCert := resp.TLS.PeerCertificates[0]; !hasSAN(serverCert) {
metricCounter.Inc()
func (c *missingSANChecker) CheckPeerCertificates(peerCertificates []*x509.Certificate) bool {
if len(peerCertificates) > 0 {
if serverCert := peerCertificates[0]; !hasSAN(serverCert) {
return true
}
}
return false
}
func hasSAN(c *x509.Certificate) bool {
@ -90,3 +165,52 @@ func hasSAN(c *x509.Certificate) bool {
}
return false
}
type sha1SignatureChecker struct {
*counterRaiser
}
func NewSHA1SignatureDeprecatedChecker(counter *metrics.Counter) *sha1SignatureChecker {
return &sha1SignatureChecker{
counterRaiser: &counterRaiser{
counter: counter,
reason: "uses an insecure SHA-1 signature",
},
}
}
// CheckRoundTripError returns true when we're running w/o GODEBUG=x509sha1=1
// and the client reports an InsecureAlgorithmError about a SHA1 signature
func (c *sha1SignatureChecker) CheckRoundTripError(err error) bool {
var unknownAuthorityError x509.UnknownAuthorityError
if err == nil {
return false
}
if !errors.As(err, &unknownAuthorityError) {
return false
}
errMsg := err.Error()
if strIdx := strings.Index(errMsg, "x509: cannot verify signature: insecure algorithm"); strIdx != -1 && strings.Contains(errMsg[strIdx:], "SHA1") {
// increase the count of registered failures due to Go 1.18 x509 sha1 signature deprecation
return true
}
return false
}
// CheckPeerCertificates returns true when the server response contains
// a non-root non-self-signed certificate with a deprecated SHA1 signature
func (c *sha1SignatureChecker) CheckPeerCertificates(peerCertificates []*x509.Certificate) bool {
// check all received non-self-signed certificates for deprecated signing algorithms
for _, cert := range peerCertificates {
if cert.SignatureAlgorithm == x509.SHA1WithRSA || cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
// the SHA-1 deprecation does not involve self-signed root certificates
if !reflect.DeepEqual(cert.Issuer, cert.Subject) {
return true
}
}
}
return false
}

View File

@ -19,103 +19,189 @@ package x509metrics
import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"github.com/stretchr/testify/require"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/testutil"
)
// taken from pkg/util/webhook/certs_test.go
var caCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIUOS2MkobR2t4rguefcC78gLuXkc0wDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMDEwMDcxMjMxNDFa
GA8yMjk0MDcyMzEyMzE0MVowGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy8o1sRSe8viuSxh8a7Ou8n
vyDJxeJA9son+clZdSwa1YpbhSsYPr2svTGLT4ZxX3JkvHZGDaB+G6QrZNIWOssJ
4+fjvEsVSnk9q7Yr8wX8QdShksnSP0qdB1xFKlqwTFcZyQHqjlyctV93/LEosM9D
RZzgPpXJuC6wD7qdLRSPxQwuegZYcMGT2Y4/8CfBgiEUkZNrGYJiDFrblI0N9jX2
jUeP/g8xWJIKLTBedVbAzj02Y66WdNecYcyTMROdclPB1IbF4ABbCGynnP0fbiaf
0+4v0pLedqwCYSWD/ujajyDtYuhI8U+OkENwkZ/h5jw/6aWq7esMZDbEAT6UPgEC
AwEAAaNTMFEwHQYDVR0OBBYEFBgvyZWkRJCRjxclYA0mMlnzc/GmMB8GA1UdIwQY
MBaAFBgvyZWkRJCRjxclYA0mMlnzc/GmMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBAHigaFJ8DqYFMGXNUT5lYM8oLLWYcbivGxR7ofDm8rXWGLdQ
tKsWeusSlgpzeO1PKCzwSrYQhlFIZI6AH+ch7EAWt84MfAaMz/1DiXF6Q8fAcR0h
QoyFIAKTiVkcgOQjSQOIM2SS5cyGeDRaHGVWfaJOwdIYo6ctFzI2asPJ4yU0QsA7
0WTD2+sBG6AXGhfafGUHEmou8sGQ+QT8rgi4hs1bfyHuT5dPgC4TbwknD1G8pMqm
ID3CIiCF8hhF5C2hjrW0LTJ6zTlg1c1K0NmmUL1ucsfzEk//c7GsU8dk+FYmtW9A
VzryJj1NmnSqA3zd3jBMuK37Ei3pRvVbO7Uxf14=
MIIDGTCCAgGgAwIBAgIUealQGELTHLUVcpsNNwz8XexiWvswDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMjAzMjUxNTMzMjla
GA8yMjk2MDEwODE1MzMyOVowGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKeW0Jkq6ViZkyhaLCUgbqsN
7+6HLwfZK/ljy/KnZ7W7QlJ65Q2tkptL0fY4DPumT7JgVTGnXyTXJ35Ss5A4yhm9
yyMH8pNVR19udK1fU74YVmbXJkc0nP7n+AXX9lD2Yy9pDvtaq1E+erN2nR1XaCS9
n0ph4C/fB1Rh7mIv/u7WW7/aRz/rJjBBZIbg7hgZPwFsukEifGi0U4uitVYR6MWp
jHj++e1G38+4JrZR9vhBoHtBJ1DBpmjAQaAtkSZAxXJnma4awE0Bv0Q4lxkUeY+D
th8OxPXxgTbwTKDaguHlWLTapppygA8FnKqmcUkwHZO5OVldi/ZKOUm2YCuJlfEC
AwEAAaNTMFEwHQYDVR0OBBYEFJGX6zVDm0Ur4gJJR+PKlgGdwhPfMB8GA1UdIwQY
MBaAFJGX6zVDm0Ur4gJJR+PKlgGdwhPfMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBAJaRryxp0iYWGfLiZ0uIdOiYRVZUtmpqUSqT9/y29ffDAnCS
5labJS8FjaiQdlyaH+E9gzo0+nkO9NyfemJRLTEsU4Mz9AAvxs/NuWucqiyF0Y6d
JSYt7+2liGK5WvJMbHfW3jloWlv3oX+qL4iGFkJN+L9G+vf0GnKZCxgNIOqM4otv
cviCA9ouPwnSfnCeTsBoaUJqhLMizvUx7avvUyZTuV+t9+aN/qH4V//hTBqz9CNq
koodzngbUuyaGFI8QISUAhU+pbt6npb69LlhJHC0icDXCc4uTsd+SDsBOorZDuUL
HsNKnE0CSPZ65ENVNfJjB1fVEYbbpjr8kizOCDE=
-----END CERTIFICATE-----`)
var caCertInter = []byte(`-----BEGIN CERTIFICATE-----
MIIDMzCCAhugAwIBAgIUTJoqwFusJcupNCs/u39LBFrkZEIwDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMjAzMjUxNTMzMjla
GA8yMjk2MDEwODE1MzMyOVowKDEmMCQGA1UEAwwdd2ViaG9va190ZXN0c19pbnRl
cm1lZGlhdGVfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDT9DIt
uNXhesrh8XtPXK4pR7xGReEsIlgLpMYf11PtFln9eV0HXvUO2CG/YvMxwgyd6Yoq
EfzD4rjmXvl5bQPMygmxf5GN1PM7ef7gVYuHfDgsQ4a82u1JFhKvuOrXn3QRfRg4
M4uYND7J4+Bg6J8oaA0yXIiMCpBi+XwEufo0RvgxM6mT+CeJ82hmlTKVhQJZZ9ZT
al1C4dTR2XeH5TLiIAvm+egBmSZhtCVn14rGk/PcHOWV7hdCxaFhSm7dSC+dR4zK
SxNleJ4Y+tZgoMfvgP/xHZEjbBzxnxyasES/Nc4nTgylcr6aqEX/fbcF0QzHpL9Z
ibkt1cBExU9zHuFJAgMBAAGjYDBeMB0GA1UdDgQWBBTfgUwjHsTOey7WqL4f3oFD
bmY77TAfBgNVHSMEGDAWgBSRl+s1Q5tFK+ICSUfjypYBncIT3zAPBgNVHRMBAf8E
BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEARYbIpIidgAVb
5ra9zd7F902+xC13/nmlrKL/dRMrRdZxk1kVVww3FbXSp7k7oHsih42KUCVDBevw
0ZZiolZlLneU57dEKKiTMkuPdVbNbIBPXIQpHLrXpVIR5BRRdRZ5OJZY24hYCvce
50XV8ITIU0R/U4sQ6NFHv8NJ5BB+2u1M3HF2LSKZFLnfP5FBcTCg84Jd6gEmTU2j
wZELnHy1AVdQnKMP9VrdAr9Wn6omWxAfO/PSb9YeKhGH5vtX+Bpb9bSPQIpXeBdB
LLCkme0M+1UsF7xua0KVi4DSuJc+RBl4aOH0ZvKmrIWzLzZhRS0vaO/fPArVCvvI
VrUba0E3WQ==
-----END CERTIFICATE-----`)
var caCertInterSHA1 = []byte(`-----BEGIN CERTIFICATE-----
MIIDMzCCAhugAwIBAgIUTJoqwFusJcupNCs/u39LBFrkZEMwDQYJKoZIhvcNAQEF
BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMjAzMjUxNTMzMjla
GA8yMjk2MDEwODE1MzMyOVowKDEmMCQGA1UEAwwdd2ViaG9va190ZXN0c19pbnRl
cm1lZGlhdGVfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDT9DIt
uNXhesrh8XtPXK4pR7xGReEsIlgLpMYf11PtFln9eV0HXvUO2CG/YvMxwgyd6Yoq
EfzD4rjmXvl5bQPMygmxf5GN1PM7ef7gVYuHfDgsQ4a82u1JFhKvuOrXn3QRfRg4
M4uYND7J4+Bg6J8oaA0yXIiMCpBi+XwEufo0RvgxM6mT+CeJ82hmlTKVhQJZZ9ZT
al1C4dTR2XeH5TLiIAvm+egBmSZhtCVn14rGk/PcHOWV7hdCxaFhSm7dSC+dR4zK
SxNleJ4Y+tZgoMfvgP/xHZEjbBzxnxyasES/Nc4nTgylcr6aqEX/fbcF0QzHpL9Z
ibkt1cBExU9zHuFJAgMBAAGjYDBeMB0GA1UdDgQWBBTfgUwjHsTOey7WqL4f3oFD
bmY77TAfBgNVHSMEGDAWgBSRl+s1Q5tFK+ICSUfjypYBncIT3zAPBgNVHRMBAf8E
BTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAAhMQTwrpAeIQ
nShHfTERiwg/tx3dL971d3pFS5wi4kEIbbYCUGpzkmK/FTw4hfUnLpwcjjAbOWkk
45glOmrLJXM4RvH5PQF3GZmZvxv8Dl4zuhH1QvWbJHUiC+gyrBWI0moyLSmNiutZ
d3TZGEehZGwivMdHHuhgiyFM4i33EQTW1vdMdOvdu8yNpAeXM2h1PcJwbEML9PO3
LONzVKhz/RsyEwv7PkX1gdmi6eyAE61BWJGwzxE4K0xIYmcr6iOjsJhEf/5Qc93P
IGSHsG/HjWwZ47gbobtv7L+8uKP/0ky+k1cE4nIB1gKYey+SYwvkQTsj24oG9xcL
XhgnIl+qDw==
-----END CERTIFICATE-----`)
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`)
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----`)
var serverCertNoSAN = []byte(`-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----`)
var sha1ServerCertInter = []byte(`-----BEGIN CERTIFICATE-----
MIIDITCCAgmgAwIBAgIUaVjtC++/JGFoZRtkpo/j5q1nQ/4wDQYJKoZIhvcNAQEF
BQAwKDEmMCQGA1UEAwwdd2ViaG9va190ZXN0c19pbnRlcm1lZGlhdGVfY2EwIBcN
MjIwMzI1MTUzMzI5WhgPMjI5NjAxMDgxNTMzMjlaMBQxEjAQBgNVBAMMCWxvY2Fs
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOdm+G8iCaVA8kyq
ORYgmuRJIsuwgnfFKJXkqJkI4ec2RrCG4M/Uao2kzCxdI6m84fYsdg0Ep2PsN9i6
jzNrDitfv2rZngmvJuW72a0aA8T9k96UB08ntAvViO46qLJjKrkRWpGmNTg8Q0T1
hfQkYIRRetjXB3587VtqzIhGLbg0ZGRrKSS/weTgMxIC6+Ij/EyjJakroWkIYudn
RGaNKSdSHuLqfMcVynqKnzt1klQ3kaY/59LWEgC4TgnRp9ETGr+cH2ZsXX2aSlIQ
a3MRX3ogweXO8XZZ84h62OZakkDwh8QEG0tYzGleGnYcQ8xeereUVOv+Xlj2eCL5
U0bWJjcCAwEAAaNVMFMwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYw
FAYIKwYBBQUHAwIGCCsGAQUFBwMBMBoGA1UdEQQTMBGHBH8AAAGCCWxvY2FsaG9z
dDANBgkqhkiG9w0BAQUFAAOCAQEATpiJFBwcRFIfZ9ffvS1WDzHqNElEnvocv/ul
3KVtoX4gmKRoOy344s3oJ5APPHYWUFuZVc3uofjW265r2uOW1Cb4P9yAtNc4htBS
+hYsdS3MQlzZCS9rItaT25R6Ieq5TbHGRCof387jzvo1NNhcAQ5akQlQKI87km77
VzoEBdAw68Q0ZE+X34Q9eAA44oCcLAgCpGvs6hQuUSInribSR3vtsjuaLjdJ5F1f
GCu2QGM4cVLaezmoa1J54ETZggT2xFw2IyWJ2g/kXFpo+HnoyaDrPthud3Pe5xEt
JMzX0s3jPSjfeAv34Pr37s0Or18r1bS1hrgxE0SV2vk31fsImg==
-----END CERTIFICATE-----`)
var serverCertInterSHA1 = []byte(`-----BEGIN CERTIFICATE-----
MIIDITCCAgmgAwIBAgIUfSzzygvth1xlpa9DtyGpHuY2V+swDQYJKoZIhvcNAQEL
BQAwKDEmMCQGA1UEAwwdd2ViaG9va190ZXN0c19pbnRlcm1lZGlhdGVfY2EwIBcN
MjIwMzI1MTUzMzI5WhgPMjI5NjAxMDgxNTMzMjlaMBQxEjAQBgNVBAMMCWxvY2Fs
aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOdm+G8iCaVA8kyq
ORYgmuRJIsuwgnfFKJXkqJkI4ec2RrCG4M/Uao2kzCxdI6m84fYsdg0Ep2PsN9i6
jzNrDitfv2rZngmvJuW72a0aA8T9k96UB08ntAvViO46qLJjKrkRWpGmNTg8Q0T1
hfQkYIRRetjXB3587VtqzIhGLbg0ZGRrKSS/weTgMxIC6+Ij/EyjJakroWkIYudn
RGaNKSdSHuLqfMcVynqKnzt1klQ3kaY/59LWEgC4TgnRp9ETGr+cH2ZsXX2aSlIQ
a3MRX3ogweXO8XZZ84h62OZakkDwh8QEG0tYzGleGnYcQ8xeereUVOv+Xlj2eCL5
U0bWJjcCAwEAAaNVMFMwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYw
FAYIKwYBBQUHAwIGCCsGAQUFBwMBMBoGA1UdEQQTMBGHBH8AAAGCCWxvY2FsaG9z
dDANBgkqhkiG9w0BAQsFAAOCAQEAwe/JUeIiJ5ugiO4tM0ZtvgHuFC3hK+ZWndRE
z4JfVXTW9soxpa/cOU9QdJhZzouIu9yqZasY4zSEerC1e6grBYP95vMbN6xUAown
wNzrQzyJ6yP526txiIdOkKf+yVNdz0OWNHMPtwTWIr8kKGK23ABF94aUa0VlkErp
Qrd8NQ3guIPI+/upuxirJCFdhE+U3U0pLHpGaGvhkOytfnLYiINwR9norVCDGbQG
ITH0tOz8gVWWWwxa9s5CmbqTnasgUMDh1jHa5xOo+riX8H5lwQUaItKU1JM+QMIR
6Z+M0Isdw647A6tmX7DqNcmHlBKxPN1GDcVXalwYJUoXwTb9Hw==
-----END CERTIFICATE-----`)
// Test_checkForHostnameError tests that the upstream message for remote server
@ -144,6 +230,7 @@ func TestCheckForHostnameError(t *testing.T) {
x509MissingSANCounter := metrics.NewCounter(&metrics.CounterOpts{Name: "Test_checkForHostnameError"})
registry := testutil.NewFakeKubeRegistry("0.0.0")
registry.MustRegister(x509MissingSANCounter)
sanChecker := NewSANDeprecatedChecker(x509MissingSANCounter)
var lastCounterVal int
for _, tt := range tests {
@ -159,7 +246,9 @@ func TestCheckForHostnameError(t *testing.T) {
_, err = client.Transport.RoundTrip(req)
checkForHostnameError(err, x509MissingSANCounter)
if sanChecker.CheckRoundTripError(err) {
sanChecker.IncreaseMetricsCounter(req)
}
errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "Test_checkForHostnameError")
if errorCounterVal == -1 {
@ -185,9 +274,6 @@ func TestCheckRespForNoSAN(t *testing.T) {
serverCert []byte
counterIncrease bool
}{
{
name: "no certs",
},
{
name: "no SAN",
serverCert: serverCertNoSAN,
@ -203,6 +289,7 @@ func TestCheckRespForNoSAN(t *testing.T) {
x509MissingSANCounter := metrics.NewCounter(&metrics.CounterOpts{Name: "Test_checkRespForNoSAN"})
registry := testutil.NewFakeKubeRegistry("0.0.0")
registry.MustRegister(x509MissingSANCounter)
sanChecker := NewSANDeprecatedChecker(x509MissingSANCounter)
var lastCounterVal int
for _, tt := range tests {
@ -228,7 +315,9 @@ func TestCheckRespForNoSAN(t *testing.T) {
TLS: tlsConnectionState,
}
checkRespForNoSAN(resp, x509MissingSANCounter)
if sanChecker.CheckPeerCertificates(resp.TLS.PeerCertificates) {
sanChecker.IncreaseMetricsCounter(nil)
}
errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "Test_checkRespForNoSAN")
if errorCounterVal == -1 {
@ -248,6 +337,291 @@ func TestCheckRespForNoSAN(t *testing.T) {
}
}
func TestCheckForInsecureAlgorithmError(t *testing.T) {
tests := []struct {
name string
serverCert []byte
counterIncrease bool
}{
{
name: "server cert sha1-signed",
serverCert: append(append(sha1ServerCertInter, byte('\n')), caCertInter...),
counterIncrease: true,
},
{
name: "intermediate CA cert sha1-signed",
serverCert: append(append(serverCertInterSHA1, byte('\n')), caCertInterSHA1...),
counterIncrease: true,
},
{
name: "different error - cert untrusted, intermediate not in returned chain",
serverCert: serverCertInterSHA1,
},
{
name: "properly signed",
serverCert: serverCert,
},
}
// register the test metrics
x509SHA1SignatureCounter := metrics.NewCounter(&metrics.CounterOpts{Name: "Test_checkForInsecureAlgorithmError"})
registry := testutil.NewFakeKubeRegistry("0.0.0")
registry.MustRegister(x509SHA1SignatureCounter)
sha1checker := NewSHA1SignatureDeprecatedChecker(x509SHA1SignatureCounter)
var lastCounterVal int
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tlsServer, serverURL := testServer(t, tt.serverCert)
defer tlsServer.Close()
req, err := http.NewRequest(http.MethodGet, serverURL.String(), nil)
if err != nil {
t.Fatalf("failed to create an http request: %v", err)
}
// can't use tlsServer.Client() as it contains the server certificate
// in tls.Config.Certificates. The signatures are, however, only checked
// during building a candidate verification certificate chain and
// if the content of tls.Config.Certificates matches the certificate
// returned by the server, this short-circuits and the signature verification is
// never performed.
caPool := x509.NewCertPool()
require.True(t, caPool.AppendCertsFromPEM(caCert))
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
RootCAs: caPool,
},
},
}
_, err = client.Transport.RoundTrip(req)
if sha1checker.CheckRoundTripError(err) {
sha1checker.IncreaseMetricsCounter(req)
}
errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "Test_checkForInsecureAlgorithmError")
if errorCounterVal == -1 {
t.Fatalf("failed to get the error counter from the registry")
}
if tt.counterIncrease && errorCounterVal != lastCounterVal+1 {
t.Errorf("expected the Test_checkForInsecureAlgorithmError metrics to increase by 1 from %d, but it is %d", lastCounterVal, errorCounterVal)
}
if !tt.counterIncrease && errorCounterVal != lastCounterVal {
t.Errorf("expected the Test_checkForInsecureAlgorithmError metrics to stay the same (%d), but it is %d", lastCounterVal, errorCounterVal)
}
lastCounterVal = errorCounterVal
})
}
}
func TestCheckRespSHA1SignedCert(t *testing.T) {
tests := []struct {
name string
serverCert []byte
counterIncrease bool
}{
{
name: "server cert sha1-signed",
serverCert: append(append(sha1ServerCertInter, byte('\n')), caCertInter...),
counterIncrease: true,
},
{
name: "intermediate CA cert sha1-signed",
serverCert: append(append(serverCertInterSHA1, byte('\n')), caCertInterSHA1...),
counterIncrease: true,
},
{
name: "properly signed",
serverCert: serverCert,
},
}
// register the test metrics
x509SHA1SignatureCounter := metrics.NewCounter(&metrics.CounterOpts{Name: "Test_checkRespSHA1SignedCert"})
registry := testutil.NewFakeKubeRegistry("0.0.0")
registry.MustRegister(x509SHA1SignatureCounter)
sha1checker := NewSHA1SignatureDeprecatedChecker(x509SHA1SignatureCounter)
var lastCounterVal int
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tlsConnectionState *tls.ConnectionState
if tt.serverCert != nil {
peerCerts := []*x509.Certificate{}
for block, rest := pem.Decode([]byte(tt.serverCert)); block != nil; block, rest = pem.Decode(rest) {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("failed to parse certificate: %v", err)
}
peerCerts = append(peerCerts, cert)
}
tlsConnectionState = &tls.ConnectionState{
PeerCertificates: peerCerts,
}
}
resp := &http.Response{
TLS: tlsConnectionState,
}
if sha1checker.CheckPeerCertificates(resp.TLS.PeerCertificates) {
sha1checker.IncreaseMetricsCounter(nil)
}
errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "Test_checkRespSHA1SignedCert")
if errorCounterVal == -1 {
t.Fatalf("failed to get the error counter from the registry")
}
if tt.counterIncrease && errorCounterVal != lastCounterVal+1 {
t.Errorf("expected the Test_checkRespSHA1SignedCert metrics to increase by 1 from %d, but it is %d", lastCounterVal, errorCounterVal)
}
if !tt.counterIncrease && errorCounterVal != lastCounterVal {
t.Errorf("expected the Test_checkRespSHA1SignedCert metrics to stay the same (%d), but it is %d", lastCounterVal, errorCounterVal)
}
lastCounterVal = errorCounterVal
})
}
}
func Test_x509DeprecatedCertificateMetricsRTWrapper_RoundTrip(t *testing.T) {
// register the test metrics
testCounter := metrics.NewCounter(&metrics.CounterOpts{Name: "testCounter"})
registry := testutil.NewFakeKubeRegistry("0.0.0")
registry.MustRegister(testCounter)
tests := []struct {
name string
checkers []deprecatedCertificateAttributeChecker
resp *http.Response
err error
counterIncrease bool
}{
{
name: "no error, resp w/ cert, no counter increase",
checkers: []deprecatedCertificateAttributeChecker{&testNegativeChecker{counterRaiser{testCounter, ""}}},
resp: httpResponseWithCert(),
},
{
name: "no error, resp w/o cert, no counter increase",
checkers: []deprecatedCertificateAttributeChecker{&testPositiveChecker{counterRaiser{testCounter, ""}}},
resp: httpResponseNoCert(),
},
{
name: "no error, resp w/ cert, counter increase",
checkers: []deprecatedCertificateAttributeChecker{&testPositiveChecker{counterRaiser{testCounter, ""}}},
resp: httpResponseWithCert(),
counterIncrease: true,
},
{
name: "unrelated error, no resp, no counter increase",
checkers: []deprecatedCertificateAttributeChecker{&testNegativeChecker{counterRaiser{testCounter, ""}}},
err: fmt.Errorf("error"),
},
{
name: "related error, no resp, counter increase",
checkers: []deprecatedCertificateAttributeChecker{&testPositiveChecker{counterRaiser{testCounter, ""}}},
err: fmt.Errorf("error"),
counterIncrease: true,
},
}
var lastCounterVal int
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &x509DeprecatedCertificateMetricsRTWrapper{
rt: newTestRoundTripper(tt.resp, tt.err),
checkers: tt.checkers,
}
got, err := w.RoundTrip(&http.Request{})
if err != tt.err {
t.Errorf("x509DeprecatedCertificateMetricsRTWrapper.RoundTrip() should not mutate the error. Got %v, want %v", err, tt.err)
return
}
if !reflect.DeepEqual(got, tt.resp) {
t.Errorf("x509DeprecatedCertificateMetricsRTWrapper.RoundTrip() = should not mutate the response. Got %v, want %v", got, tt.resp)
}
errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "testCounter")
if errorCounterVal == -1 {
t.Fatalf("failed to get the error counter from the registry")
}
if tt.counterIncrease && errorCounterVal != lastCounterVal+1 {
t.Errorf("expected the testCounter metrics to increase by 1 from %d, but it is %d", lastCounterVal, errorCounterVal)
}
if !tt.counterIncrease && errorCounterVal != lastCounterVal {
t.Errorf("expected the testCounter metrics to stay the same (%d), but it is %d", lastCounterVal, errorCounterVal)
}
lastCounterVal = errorCounterVal
})
}
}
type testRoundTripper func(req *http.Request) (*http.Response, error)
func (rt testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return rt(req)
}
func newTestRoundTripper(resp *http.Response, err error) testRoundTripper {
return func(_ *http.Request) (*http.Response, error) {
return resp, err
}
}
type testPositiveChecker struct {
counterRaiser
}
func (c *testPositiveChecker) CheckRoundTripError(_ error) bool {
return true
}
func (c *testPositiveChecker) CheckPeerCertificates(_ []*x509.Certificate) bool {
return true
}
type testNegativeChecker struct {
counterRaiser
}
func (c *testNegativeChecker) CheckRoundTripError(_ error) bool {
return false
}
func (c *testNegativeChecker) CheckPeerCertificates(_ []*x509.Certificate) bool {
return false
}
func httpResponseWithCert() *http.Response {
return &http.Response{
TLS: &tls.ConnectionState{
PeerCertificates: []*x509.Certificate{
{Issuer: pkix.Name{CommonName: "a name"}},
},
},
}
}
func httpResponseNoCert() *http.Response {
return &http.Response{}
}
func testServer(t *testing.T, serverCert []byte) (*httptest.Server, *url.URL) {
rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(caCert)

View File

@ -241,7 +241,10 @@ func (r *proxyHandler) updateAPIService(apiService *apiregistrationv1api.APIServ
CAData: apiService.Spec.CABundle,
},
}
clientConfig.Wrap(x509metrics.NewMissingSANRoundTripperWrapperConstructor(x509MissingSANCounter))
clientConfig.Wrap(x509metrics.NewDeprecatedCertificateRoundTripperWrapperConstructor(
x509MissingSANCounter,
x509InsecureSHA1Counter,
))
newInfo := proxyHandlingInfo{
name: apiService.Name,

View File

@ -34,6 +34,19 @@ var x509MissingSANCounter = metrics.NewCounter(
},
)
var x509InsecureSHA1Counter = metrics.NewCounter(
&metrics.CounterOpts{
Subsystem: "kube_aggregator",
Namespace: "apiserver",
Name: "x509_insecure_sha1_total",
Help: "Counts the number of requests to servers with insecure SHA1 signatures " +
"in their serving certificate OR the number of connection failures " +
"due to the insecure SHA1 signatures (either/or, based on the runtime environment)",
StabilityLevel: metrics.ALPHA,
},
)
func init() {
legacyregistry.MustRegister(x509MissingSANCounter)
legacyregistry.MustRegister(x509InsecureSHA1Counter)
}