123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- package test
- import (
- "crypto/tls"
- "crypto/x509"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "strings"
- "testing"
- "time"
- )
- // TestTLSOff asserts that non-TLS-encrypted communication between the
- // etcd server and an unauthenticated client works
- func TestTLSOff(t *testing.T) {
- proc, err := startServer([]string{})
- if err != nil {
- t.Fatal(err.Error())
- }
- defer stopServer(proc)
- client := buildClient()
- err = assertServerFunctional(client, "http")
- if err != nil {
- t.Fatal(err.Error())
- }
- }
- // TestTLSAnonymousClient asserts that TLS-encrypted communication between the etcd
- // server and an anonymous client works
- func TestTLSAnonymousClient(t *testing.T) {
- proc, err := startServer([]string{
- "-cert-file=../../fixtures/ca/server.crt",
- "-key-file=../../fixtures/ca/server.key.insecure",
- })
- if err != nil {
- t.Fatal(err.Error())
- }
- defer stopServer(proc)
- cacertfile := "../../fixtures/ca/ca.crt"
- cp := x509.NewCertPool()
- bytes, err := ioutil.ReadFile(cacertfile)
- if err != nil {
- panic(err)
- }
- cp.AppendCertsFromPEM(bytes)
- cfg := tls.Config{}
- cfg.RootCAs = cp
- client := buildTLSClient(&cfg)
- err = assertServerFunctional(client, "https")
- if err != nil {
- t.Fatal(err)
- }
- }
- // TestTLSAuthenticatedClient asserts that TLS-encrypted communication
- // between the etcd server and an authenticated client works
- func TestTLSAuthenticatedClient(t *testing.T) {
- proc, err := startServer([]string{
- "-cert-file=../../fixtures/ca/server.crt",
- "-key-file=../../fixtures/ca/server.key.insecure",
- "-ca-file=../../fixtures/ca/ca.crt",
- })
- if err != nil {
- t.Fatal(err.Error())
- }
- defer stopServer(proc)
- cacertfile := "../../fixtures/ca/ca.crt"
- certfile := "../../fixtures/ca/server2.crt"
- keyfile := "../../fixtures/ca/server2.key.insecure"
- cert, err := tls.LoadX509KeyPair(certfile, keyfile)
- if err != nil {
- panic(err)
- }
- cp := x509.NewCertPool()
- bytes, err := ioutil.ReadFile(cacertfile)
- if err != nil {
- panic(err)
- }
- cp.AppendCertsFromPEM(bytes)
- cfg := tls.Config{}
- cfg.Certificates = []tls.Certificate{cert}
- cfg.RootCAs = cp
- time.Sleep(time.Second)
- client := buildTLSClient(&cfg)
- err = assertServerFunctional(client, "https")
- if err != nil {
- t.Fatal(err)
- }
- }
- // TestTLSUnathenticatedClient asserts that TLS-encrypted communication
- // between the etcd server and an unauthenticated client fails
- func TestTLSUnauthenticatedClient(t *testing.T) {
- proc, err := startServer([]string{
- "-cert-file=../../fixtures/ca/server.crt",
- "-key-file=../../fixtures/ca/server.key.insecure",
- "-ca-file=../../fixtures/ca/ca.crt",
- })
- if err != nil {
- t.Fatal(err.Error())
- }
- defer stopServer(proc)
- cacertfile := "../../fixtures/ca/ca.crt"
- certfile := "../../fixtures/ca/broken_server.crt"
- keyfile := "../../fixtures/ca/broken_server.key.insecure"
- cert, err := tls.LoadX509KeyPair(certfile, keyfile)
- if err != nil {
- panic(err)
- }
- cp := x509.NewCertPool()
- bytes, err := ioutil.ReadFile(cacertfile)
- if err != nil {
- panic(err)
- }
- cp.AppendCertsFromPEM(bytes)
- cfg := tls.Config{}
- cfg.Certificates = []tls.Certificate{cert}
- cfg.RootCAs = cp
- time.Sleep(time.Second)
- client := buildTLSClient(&cfg)
- err = assertServerNotFunctional(client, "https")
- if err != nil {
- t.Fatal(err)
- }
- }
- func buildClient() http.Client {
- return http.Client{}
- }
- func buildTLSClient(tlsConf *tls.Config) http.Client {
- tr := http.Transport{TLSClientConfig: tlsConf}
- return http.Client{Transport: &tr}
- }
- func startServer(extra []string) (*os.Process, error) {
- procAttr := new(os.ProcAttr)
- procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
- cmd := []string{"etcd", "-f", "-data-dir=/tmp/node1", "-name=node1"}
- cmd = append(cmd, extra...)
- println(strings.Join(cmd, " "))
- return os.StartProcess(EtcdBinPath, cmd, procAttr)
- }
- // TODO(yichengq): refactor these helper functions in #645
- func startServer2(extra []string) (*os.Process, error) {
- procAttr := new(os.ProcAttr)
- procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
- cmd := []string{"etcd", "-f", "-data-dir=/tmp/node2", "-name=node2"}
- cmd = append(cmd, extra...)
- fmt.Println(strings.Join(cmd, " "))
- return os.StartProcess(EtcdBinPath, cmd, procAttr)
- }
- func startServerWithDataDir(extra []string) (*os.Process, error) {
- procAttr := new(os.ProcAttr)
- procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
- cmd := []string{"etcd", "-data-dir=/tmp/node1", "-name=node1"}
- cmd = append(cmd, extra...)
- fmt.Println(strings.Join(cmd, " "))
- return os.StartProcess(EtcdBinPath, cmd, procAttr)
- }
- func startServer2WithDataDir(extra []string) (*os.Process, error) {
- procAttr := new(os.ProcAttr)
- procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
- cmd := []string{"etcd", "-data-dir=/tmp/node2", "-name=node2"}
- cmd = append(cmd, extra...)
- println(strings.Join(cmd, " "))
- return os.StartProcess(EtcdBinPath, cmd, procAttr)
- }
- func stopServer(proc *os.Process) {
- err := proc.Kill()
- if err != nil {
- panic(err.Error())
- }
- proc.Release()
- }
- func assertServerFunctional(client http.Client, scheme string) error {
- path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
- fields := url.Values(map[string][]string{"value": {"bar"}})
- for i := 0; i < 10; i++ {
- time.Sleep(1 * time.Second)
- resp, err := client.PostForm(path, fields)
- // If the status is Temporary Redirect, we should follow the
- // new location, because the request did not go to the leader yet.
- // TODO(yichengq): the difference between Temporary Redirect(307)
- // and Created(201) could distinguish between leader and followers
- for err == nil && resp.StatusCode == http.StatusTemporaryRedirect {
- loc, _ := resp.Location()
- newPath := loc.String()
- resp, err = client.PostForm(newPath, fields)
- }
- if err == nil {
- // Internal error may mean that servers are in leader election
- if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusInternalServerError {
- return errors.New(fmt.Sprintf("resp.StatusCode == %s", resp.Status))
- } else {
- return nil
- }
- }
- }
- return errors.New("etcd server was not reachable in time / had internal error")
- }
- func assertServerNotFunctional(client http.Client, scheme string) error {
- path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
- fields := url.Values(map[string][]string{"value": {"bar"}})
- for i := 0; i < 10; i++ {
- time.Sleep(1 * time.Second)
- _, err := client.PostForm(path, fields)
- if err == nil {
- return errors.New("Expected error during POST, got nil")
- } else {
- errString := err.Error()
- if strings.Contains(errString, "connection refused") {
- continue
- } else if strings.Contains(errString, "bad certificate") {
- return nil
- } else {
- return err
- }
- }
- }
- return errors.New("Expected server to fail with 'bad certificate'")
- }
|