github.com/macb/etcd@v0.3.1-0.20140227003422-a60481c6b1a0/tests/functional/etcd_tls_test.go (about)

     1  package test
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  // TestTLSOff asserts that non-TLS-encrypted communication between the
    18  // etcd server and an unauthenticated client works
    19  func TestTLSOff(t *testing.T) {
    20  	proc, err := startServer([]string{})
    21  	if err != nil {
    22  		t.Fatal(err.Error())
    23  	}
    24  	defer stopServer(proc)
    25  
    26  	client := buildClient()
    27  	err = assertServerFunctional(client, "http")
    28  	if err != nil {
    29  		t.Fatal(err.Error())
    30  	}
    31  }
    32  
    33  // TestTLSAnonymousClient asserts that TLS-encrypted communication between the etcd
    34  // server and an anonymous client works
    35  func TestTLSAnonymousClient(t *testing.T) {
    36  	proc, err := startServer([]string{
    37  		"-cert-file=../../fixtures/ca/server.crt",
    38  		"-key-file=../../fixtures/ca/server.key.insecure",
    39  	})
    40  	if err != nil {
    41  		t.Fatal(err.Error())
    42  	}
    43  	defer stopServer(proc)
    44  
    45  	cacertfile := "../../fixtures/ca/ca.crt"
    46  
    47  	cp := x509.NewCertPool()
    48  	bytes, err := ioutil.ReadFile(cacertfile)
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  	cp.AppendCertsFromPEM(bytes)
    53  
    54  	cfg := tls.Config{}
    55  	cfg.RootCAs = cp
    56  
    57  	client := buildTLSClient(&cfg)
    58  	err = assertServerFunctional(client, "https")
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  }
    63  
    64  // TestTLSAuthenticatedClient asserts that TLS-encrypted communication
    65  // between the etcd server and an authenticated client works
    66  func TestTLSAuthenticatedClient(t *testing.T) {
    67  	proc, err := startServer([]string{
    68  		"-cert-file=../../fixtures/ca/server.crt",
    69  		"-key-file=../../fixtures/ca/server.key.insecure",
    70  		"-ca-file=../../fixtures/ca/ca.crt",
    71  	})
    72  	if err != nil {
    73  		t.Fatal(err.Error())
    74  	}
    75  	defer stopServer(proc)
    76  
    77  	cacertfile := "../../fixtures/ca/ca.crt"
    78  	certfile := "../../fixtures/ca/server2.crt"
    79  	keyfile := "../../fixtures/ca/server2.key.insecure"
    80  
    81  	cert, err := tls.LoadX509KeyPair(certfile, keyfile)
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  
    86  	cp := x509.NewCertPool()
    87  	bytes, err := ioutil.ReadFile(cacertfile)
    88  	if err != nil {
    89  		panic(err)
    90  	}
    91  	cp.AppendCertsFromPEM(bytes)
    92  
    93  	cfg := tls.Config{}
    94  	cfg.Certificates = []tls.Certificate{cert}
    95  	cfg.RootCAs = cp
    96  
    97  	time.Sleep(time.Second)
    98  
    99  	client := buildTLSClient(&cfg)
   100  	err = assertServerFunctional(client, "https")
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  }
   105  
   106  // TestTLSUnathenticatedClient asserts that TLS-encrypted communication
   107  // between the etcd server and an unauthenticated client fails
   108  func TestTLSUnauthenticatedClient(t *testing.T) {
   109  	proc, err := startServer([]string{
   110  		"-cert-file=../../fixtures/ca/server.crt",
   111  		"-key-file=../../fixtures/ca/server.key.insecure",
   112  		"-ca-file=../../fixtures/ca/ca.crt",
   113  	})
   114  	if err != nil {
   115  		t.Fatal(err.Error())
   116  	}
   117  	defer stopServer(proc)
   118  
   119  	cacertfile := "../../fixtures/ca/ca.crt"
   120  	certfile := "../../fixtures/ca/broken/server.crt"
   121  	keyfile := "../../fixtures/ca/broken/server.key.insecure"
   122  
   123  	cert, err := tls.LoadX509KeyPair(certfile, keyfile)
   124  	if err != nil {
   125  		panic(err)
   126  	}
   127  
   128  	cp := x509.NewCertPool()
   129  	bytes, err := ioutil.ReadFile(cacertfile)
   130  	if err != nil {
   131  		panic(err)
   132  	}
   133  	cp.AppendCertsFromPEM(bytes)
   134  
   135  	cfg := tls.Config{}
   136  	cfg.Certificates = []tls.Certificate{cert}
   137  	cfg.RootCAs = cp
   138  
   139  	time.Sleep(time.Second)
   140  
   141  	client := buildTLSClient(&cfg)
   142  	err = assertServerNotFunctional(client, "https")
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  }
   147  
   148  
   149  func buildClient() http.Client {
   150  	return http.Client{}
   151  }
   152  
   153  func buildTLSClient(tlsConf *tls.Config) http.Client {
   154  	tr := http.Transport{TLSClientConfig: tlsConf}
   155  	return http.Client{Transport: &tr}
   156  }
   157  
   158  func startServer(extra []string) (*os.Process, error) {
   159  	procAttr := new(os.ProcAttr)
   160  	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
   161  
   162  	cmd := []string{"etcd",	"-f", "-data-dir=/tmp/node1", "-name=node1"}
   163  	cmd = append(cmd, extra...)
   164  
   165  	println(strings.Join(cmd, " "))
   166  
   167  	return os.StartProcess(EtcdBinPath, cmd, procAttr)
   168  }
   169  
   170  func startServerWithDataDir(extra []string) (*os.Process, error) {
   171  	procAttr := new(os.ProcAttr)
   172  	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
   173  
   174  	cmd := []string{"etcd",	"-data-dir=/tmp/node1", "-name=node1"}
   175  	cmd = append(cmd, extra...)
   176  
   177  	println(strings.Join(cmd, " "))
   178  
   179  	return os.StartProcess(EtcdBinPath, cmd, procAttr)
   180  }
   181  
   182  func stopServer(proc *os.Process) {
   183  	err := proc.Kill()
   184  	if err != nil {
   185  		panic(err.Error())
   186  	}
   187  	proc.Release()
   188  }
   189  
   190  func assertServerFunctional(client http.Client, scheme string) error {
   191  	path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
   192  	fields := url.Values(map[string][]string{"value": []string{"bar"}})
   193  
   194  	for i := 0; i < 10; i++ {
   195  		time.Sleep(1 * time.Second)
   196  
   197  		resp, err := client.PostForm(path, fields)
   198  		// If the status is Temporary Redirect, we should follow the
   199  		// new location, because the request did not go to the leader yet.
   200  		// TODO(yichengq): the difference between Temporary Redirect(307)
   201  		// and Created(201) could distinguish between leader and followers
   202  		for err == nil && resp.StatusCode == http.StatusTemporaryRedirect {
   203  			loc, _ := resp.Location()
   204  			newPath := loc.String()
   205  			resp, err = client.PostForm(newPath, fields)
   206  		}
   207  
   208  		if err == nil {
   209  			// Internal error may mean that servers are in leader election
   210  			if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusInternalServerError {
   211  				return errors.New(fmt.Sprintf("resp.StatusCode == %s", resp.Status))
   212  			} else {
   213  				return nil
   214  			}
   215  		}
   216  	}
   217  
   218  	return errors.New("etcd server was not reachable in time / had internal error")
   219  }
   220  
   221  func assertServerNotFunctional(client http.Client, scheme string) error {
   222  	path := fmt.Sprintf("%s://127.0.0.1:4001/v2/keys/foo", scheme)
   223  	fields := url.Values(map[string][]string{"value": []string{"bar"}})
   224  
   225  	for i := 0; i < 10; i++ {
   226  		time.Sleep(1 * time.Second)
   227  
   228  		_, err := client.PostForm(path, fields)
   229  		if err == nil {
   230  			return errors.New("Expected error during POST, got nil")
   231  		} else {
   232  			errString := err.Error()
   233  			if strings.Contains(errString, "connection refused") {
   234  				continue
   235  			} else if strings.Contains(errString, "bad certificate") {
   236  				return nil
   237  			} else {
   238  				return err
   239  			}
   240  		}
   241  	}
   242  
   243  	return errors.New("Expected server to fail with 'bad certificate'")
   244  }