diff --git a/docs/content/en/docs/reference/changelog.md b/docs/content/en/docs/reference/changelog.md index 0a26b7f5b462..7ecd0fb2ed50 100644 --- a/docs/content/en/docs/reference/changelog.md +++ b/docs/content/en/docs/reference/changelog.md @@ -4,6 +4,13 @@ linkTitle: "What's New?" weight: 35 --- +## Unreleased + +### Added + +### Fixed +- Fix issue using self-signed certificates for registry mirror [#1857](https://github.com/aws/eks-anywhere/issues/1857) + ## [v0.9.0](https://github.com/aws/eks-anywhere/releases/tag/v0.9.0) ### Added diff --git a/pkg/crypto/validator.go b/pkg/crypto/validator.go index 83eaf8fa6cf6..e13eef5b3af4 100644 --- a/pkg/crypto/validator.go +++ b/pkg/crypto/validator.go @@ -11,7 +11,7 @@ import ( type DefaultTlsValidator struct{} type TlsValidator interface { - ValidateCert(host, port, cert string) error + ValidateCert(host, port, caCertContent string) error HasSelfSignedCert(host, port string) (bool, error) } @@ -37,9 +37,9 @@ func (tv *DefaultTlsValidator) HasSelfSignedCert(host, port string) (bool, error } // ValidateCert parses the cert, ensures that the cert format is valid and verifies that the cert is valid for the url -func (tv *DefaultTlsValidator) ValidateCert(host, port, cert string) error { +func (tv *DefaultTlsValidator) ValidateCert(host, port, caCertContent string) error { // Validates that the cert format is valid - block, _ := pem.Decode([]byte(cert)) + block, _ := pem.Decode([]byte(caCertContent)) if block == nil { return fmt.Errorf("failed to parse certificate PEM") } @@ -50,15 +50,18 @@ func (tv *DefaultTlsValidator) ValidateCert(host, port, cert string) error { roots := x509.NewCertPool() roots.AddCert(providedCert) - opts := x509.VerifyOptions{ - DNSName: host, - Roots: roots, + conf := &tls.Config{ + InsecureSkipVerify: false, + RootCAs: roots, } - - // Verifies that the cert is valid - _, err = providedCert.Verify(opts) + // Verifies that the cert is valid by making a connection to the endpoint + endpoint := net.JoinHostPort(host, port) + conn, err := tls.Dial("tcp", endpoint, conf) if err != nil { - return fmt.Errorf("failed to verify certificate: %v", err) + return fmt.Errorf("verifying tls connection to host with custom CA: %v", err) + } + if err = conn.Close(); err != nil { + return fmt.Errorf("closing tls connection to %v: %v", endpoint, err) } return nil } diff --git a/pkg/crypto/validator_test.go b/pkg/crypto/validator_test.go index c0c6f49e2813..9eb8b4cfed58 100644 --- a/pkg/crypto/validator_test.go +++ b/pkg/crypto/validator_test.go @@ -1,24 +1,95 @@ package crypto_test import ( + "crypto/tls" + "fmt" + "net/http" + "net/http/httptest" + "strings" "testing" - "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/crypto" ) const ( - endpoint = "unit-test.local" - invalid_endpoint = "invalid-endpoint.local" - port = constants.DefaultHttpsPort + endpoint = "127.0.0.1" + invalidEndpoint = "invalid-endpoint.local" /* - This certificate was generated using the following commands and is valid only for `unit-test.local` + This certificate was generated using the following commands and is valid only for `127.0.0.1` openssl genrsa -out ca.key 2048 openssl req -new -x509 -days 3650 -key ca.key -out ca.crt openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr - openssl x509 -req -extfile <(printf "subjectAltName=DNS:unit-test.local") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt + openssl x509 -req -extfile <(printf "subjectAltName=IP:127.0.0.1") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt */ - cert = ` + caCert = ` +-----BEGIN CERTIFICATE----- +MIICrjCCAZYCCQD6stMfKEVAGTANBgkqhkiG9w0BAQsFADAZMQswCQYDVQQGEwJV +UzEKMAgGA1UEAwwBKjAeFw0yMjA2MTUwNzE4MDRaFw0zMjA2MTIwNzE4MDRaMBkx +CzAJBgNVBAYTAlVTMQowCAYDVQQDDAEqMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAv25FFUfMGKdF9wRb3GBBmE0F0ifMz8nUb52nEX0+BGFg+x6Q8gRD +xTCvCwZoMOo2uRZpaz6KtGQywFuuQ5h8VrYB3WcuDPcpg1OJueWrITRY0NHUBMjh +JbvvqZGs8I/DfKoQOyxcXoeIMfbSW2IHnMeHzTfE9lkGiZatQVqlRQAOkshFIed6 +YZ2p3hgxrzkPmTTiDNoQQotluTN0FcNgUwiKYGINVlmH4LaDobranBaMLztulWsK +0tQiDw2e7aaAZmxqaUN6QRMzwc1g2mvj+PXNf/V3/WGymsXUShy9zCx4PiMDuINW +fA/EhAdkZk0MrpYbfk221gSfxxvnd1xFLwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +AQCwRcvdYlSPYypWh7B1gCvqoq4XNk8YiRPs8X4JLly41uXlgQLfDw9oJUnR0h8c +TNObdEZEdsneinC8Gwg9hscooB2R5iYyqDjST0NAS7jPDDx9x42iRY6naUW7zuVZ +2IC/9ss4BZYlS0zztUOBDax9YOJxV3dwROwnfFqGiiiYqWuDfhlfUaHj6q+NAX9/ +xISi7YOoym7wHgMlMIEwGKfMLPOgvZBnU8vMqVqhbxMjAx42VdWyyV8AOcIZxasq +jXcaCczmsh5VMZHNocnTmivmY3KwmUXgC+/4w7E/fRGPblh+2CKxBO6Wlz+ZyN1n +CNgC8QQ1opc4yanhaAZgn/Yd +-----END CERTIFICATE----- +` + serverKey = ` +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZzMrH+mOavXSn +XHV9lMywwmZARE0TLpuyUTBDuYwVHG13Yx5X7br3uwEtyp2tTUBMHRh+2uVf/7lm +fQfy0AoE5O2qa4EvuOc4zS8eHr1gW4rFC++EzA2FrTMLGczIwKzPPa4VAMteQyCj +LYmfdrOSLSM0Fn7TPFJ1c3DBjj1pY/DdIs/GPItPnL7B2m+gNSQ9E5lRJ/DmP1j8 +2+hnScbB5GiLqdZBASd7T+Dwp7gUqZInMKdW6JJjbUY9xNXcHrCS7GINuXHbo5CK +z9Z19nl7+v0fzQbcVP6fH2jWAo7vfFd9IR+Tz/7zJ76UescRS1ONEu6071KfTA7j +fW1dhgqLAgMBAAECggEAdbH0NsK5Boqwuiv9laJORoqWpM4D9ISwQFkdQsvGxjW5 +ddWLNSrTaUGV7n/aWycuwrLKZOq3Hvxa3OZd4DnJ4EExqXE0u2wpDwnaF2W3IpX1 +VGwRv+pguEcTGUGU5zsvZ0JGizUFsOeHgIaAIzsK6MgZiPFLEa08Rhne6cmKqCMI +tueV7Rqwbbd6+pvnvHFqSrA9KHaJax9+GY0rDr3KlzuJkjK0Wy1D13j/1fkDzalN +opsxF3rShEuTHAVEiwjS+nzD5g6KZ3q2WDTRSKVAbh50rBOEPE9ERwY0vlEQRgKP +4YX/UH1DLDRiDguM2Fr4nhIiTlL98nF6EVLnD7dugQKBgQD+8ZWrvgRlvcqVJmB8 +BloKWsoyJp3BgBO77ys0j1fgmQe+TPLpsnVetGmjIK75EQmv/30/lFoLXIM9sfcY +4WgOsnndXFS8gKLfvG8+yMVoSwxNIR/2dNnqZuohqp5LCMlamb7Xvb2rkeSMOx4P +Q0OkTgd0YSl2l7VUwCmBs0VBSwKBgQDas89DAkBVN6q0NRd3iqrjcAN5kKchFO3W +IyDVwrrqhodnx7c7GncPULacMjOs1FPDrhiBoE7x5pDO4v30uVQKrKRZbmN4EHJC +lAUVGKBJFG5RFGzqWrCWf4Hdzynlv5na2SYtGfcmEV5F5ja9ZoDxsOdnjvy6yh0p +E44mtB3TwQKBgGVxASn2EM/e5eXVAF05Ncia+Ytc/DaLXM7RyrI+Oyw+F+uruJgu +jy8gwEvNbHHkSqOCGHcc83tD02DQGE8JGZuHfqAK5hifYq99zhIAVzQ5cGqcPJiX +REJVsuG0fwnCNERdmqdDc136TiNSPpK6JAcTmTnAk3wBv4A6egmGqI7jAoGAG4Vb +BIyo+dBKe+jebh2WCY7T8R1B2sjecP70p9GcYdzR9z5LkXVwHA5FHHy4wfvqGoqy +7MT2ijxAZrhryrrzl3BIMjTQ8Y/oQPaNeS0jJm8avrs6RXdqF1YuSnJCTHYC72Y6 +Bpzo2/J9kYA5zTWz7jYbuI1mwj6i0sNyNO6ffkECgYBJ3xZ/h5zDsrbxPDiTsXCs +3liEvmzCibkoCexflz/sN524E1d3F5jj+gsTvcXTvYTaSKLkjRgvtGT9deSCJt76 +wKprVZnZJeY+y21aOW1+HM22zfrGJ1GLt1Qjx33GLDzxPJDL9gsHzyRM0S2wLa8u +z9TAYr37qFPTWOJJtmRitg== +-----END PRIVATE KEY----- +` + serverCert = ` +-----BEGIN CERTIFICATE----- +MIICyDCCAbCgAwIBAgIJAJA97n2wo1wBMA0GCSqGSIb3DQEBBQUAMBkxCzAJBgNV +BAYTAlVTMQowCAYDVQQDDAEqMB4XDTIyMDYxNTA3MTkwOVoXDTIzMDYxNTA3MTkw +OVowGTELMAkGA1UEBhMCVVMxCjAIBgNVBAMMASowggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDZzMrH+mOavXSnXHV9lMywwmZARE0TLpuyUTBDuYwVHG13 +Yx5X7br3uwEtyp2tTUBMHRh+2uVf/7lmfQfy0AoE5O2qa4EvuOc4zS8eHr1gW4rF +C++EzA2FrTMLGczIwKzPPa4VAMteQyCjLYmfdrOSLSM0Fn7TPFJ1c3DBjj1pY/Dd +Is/GPItPnL7B2m+gNSQ9E5lRJ/DmP1j82+hnScbB5GiLqdZBASd7T+Dwp7gUqZIn +MKdW6JJjbUY9xNXcHrCS7GINuXHbo5CKz9Z19nl7+v0fzQbcVP6fH2jWAo7vfFd9 +IR+Tz/7zJ76UescRS1ONEu6071KfTA7jfW1dhgqLAgMBAAGjEzARMA8GA1UdEQQI +MAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADggEBAJTUv9eqrYJnF9ugKpC6QjsxQzJm +6A9Xwc13ORcno+NlYip1t0ITgW2ZebUboqvScy2B+IHM9HyQGkmVu8bXFJQ1clxy +HHA7Xp3xM+zB1KnvQ0ZPbaHnquOl39nULOCTxqSWCN8fqCuA/X3y9UiYL6P1cYRz +GFuoSgMoU+CrhkMQbF8c4dvl1Wr52Re9SYoX0o74N45pXhOMjeqUq2Mauapq7pFE ++21Y+KHEXojBWEufU8F6ihmWDMqu2vbbfODT63qp+6+VAiQOvMrBvsjW7Iq86VLE +8jddCYLRLwQjqaSn6APSSEocuzzP2J6CeGA4mxXHZz5gD22pugKbKLjHxwQ= +-----END CERTIFICATE----- +` + incorrectCert = ` -----BEGIN CERTIFICATE----- MIID/jCCAuagAwIBAgIUO/ncrEaWxLUqZ8IioBVCRl1P2R4wDQYJKoZIhvcNAQEL BQAwgagxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH @@ -44,30 +115,132 @@ zwaAnAW1ZiriAbeFx+xOaO1fETVSm+5Poyl97r5Mmu97+3IpoWHFPO2z4Os9vn3q XsKvL2lz2uQY+ZbrfvrL20p2 -----END CERTIFICATE----- ` - invalid_cert = ` + invalidCert = ` -----BEGIN CERTIFICATE----- invalidCert -----END CERTIFICATE----- ` ) -func TestValidateCertValidCert(t *testing.T) { - tv := crypto.NewTlsValidator() - if err := tv.ValidateCert(endpoint, port, cert); err != nil { - t.Fatalf("Failed to validate cert: %v", err) +func TestHasSelfSignedCert(t *testing.T) { + certSvr, err := runTestServerWithCert(serverCert, serverKey) + if err != nil { + t.Fatalf("starting test server with certs: %v", err) + } + defer certSvr.Close() + certServerPort := strings.Split(certSvr.URL, ":")[2] + svr, err := runTestServer() + if err != nil { + t.Fatalf("starting test server: %v", err) + } + defer svr.Close() + serverPort := strings.Split(svr.URL, ":")[2] + tests := []struct { + testName string + endpoint string + port string + wantCert bool + wantError bool + }{ + { + testName: "valid cert", + endpoint: endpoint, + port: certServerPort, + wantCert: true, + wantError: false, + }, + { + testName: "invalid endpoint", + endpoint: invalidEndpoint, + port: serverPort, + wantCert: false, + wantError: true, + }, + } + for _, tc := range tests { + t.Run(tc.testName, func(t *testing.T) { + tv := crypto.NewTlsValidator() + hasCert, err := tv.HasSelfSignedCert(tc.endpoint, tc.port) + if (err != nil) != tc.wantError { + t.Fatalf("HasSelfSignedCert() error = %v, wantError %v", err, tc.wantError) + } + if hasCert != tc.wantCert { + t.Fatalf("HasSelfSignedCert() returned %v, want %v", hasCert, tc.wantCert) + } + }) } } -func TestValidateCertInvalidEndpoint(t *testing.T) { - tv := crypto.NewTlsValidator() - if err := tv.ValidateCert(invalid_endpoint, port, cert); err == nil { - t.Fatalf("Certificate validation passed for invalid endpoint") +func TestValidateCert(t *testing.T) { + svr, err := runTestServerWithCert(serverCert, serverKey) + if err != nil { + t.Fatalf("starting test server with certs: %v", err) + } + defer svr.Close() + serverPort := strings.Split(svr.URL, ":")[2] + tests := []struct { + testName string + endpoint string + port string + caCert string + wantError bool + }{ + { + testName: "valid cert", + endpoint: endpoint, + port: serverPort, + caCert: caCert, + wantError: false, + }, + { + testName: "invalid endpoint", + endpoint: invalidEndpoint, + port: serverPort, + caCert: caCert, + wantError: true, + }, + { + testName: "incorrect cert", + endpoint: endpoint, + port: serverPort, + caCert: incorrectCert, + wantError: true, + }, + { + testName: "invalid cert format", + endpoint: endpoint, + port: serverPort, + caCert: invalidCert, + wantError: true, + }, + } + for _, tc := range tests { + t.Run(tc.testName, func(t *testing.T) { + tv := crypto.NewTlsValidator() + err := tv.ValidateCert(tc.endpoint, tc.port, tc.caCert) + if (err != nil) != tc.wantError { + t.Fatalf("ValidateCert() error = %v, wantError %v", err, tc.wantError) + } + }) } } -func TestValidateCertInvalidCert(t *testing.T) { - tv := crypto.NewTlsValidator() - if err := tv.ValidateCert(endpoint, port, invalid_cert); err == nil { - t.Fatalf("Certificate validation passed for invalid cert") +func runTestServerWithCert(serverCert, serverKey string) (*httptest.Server, error) { + mux := http.NewServeMux() + svr := httptest.NewUnstartedServer(mux) + certificate, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) + if err != nil { + return nil, fmt.Errorf("creating key pair: %v", err) } + svr.TLS = &tls.Config{ + Certificates: []tls.Certificate{certificate}, + } + svr.StartTLS() + return svr, nil +} + +func runTestServer() (*httptest.Server, error) { + mux := http.NewServeMux() + svr := httptest.NewServer(mux) + return svr, nil } diff --git a/pkg/validations/cluster.go b/pkg/validations/cluster.go index aab4119cc912..f322249fdbf4 100644 --- a/pkg/validations/cluster.go +++ b/pkg/validations/cluster.go @@ -35,8 +35,7 @@ func ValidateCertForRegistryMirror(clusterSpec *cluster.Spec, tlsValidator TlsVa } if certContent != "" { - err := tlsValidator.ValidateCert(host, port, certContent) - if err != nil { + if err = tlsValidator.ValidateCert(host, port, certContent); err != nil { return fmt.Errorf("invalid registry certificate: %v", err) } } diff --git a/pkg/validations/mocks/tls.go b/pkg/validations/mocks/tls.go index 5a5a47d290a2..1e9bfd2dc7be 100644 --- a/pkg/validations/mocks/tls.go +++ b/pkg/validations/mocks/tls.go @@ -49,15 +49,15 @@ func (mr *MockTlsValidatorMockRecorder) HasSelfSignedCert(host, port interface{} } // ValidateCert mocks base method. -func (m *MockTlsValidator) ValidateCert(host, port, cert string) error { +func (m *MockTlsValidator) ValidateCert(host, port, caCertContent string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateCert", host, port, cert) + ret := m.ctrl.Call(m, "ValidateCert", host, port, caCertContent) ret0, _ := ret[0].(error) return ret0 } // ValidateCert indicates an expected call of ValidateCert. -func (mr *MockTlsValidatorMockRecorder) ValidateCert(host, port, cert interface{}) *gomock.Call { +func (mr *MockTlsValidatorMockRecorder) ValidateCert(host, port, caCertContent interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateCert", reflect.TypeOf((*MockTlsValidator)(nil).ValidateCert), host, port, cert) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateCert", reflect.TypeOf((*MockTlsValidator)(nil).ValidateCert), host, port, caCertContent) } diff --git a/pkg/validations/tls.go b/pkg/validations/tls.go index 5caffbc9845d..dec48706d966 100644 --- a/pkg/validations/tls.go +++ b/pkg/validations/tls.go @@ -1,6 +1,6 @@ package validations type TlsValidator interface { - ValidateCert(host, port, cert string) error + ValidateCert(host, port, caCertContent string) error HasSelfSignedCert(host, port string) (bool, error) }