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 }