/*
Copyright 2019 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 dynamiccertificates

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"strings"
	"testing"
	"time"

	"k8s.io/client-go/util/cert"
)

func TestServingCert(t *testing.T) {
	tlsConfig := &tls.Config{
		// Can't use SSLv3 because of POODLE and BEAST
		// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
		// Can't use TLSv1.1 because of RC4 cipher usage
		MinVersion: tls.VersionTLS12,
		// enable HTTP2 for go's 1.7 HTTP Server
		NextProtos: []string{"h2", "http/1.1"},
	}

	defaultCertProvider, err := createTestTLSCerts(testCertSpec{
		host:  "172.30.0.1",
		ips:   []string{"172.30.0.1"},
		names: []string{"kubernetes", "kubernetes.default.svc.cluster.local"},
	}, nil)
	if err != nil {
		t.Fatal(err)
	}

	var sniCerts []SNICertKeyContentProvider
	certSpecs := []testCertSpec{
		{
			host:  "172.30.0.1",
			ips:   []string{"172.30.0.1"},
			names: []string{"openshift", "openshift.default.svc.cluster.local"},
		},
		{
			host:  "127.0.0.1",
			ips:   []string{"127.0.0.1"},
			names: []string{"localhost"},
		},
		{
			host:  "2001:abcd:bcda::1",
			ips:   []string{"2001:abcd:bcda::1"},
			names: []string{"openshiftv6", "openshiftv6.default.svc.cluster.local"},
		},
	}

	for _, certSpec := range certSpecs {
		names := append([]string{}, certSpec.ips...)
		names = append(names, certSpec.names...)
		certProvider, err := createTestTLSCerts(certSpec, names)
		if err != nil {
			t.Fatal(err)
		}
		sniCerts = append(sniCerts, certProvider)
	}

	dynamicCertificateController := NewDynamicServingCertificateController(
		*tlsConfig,
		&nullCAContent{name: "client-ca"},
		defaultCertProvider,
		sniCerts,
		nil, // TODO see how to plumb an event recorder down in here. For now this results in simply klog messages.
	)
	if err := dynamicCertificateController.RunOnce(); err != nil {
		t.Fatal(err)
	}
	tlsConfig.GetConfigForClient = dynamicCertificateController.GetConfigForClient
	tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return nil, fmt.Errorf("positive failure") }

	stopCh := make(chan struct{})
	defer close(stopCh)
	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			w.WriteHeader(http.StatusOK)
		}),
		MaxHeaderBytes: 1 << 20,
		TLSConfig:      tlsConfig,
	}
	listener, _, err := createListener("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	apiPort := listener.Addr().String()
	go func() {
		t.Logf("listening on %s", listener.Addr().String())
		listener = tls.NewListener(listener, server.TLSConfig)
		if err := server.ServeTLS(listener, "", ""); err != nil {
			t.Error(err)
			panic(err)
		}

		<-stopCh
		server.Close()
		t.Logf("stopped listening on %s", listener.Addr().String())
	}()

	time.Sleep(1 * time.Second)

	expectedDefaultCertBytes, _ := defaultCertProvider.CurrentCertKeyContent()
	expectedServiceCertBytes, _ := sniCerts[0].CurrentCertKeyContent()
	expectedLocalhostCertBytes, _ := sniCerts[1].CurrentCertKeyContent()
	expectedServiceV6CertBytes, _ := sniCerts[2].CurrentCertKeyContent()

	tests := []struct {
		name       string
		serverName string
		expected   []byte
	}{
		{
			name:       "default", // the no server name hits 127.0.0.1, which we hope matches by IP
			serverName: "",
			expected:   []byte(strings.TrimSpace(string(expectedLocalhostCertBytes))),
		},
		{
			name:       "invalid",
			serverName: "not-marked",
			expected:   []byte(strings.TrimSpace(string(expectedDefaultCertBytes))),
		},
		{
			name:       "service by dns",
			serverName: "openshift",
			expected:   []byte(strings.TrimSpace(string(expectedServiceCertBytes))),
		},
		{
			name:       "service v6 by dns",
			serverName: "openshiftv6",
			expected:   []byte(strings.TrimSpace(string(expectedServiceV6CertBytes))),
		},
		{
			name:       "localhost by dns",
			serverName: "localhost",
			expected:   []byte(strings.TrimSpace(string(expectedLocalhostCertBytes))),
		},
		{
			name:       "localhost by IP",
			serverName: "127.0.0.1", // this can never actually happen, but let's see
			expected:   []byte(strings.TrimSpace(string(expectedLocalhostCertBytes))),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, actualDefaultCertBytes, err := cert.GetServingCertificates(apiPort, test.serverName)
			if err != nil {
				t.Fatal(err)
			}
			if e, a := test.expected, actualDefaultCertBytes[0]; !bytes.Equal(e, a) {
				eCert, _ := cert.ParseCertsPEM(e)
				aCert, _ := cert.ParseCertsPEM(a)
				t.Fatalf("expected %v\n, got %v", GetHumanCertDetail(eCert[0]), GetHumanCertDetail(aCert[0]))
			}
		})
	}

}

func createListener(network, addr string) (net.Listener, int, error) {
	if len(network) == 0 {
		network = "tcp"
	}
	ln, err := net.Listen(network, addr)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to listen on %v: %v", addr, err)
	}

	// get port
	tcpAddr, ok := ln.Addr().(*net.TCPAddr)
	if !ok {
		ln.Close()
		return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
	}

	return ln, tcpAddr.Port, nil
}

type nullCAContent struct {
	name string
}

var _ CAContentProvider = &nullCAContent{}

// Name is just an identifier
func (c *nullCAContent) Name() string {
	return c.name
}

// CurrentCABundleContent provides ca bundle byte content
func (c *nullCAContent) CurrentCABundleContent() (cabundle []byte) {
	return nil
}

func (c *nullCAContent) VerifyOptions() (x509.VerifyOptions, bool) {
	return x509.VerifyOptions{}, false
}
