/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 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" auditapi "k8s.io/apiserver/pkg/apis/audit" "k8s.io/apiserver/pkg/audit" "k8s.io/component-base/metrics" "k8s.io/component-base/metrics/testutil" ) // taken from pkg/util/webhook/certs_test.go var caCert = []byte(`-----BEGIN CERTIFICATE----- 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----- MIIEowIBAAKCAQEA52b4byIJpUDyTKo5FiCa5Ekiy7CCd8UoleSomQjh5zZGsIbg z9RqjaTMLF0jqbzh9ix2DQSnY+w32LqPM2sOK1+/atmeCa8m5bvZrRoDxP2T3pQH Tye0C9WI7jqosmMquRFakaY1ODxDRPWF9CRghFF62NcHfnztW2rMiEYtuDRkZGsp JL/B5OAzEgLr4iP8TKMlqSuhaQhi52dEZo0pJ1Ie4up8xxXKeoqfO3WSVDeRpj/n 0tYSALhOCdGn0RMav5wfZmxdfZpKUhBrcxFfeiDB5c7xdlnziHrY5lqSQPCHxAQb S1jMaV4adhxDzF56t5RU6/5eWPZ4IvlTRtYmNwIDAQABAoIBAAb0r28XxNZ011O6 ojCqFj3afPNGgQV8pbWrw+2luLSsiv9vbn6Q0gsj8wc6XYISrXAq8fl+NFHqndsj 8H4JL8nZ/PUHSZrc6vxo4ygy6f4X6UP9iyKz/NOGPbF7jeqe1H/vp5tNNbhVB2ih QL+QAF653El8XTtOIgxnb3KBOYqZ6e0rWvC5XlZrfT4EGqpokW4zQ6ROQUbnWyCk LC4CtQpcLLd7fdGfA2cB2xDdGJ3Er8gAnU/X+tAtcghWanoNARKGU6opyGpwhipe +31CivIUhtASWdbS73ay5QaDQSlgNM1/2hk5Beal7D9bGfKtwT/VGDSpKc4EKP8j ktQSE0ECgYEA/jHMLQyvJ2VuqBdMI5hbaw5jzUAQwaJof5iQNuvFrbtWDeotAf+6 HomwoqzZ9+juiil4PHLQJzkArHwMWXbc+3FAznN1foS+YlOmIgJrjKa+EP+sz/X2 GxuyH3RD9+TH4EGd4TbeDr0eZOnIbKVybj4ueE+um7jtdLzYW2Y8iCcCgYEA6Qu6 x5WOQaPaEOQwEP5AqVBZnZl1ogeEVanlPYl6amPFFnlc41+M61p3ebwRqikaC9Dv hePiOcTTJyt4h7qzgd5rJTjy5bNYDx9F61NGagF0xJLQiMnXM/TsoFABVWetLepG DTzgvCf7wmB9QTgdLct7KyG4suDBJlEAvr70q3ECgYEAxx4pC0z5U4oAMYn2aZeq XOUrxpcdySC4bOMMbQkpk1rBIStEUGGK4OsIw5VVNP5xBSdQ+UESzva3EWYmoloa 5pgjpNUKv62qGQnfhJqStt3S2yv8qfbI7xk14a/IokHDVGbyDn5VWgRI79G1322G gtcQvcvlQjSNRbm8XXRrjFcCgYA66x1Awl3h2IQUSyyfzzgX1lmhz59+5HmfksGD SlOpvCmi4fILBihBhHC6VUL+C0ArhppX9mJGiq17tLDXV+t0RQA/u+MlEa+MuzJZ KYee21ljLV8NhkIjP6Pnb/K2XezZs+YcCK0kxNMQtIZWS9KMtmogYHkquEn83vPa Rbrj8QKBgHifm6Z9F4qTz2b2RsYPoMHOdsX0DrZ8xQOH8jioTAy/Xi2hrL5Klp7h zaLifWtOdtckFxIk+6D/zLLn1icC7cc8n4TMwQ1ikY+9IPnkTXVx4b/r/NSbAVxZ J821mkhGdqKJGAzk6uh/Sn4rNGubH+I1x2Xa9hWbARCLsj8tp6TX -----END RSA PRIVATE KEY-----`) var serverCert = []byte(`-----BEGIN CERTIFICATE----- MIIDHzCCAgegAwIBAgIUTJoqwFusJcupNCs/u39LBFrkZEQwDQYJKoZIhvcNAQEL BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMjAzMjUxNTMzMjla GA8yMjk2MDEwODE1MzMyOVowHzEdMBsGA1UEAwwUd2ViaG9va190ZXN0c19zZXJ2 ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnZvhvIgmlQPJMqjkW IJrkSSLLsIJ3xSiV5KiZCOHnNkawhuDP1GqNpMwsXSOpvOH2LHYNBKdj7DfYuo8z aw4rX79q2Z4Jryblu9mtGgPE/ZPelAdPJ7QL1YjuOqiyYyq5EVqRpjU4PENE9YX0 JGCEUXrY1wd+fO1basyIRi24NGRkaykkv8Hk4DMSAuviI/xMoyWpK6FpCGLnZ0Rm jSknUh7i6nzHFcp6ip87dZJUN5GmP+fS1hIAuE4J0afRExq/nB9mbF19mkpSEGtz EV96IMHlzvF2WfOIetjmWpJA8IfEBBtLWMxpXhp2HEPMXnq3lFTr/l5Y9ngi+VNG 1iY3AgMBAAGjVTBTMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQG CCsGAQUFBwMCBggrBgEFBQcDATAaBgNVHREEEzARhwR/AAABgglsb2NhbGhvc3Qw DQYJKoZIhvcNAQELBQADggEBAAeUHlNJiGfvhi8ts96javP8tO5gPkN7uErIMpzA N1rf5Kdy7/LsxM6Uvwn0ns+p1vxANAjR/c0nfu0eIO1t5fKVDD0s9+ohKA/6phrm xChTyl21mDZlFKjq0sjSwzBcUHPJjzUW9+AMDvS7pOjR5h4nD21LlMIkBzinl5KT uo2Pm/OZqepPdM5XH9DaW0T0tjXKvRFe4FklJSKGD7f+T1whtmyziyA84YjYVa/6 gF+gpIOmPruJI9UoFqEncNpLfh5vKu2Vxv+maztFRhb+9gOg+nVBq1pxmMZV0PuM L+tz0avIZEO2+KhgVGF3AF8HSZQHYcaskGFSGc8FxDKcDjM= -----END CERTIFICATE-----`) var serverCertNoSAN = []byte(`-----BEGIN CERTIFICATE----- MIIC+DCCAeCgAwIBAgIUTJoqwFusJcupNCs/u39LBFrkZEUwDQYJKoZIhvcNAQEL BQAwGzEZMBcGA1UEAwwQd2ViaG9va190ZXN0c19jYTAgFw0yMjAzMjUxNTMzMjla GA8yMjk2MDEwODE1MzMyOVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkq hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA52b4byIJpUDyTKo5FiCa5Ekiy7CCd8Uo leSomQjh5zZGsIbgz9RqjaTMLF0jqbzh9ix2DQSnY+w32LqPM2sOK1+/atmeCa8m 5bvZrRoDxP2T3pQHTye0C9WI7jqosmMquRFakaY1ODxDRPWF9CRghFF62NcHfnzt W2rMiEYtuDRkZGspJL/B5OAzEgLr4iP8TKMlqSuhaQhi52dEZo0pJ1Ie4up8xxXK eoqfO3WSVDeRpj/n0tYSALhOCdGn0RMav5wfZmxdfZpKUhBrcxFfeiDB5c7xdlnz iHrY5lqSQPCHxAQbS1jMaV4adhxDzF56t5RU6/5eWPZ4IvlTRtYmNwIDAQABozkw NzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYI KwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAGfCa0eCws/7+NYLJwVsdd7C/QHT qbPw6w8oGnlXELMPwC701VFOcadhhengYCY1Kwa/KVu1ucFODDgp1ncvRoMVVWvD /q6V07zu+aV/aW64zU27f+TzxTVXyCgfCSFUELJYBsBFWLw0K57ZDZdN2KJD+zD5 BAU0ghmy1DB+WSFMTqQ2iQaNX8oZh5jTZV3JtRNncqEqqIh4Nv7YYYZ02rgE7P2o btVFYLBXHW7VYqnWpWM1pBfZpfGzMpGdR+1feST/88gUZh7ze15Ib4BlyU13v+0l /BjuUsSWiITKWb2fqTiAkrqVbkOrC7Orz8yvgjuih4lEinQV1+KJUtcMmng= -----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 // certificate's hostname hasn't changed when no SAN extension is present and that // the metrics counter increases properly when such an error is encountered // // Requires GODEBUG=x509ignoreCN=0 to not be set in the environment func TestCheckForHostnameError(t *testing.T) { tests := []struct { name string serverCert []byte counterIncrease bool }{ { name: "no SAN", serverCert: serverCertNoSAN, counterIncrease: true, }, { name: "with SAN", serverCert: serverCert, }, } // register the test metrics 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 { t.Run(tt.name, func(t *testing.T) { tlsServer, serverURL := testServer(t, tt.serverCert) defer tlsServer.Close() client := tlsServer.Client() req, err := http.NewRequest(http.MethodGet, serverURL.String(), nil) if err != nil { t.Fatalf("failed to create an http request: %v", err) } req = req.WithContext(audit.WithAuditContext(req.Context())) auditCtx := audit.AuditContextFrom(req.Context()) auditCtx.Event = &auditapi.Event{Level: auditapi.LevelMetadata} _, err = client.Transport.RoundTrip(req) if sanChecker.CheckRoundTripError(err) { sanChecker.IncreaseMetricsCounter(req) if len(auditCtx.Event.Annotations["missing-san.invalid-cert.kubernetes.io/"+req.URL.Hostname()]) == 0 { t.Errorf("expected audit annotations, got %#v", auditCtx.Event.Annotations) } } errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "Test_checkForHostnameError") 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_checkForHostnameError metrics to increase by 1 from %d, but it is %d", lastCounterVal, errorCounterVal) } if !tt.counterIncrease && errorCounterVal != lastCounterVal { t.Errorf("expected the Test_checkForHostnameError metrics to stay the same (%d), but it is %d", lastCounterVal, errorCounterVal) } lastCounterVal = errorCounterVal }) } } func TestCheckRespForNoSAN(t *testing.T) { tests := []struct { name string serverCert []byte counterIncrease bool }{ { name: "no SAN", serverCert: serverCertNoSAN, counterIncrease: true, }, { name: "with SAN", serverCert: serverCert, }, } // register the test metrics 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 { t.Run(tt.name, func(t *testing.T) { var tlsConnectionState *tls.ConnectionState if tt.serverCert != nil { block, _ := pem.Decode([]byte(tt.serverCert)) if block == nil { t.Fatal("failed to parse certificate PEM") } serverCert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("failed to parse certificate: %v", err) } tlsConnectionState = &tls.ConnectionState{ PeerCertificates: []*x509.Certificate{serverCert}, } } resp := &http.Response{ TLS: tlsConnectionState, } if sanChecker.CheckPeerCertificates(resp.TLS.PeerCertificates) { sanChecker.IncreaseMetricsCounter(nil) } errorCounterVal := getSingleCounterValueFromRegistry(t, registry, "Test_checkRespForNoSAN") 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_checkRespForNoSAN metrics to increase by 1 from %d, but it is %d", lastCounterVal, errorCounterVal) } if !tt.counterIncrease && errorCounterVal != lastCounterVal { t.Errorf("expected the Test_checkRespForNoSAN metrics to stay the same (%d), but it is %d", lastCounterVal, errorCounterVal) } lastCounterVal = errorCounterVal }) } } 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) } req = req.WithContext(audit.WithAuditContext(req.Context())) auditCtx := audit.AuditContextFrom(req.Context()) auditCtx.Event = &auditapi.Event{Level: auditapi.LevelMetadata} // 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) if len(auditCtx.Event.Annotations["insecure-sha1.invalid-cert.kubernetes.io/"+req.URL.Hostname()]) == 0 { t.Errorf("expected audit annotations, got %#v", auditCtx.Event.Annotations) } } 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) cert, err := tls.X509KeyPair(serverCert, serverKey) if err != nil { t.Fatalf("failed to init x509 cert/key pair: %v", err) } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: rootCAs, } tlsServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(200) w.Write([]byte("ok")) })) tlsServer.TLS = tlsConfig tlsServer.StartTLS() serverURL, err := url.Parse(tlsServer.URL) if err != nil { tlsServer.Close() t.Fatalf("failed to parse the testserver URL: %v", err) } serverURL.Host = net.JoinHostPort("localhost", serverURL.Port()) return tlsServer, serverURL } func getSingleCounterValueFromRegistry(t *testing.T, r metrics.Gatherer, name string) int { mfs, err := r.Gather() if err != nil { t.Logf("failed to gather local registry metrics: %v", err) return -1 } for _, mf := range mfs { if mf.Name != nil && *mf.Name == name { mfMetric := mf.GetMetric() for _, m := range mfMetric { if m.GetCounter() != nil { return int(m.GetCounter().GetValue()) } } } } return -1 }