package api import ( "context" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "fmt" "net" "net/http" "net/url" "testing" "github.com/go-test/deep" "github.com/linkerd/linkerd2/controller/k8s" k8sutils "github.com/linkerd/linkerd2/pkg/k8s" ) func TestAPIServerAuth(t *testing.T) { expectations := []struct { k8sRes []string clientCAPem string allowedNames []string usernameHeader string groupHeader string err error }{ { err: fmt.Errorf("failed to load [%s] config: configmaps %q not found", k8sutils.ExtensionAPIServerAuthenticationConfigMapName, k8sutils.ExtensionAPIServerAuthenticationConfigMapName), }, { k8sRes: []string{` apiVersion: v1 kind: ConfigMap metadata: name: extension-apiserver-authentication namespace: kube-system data: client-ca-file: 'client-ca-file' requestheader-allowed-names: '["name1", "name2"]' requestheader-client-ca-file: 'requestheader-client-ca-file' requestheader-extra-headers-prefix: '["X-Remote-Extra-"]' requestheader-group-headers: '["X-Remote-Group"]' requestheader-username-headers: '["X-Remote-User"]' `, }, clientCAPem: "requestheader-client-ca-file", allowedNames: []string{"name1", "name2"}, usernameHeader: "X-Remote-User", groupHeader: "X-Remote-Group", err: nil, }, } ctx := context.Background() for i, exp := range expectations { exp := exp // pin t.Run(fmt.Sprintf("%d parses the apiServerAuth ConfigMap", i), func(t *testing.T) { k8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...) if err != nil { t.Fatalf("NewFakeAPI returned an error: %s", err) } clientCAPem, allowedNames, usernameHeader, groupHeader, err := serverAuth(ctx, k8sAPI) if err != nil && exp.err != nil { if err.Error() != exp.err.Error() { t.Errorf("apiServerAuth returned unexpected error: %q, expected: %q", err, exp.err) } } else if err != nil { t.Fatalf("Unexpected error: %s", err) } else if exp.err != nil { t.Fatalf("Did not encounter expected error: %s", err) } if clientCAPem != exp.clientCAPem { t.Errorf("apiServerAuth returned unexpected clientCAPem: %q, expected: %q", clientCAPem, exp.clientCAPem) } if diff := deep.Equal(allowedNames, exp.allowedNames); diff != nil { t.Errorf("%v", diff) } if usernameHeader != exp.usernameHeader { t.Errorf("apiServerAuth returned unexpected usernameHeader: %q, expected: %q", usernameHeader, exp.usernameHeader) } if groupHeader != exp.groupHeader { t.Errorf("apiServerAuth returned unexpected groupHeader: %q, expected: %q", groupHeader, exp.groupHeader) } }) } } func TestValidate(t *testing.T) { cert := testCertificate() cert.Subject.CommonName = "name-any" tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}} req := http.Request{TLS: &tls} server := Server{} if err := server.validate(&req); err != nil { t.Fatalf("No error expected for %q but encountered %q", cert.Subject.CommonName, err.Error()) } } func TestValidate_ClientAllowed(t *testing.T) { cert := testCertificate() cert.Subject.CommonName = "name-trusted" tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}} req := http.Request{TLS: &tls} server := Server{allowedNames: []string{"name-trusted"}} if err := server.validate(&req); err != nil { t.Fatalf("No error expected for %q but encountered %q", cert.Subject.CommonName, err.Error()) } } func TestValidate_ClientAllowedViaSAN(t *testing.T) { cert := testCertificate() cert.Subject.CommonName = "name-any" tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}} req := http.Request{TLS: &tls} server := Server{allowedNames: []string{"linkerd.io"}} if err := server.validate(&req); err != nil { t.Fatalf("No error expected for %q but encountered %q", cert.Subject.CommonName, err.Error()) } } func TestValidate_ClientNotAllowed(t *testing.T) { cert := testCertificate() cert.Subject.CommonName = "name-untrusted" tls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}} req := http.Request{TLS: &tls} server := Server{allowedNames: []string{"name-trusted"}} if err := server.validate(&req); err == nil { t.Fatalf("Expected request to be rejected for %q", cert.Subject.CommonName) } } func TestIsSubjectAlternateName(t *testing.T) { testCases := []struct { name string expected bool }{ { name: "linkerd.io", expected: true, }, { name: "root@localhost", expected: true, }, { name: "192.168.1.1", expected: true, }, { name: "http://localhost/api/test", expected: true, }, { name: "mystique", expected: false, }, } cert := testCertificate() for _, tc := range testCases { tc := tc // pin t.Run(tc.name, func(t *testing.T) { actual := isSubjectAlternateName(&cert, tc.name) if actual != tc.expected { t.Fatalf("expected %t, but got %t", tc.expected, actual) } }) } } func testCertificate() x509.Certificate { uri, _ := url.Parse("http://localhost/api/test") cert := x509.Certificate{ Subject: pkix.Name{ CommonName: "linkerd-test", }, DNSNames: []string{ "localhost", "linkerd.io", }, EmailAddresses: []string{ "root@localhost", }, IPAddresses: []net.IP{ net.IPv4(127, 0, 0, 1), net.IPv4(192, 168, 1, 1), }, URIs: []*url.URL{ uri, }, } return cert }