k8s.io/apiserver@v0.31.1/pkg/util/x509metrics/server_cert_deprecations.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package x509metrics 18 19 import ( 20 "crypto/x509" 21 "errors" 22 "fmt" 23 "net/http" 24 "reflect" 25 "strings" 26 27 utilnet "k8s.io/apimachinery/pkg/util/net" 28 "k8s.io/apiserver/pkg/audit" 29 "k8s.io/component-base/metrics" 30 "k8s.io/klog/v2" 31 ) 32 33 var _ utilnet.RoundTripperWrapper = &x509DeprecatedCertificateMetricsRTWrapper{} 34 35 type x509DeprecatedCertificateMetricsRTWrapper struct { 36 rt http.RoundTripper 37 38 checkers []deprecatedCertificateAttributeChecker 39 } 40 41 type deprecatedCertificateAttributeChecker interface { 42 // CheckRoundTripError returns true if the err is an error specific 43 // to this deprecated certificate attribute 44 CheckRoundTripError(err error) bool 45 // CheckPeerCertificates returns true if the deprecated attribute/value pair 46 // was found in a given certificate in the http.Response.TLS.PeerCertificates bundle 47 CheckPeerCertificates(certs []*x509.Certificate) bool 48 // IncreaseCounter increases the counter internal to this interface 49 // Use the req to derive and log information useful for troubleshooting the certificate issue 50 IncreaseMetricsCounter(req *http.Request) 51 } 52 53 // counterRaiser is a helper structure to include in certificate deprecation checkers. 54 // It implements the IncreaseMetricsCounter() method so that, when included in the checker, 55 // it does not have to be reimplemented. 56 type counterRaiser struct { 57 counter *metrics.Counter 58 // programmatic id used in log and audit annotations prefixes 59 id string 60 // human readable explanation 61 reason string 62 } 63 64 func (c *counterRaiser) IncreaseMetricsCounter(req *http.Request) { 65 if req != nil && req.URL != nil { 66 if hostname := req.URL.Hostname(); len(hostname) > 0 { 67 prefix := fmt.Sprintf("%s.invalid-cert.kubernetes.io", c.id) 68 klog.Infof("%s: invalid certificate detected connecting to %q: %s", prefix, hostname, c.reason) 69 audit.AddAuditAnnotation(req.Context(), prefix+"/"+hostname, c.reason) 70 } 71 } 72 c.counter.Inc() 73 } 74 75 // NewDeprecatedCertificateRoundTripperWrapperConstructor returns a RoundTripper wrapper that's usable within ClientConfig.Wrap. 76 // 77 // It increases the `missingSAN` counter whenever: 78 // 1. we get a x509.HostnameError with string `x509: certificate relies on legacy Common Name field` 79 // which indicates an error caused by the deprecation of Common Name field when veryfing remote 80 // hostname 81 // 2. the server certificate in response contains no SAN. This indicates that this binary run 82 // with the GODEBUG=x509ignoreCN=0 in env 83 // 84 // It increases the `sha1` counter whenever: 85 // 1. we get a x509.InsecureAlgorithmError with string `SHA1` 86 // which indicates an error caused by an insecure SHA1 signature 87 // 2. the server certificate in response contains a SHA1WithRSA or ECDSAWithSHA1 signature. 88 // This indicates that this binary run with the GODEBUG=x509sha1=1 in env 89 func NewDeprecatedCertificateRoundTripperWrapperConstructor(missingSAN, sha1 *metrics.Counter) func(rt http.RoundTripper) http.RoundTripper { 90 return func(rt http.RoundTripper) http.RoundTripper { 91 return &x509DeprecatedCertificateMetricsRTWrapper{ 92 rt: rt, 93 checkers: []deprecatedCertificateAttributeChecker{ 94 NewSANDeprecatedChecker(missingSAN), 95 NewSHA1SignatureDeprecatedChecker(sha1), 96 }, 97 } 98 } 99 } 100 101 func (w *x509DeprecatedCertificateMetricsRTWrapper) RoundTrip(req *http.Request) (*http.Response, error) { 102 resp, err := w.rt.RoundTrip(req) 103 104 if err != nil { 105 for _, checker := range w.checkers { 106 if checker.CheckRoundTripError(err) { 107 checker.IncreaseMetricsCounter(req) 108 } 109 } 110 } else if resp != nil { 111 if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 { 112 for _, checker := range w.checkers { 113 if checker.CheckPeerCertificates(resp.TLS.PeerCertificates) { 114 checker.IncreaseMetricsCounter(req) 115 } 116 } 117 } 118 } 119 120 return resp, err 121 } 122 123 func (w *x509DeprecatedCertificateMetricsRTWrapper) WrappedRoundTripper() http.RoundTripper { 124 return w.rt 125 } 126 127 var _ deprecatedCertificateAttributeChecker = &missingSANChecker{} 128 129 type missingSANChecker struct { 130 counterRaiser 131 } 132 133 func NewSANDeprecatedChecker(counter *metrics.Counter) *missingSANChecker { 134 return &missingSANChecker{ 135 counterRaiser: counterRaiser{ 136 counter: counter, 137 id: "missing-san", 138 reason: "relies on a legacy Common Name field instead of the SAN extension for subject validation", 139 }, 140 } 141 } 142 143 // CheckRoundTripError returns true when we're running w/o GODEBUG=x509ignoreCN=0 144 // and the client reports a HostnameError about the legacy CN fields 145 func (c *missingSANChecker) CheckRoundTripError(err error) bool { 146 if err != nil && errors.As(err, &x509.HostnameError{}) && strings.Contains(err.Error(), "x509: certificate relies on legacy Common Name field") { 147 // increase the count of registered failures due to Go 1.15 x509 cert Common Name deprecation 148 return true 149 } 150 151 return false 152 } 153 154 // CheckPeerCertificates returns true when the server response contains 155 // a leaf certificate w/o the SAN extension 156 func (c *missingSANChecker) CheckPeerCertificates(peerCertificates []*x509.Certificate) bool { 157 if len(peerCertificates) > 0 { 158 if serverCert := peerCertificates[0]; !hasSAN(serverCert) { 159 return true 160 } 161 } 162 163 return false 164 } 165 166 func hasSAN(c *x509.Certificate) bool { 167 sanOID := []int{2, 5, 29, 17} 168 169 for _, e := range c.Extensions { 170 if e.Id.Equal(sanOID) { 171 return true 172 } 173 } 174 return false 175 } 176 177 type sha1SignatureChecker struct { 178 *counterRaiser 179 } 180 181 func NewSHA1SignatureDeprecatedChecker(counter *metrics.Counter) *sha1SignatureChecker { 182 return &sha1SignatureChecker{ 183 counterRaiser: &counterRaiser{ 184 counter: counter, 185 id: "insecure-sha1", 186 reason: "uses an insecure SHA-1 signature", 187 }, 188 } 189 } 190 191 // CheckRoundTripError returns true when we're running w/o GODEBUG=x509sha1=1 192 // and the client reports an InsecureAlgorithmError about a SHA1 signature 193 func (c *sha1SignatureChecker) CheckRoundTripError(err error) bool { 194 var unknownAuthorityError x509.UnknownAuthorityError 195 if err == nil { 196 return false 197 } 198 if !errors.As(err, &unknownAuthorityError) { 199 return false 200 } 201 202 errMsg := err.Error() 203 if strIdx := strings.Index(errMsg, "x509: cannot verify signature: insecure algorithm"); strIdx != -1 && strings.Contains(errMsg[strIdx:], "SHA1") { 204 // increase the count of registered failures due to Go 1.18 x509 sha1 signature deprecation 205 return true 206 } 207 208 return false 209 } 210 211 // CheckPeerCertificates returns true when the server response contains 212 // a non-root non-self-signed certificate with a deprecated SHA1 signature 213 func (c *sha1SignatureChecker) CheckPeerCertificates(peerCertificates []*x509.Certificate) bool { 214 // check all received non-self-signed certificates for deprecated signing algorithms 215 for _, cert := range peerCertificates { 216 if cert.SignatureAlgorithm == x509.SHA1WithRSA || cert.SignatureAlgorithm == x509.ECDSAWithSHA1 { 217 // the SHA-1 deprecation does not involve self-signed root certificates 218 if !reflect.DeepEqual(cert.Issuer, cert.Subject) { 219 return true 220 } 221 } 222 } 223 224 return false 225 }