github.com/coreos/mantle@v0.13.0/kola/tests/locksmith/locksmith.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package locksmith 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "time" 22 23 "golang.org/x/crypto/ssh" 24 "golang.org/x/net/context" 25 26 "github.com/coreos/mantle/kola/cluster" 27 "github.com/coreos/mantle/kola/register" 28 "github.com/coreos/mantle/kola/tests/etcd" 29 "github.com/coreos/mantle/lang/worker" 30 "github.com/coreos/mantle/platform" 31 "github.com/coreos/mantle/platform/conf" 32 "github.com/coreos/mantle/util" 33 ) 34 35 func init() { 36 register.Register(®ister.Test{ 37 Name: "cl.locksmith.cluster", 38 Run: locksmithCluster, 39 ClusterSize: 3, 40 UserData: conf.ContainerLinuxConfig(`locksmith: 41 reboot_strategy: etcd-lock 42 etcd: 43 listen_client_urls: http://0.0.0.0:2379 44 advertise_client_urls: http://{PRIVATE_IPV4}:2379 45 initial_advertise_peer_urls: http://{PRIVATE_IPV4}:2380 46 listen_peer_urls: http://{PRIVATE_IPV4}:2380 47 discovery: $discovery`), 48 Flags: []register.Flag{register.RequiresInternetAccess}, // etcdctl health-check requires networking 49 Distros: []string{"cl"}, 50 }) 51 register.Register(®ister.Test{ 52 Name: "coreos.locksmith.reboot", 53 Run: locksmithReboot, 54 ClusterSize: 1, 55 Distros: []string{"cl"}, 56 }) 57 register.Register(®ister.Test{ 58 Name: "coreos.locksmith.tls", 59 Run: locksmithTLS, 60 ClusterSize: 1, 61 UserData: conf.Ignition(`{ 62 "ignition": { "version": "2.0.0" }, 63 "systemd": { 64 "units": [ 65 { 66 "name": "certgen.service", 67 "contents": "[Unit]\nAfter=system-config.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStartPre=/usr/bin/mkdir -p /etc/ssl/certs\nExecStart=/usr/bin/openssl req -config /etc/ssl/etcd.cnf -x509 -nodes -newkey rsa:4096 -sha512 -days 3 -extensions etcd_ca -subj '/CN=etcd CA' -out /etc/ssl/certs/ca-etcd-cert.pem -keyout /etc/ssl/certs/ca-etcd-key.pem\nExecStart=/usr/bin/openssl req -config /etc/ssl/etcd.cnf -x509 -nodes -newkey rsa:4096 -sha512 -days 3 -extensions etcd_server -subj '/CN=localhost' -out /etc/ssl/certs/etcd-cert-self.pem -keyout /etc/ssl/certs/etcd-key.pem\nExecStart=/usr/bin/openssl x509 -extfile /etc/ssl/etcd.cnf -CA /etc/ssl/certs/ca-etcd-cert.pem -CAkey /etc/ssl/certs/ca-etcd-key.pem -CAcreateserial -sha512 -days 3 -in /etc/ssl/certs/etcd-cert-self.pem -out /etc/ssl/certs/etcd-cert.pem\nExecStart=/usr/bin/openssl req -config /etc/ssl/etcd.cnf -x509 -nodes -newkey rsa:4096 -sha512 -days 3 -extensions etcd_ca -subj '/CN=locksmith CA' -out /etc/ssl/certs/ca-locksmith-cert.pem -keyout /etc/ssl/certs/ca-locksmith-key.pem\nExecStart=/usr/bin/openssl req -config /etc/ssl/etcd.cnf -x509 -nodes -newkey rsa:4096 -sha512 -days 3 -extensions etcd_client -subj '/CN=locksmith client' -out /etc/ssl/certs/locksmith-cert-self.pem -keyout /etc/ssl/certs/locksmith-key.pem\nExecStart=/usr/bin/openssl x509 -extfile /etc/ssl/etcd.cnf -CA /etc/ssl/certs/ca-locksmith-cert.pem -CAkey /etc/ssl/certs/ca-locksmith-key.pem -CAcreateserial -sha512 -days 3 -in /etc/ssl/certs/locksmith-cert-self.pem -out /etc/ssl/certs/locksmith-cert.pem\nExecStart=/usr/bin/chmod 0644 /etc/ssl/certs/ca-etcd-cert.pem /etc/ssl/certs/ca-etcd-key.pem /etc/ssl/certs/ca-locksmith-cert.pem /etc/ssl/certs/ca-locksmith-key.pem /etc/ssl/certs/etcd-cert.pem /etc/ssl/certs/etcd-key.pem /etc/ssl/certs/locksmith-cert.pem /etc/ssl/certs/locksmith-key.pem\nExecStart=/usr/bin/ln -fns ca-etcd-cert.pem /etc/ssl/certs/etcd.pem\nExecStart=/usr/bin/c_rehash" 68 }, 69 { 70 "name": "etcd-member.service", 71 "dropins": [{ 72 "name": "environment.conf", 73 "contents": "[Unit]\nAfter=certgen.service\nRequires=certgen.service\n[Service]\nEnvironment=ETCD_ADVERTISE_CLIENT_URLS=https://127.0.0.1:2379\nEnvironment=ETCD_LISTEN_CLIENT_URLS=https://127.0.0.1:2379\nEnvironment=ETCD_CERT_FILE=/etc/ssl/certs/etcd-cert.pem\nEnvironment=ETCD_KEY_FILE=/etc/ssl/certs/etcd-key.pem\nEnvironment=ETCD_TRUSTED_CA_FILE=/etc/ssl/certs/ca-locksmith-cert.pem\nEnvironment=ETCD_CLIENT_CERT_AUTH=true" 74 }] 75 }, 76 { 77 "name": "locksmithd.service", 78 "enable": true, 79 "dropins": [{ 80 "name": "environment.conf", 81 "contents": "[Unit]\nAfter=etcd-member.service\nRequires=etcd-member.service\n[Service]\nEnvironment=LOCKSMITHD_ETCD_CERTFILE=/etc/ssl/certs/locksmith-cert.pem\nEnvironment=LOCKSMITHD_ETCD_KEYFILE=/etc/ssl/certs/locksmith-key.pem\nEnvironment=LOCKSMITHD_ETCD_CAFILE=/etc/ssl/certs/ca-etcd-cert.pem\nEnvironment=LOCKSMITHD_ENDPOINT=https://localhost:2379\nEnvironment=LOCKSMITHD_REBOOT_WINDOW_START=00:00\nEnvironment=LOCKSMITHD_REBOOT_WINDOW_LENGTH=23h59m" 82 }] 83 } 84 ] 85 }, 86 "storage": { 87 "files": [ 88 { 89 "filesystem": "root", 90 "path": "/etc/coreos/update.conf", 91 "contents": { "source": "data:,REBOOT_STRATEGY=etcd-lock%0A" }, 92 "mode": 420 93 }, 94 { 95 "filesystem": "root", 96 "path": "/etc/ssl/etcd.cnf", 97 "contents": { "source": "data:,%5Breq%5D%0Adistinguished_name=req%0A%5Betcd_ca%5D%0AbasicConstraints=CA:true%0AkeyUsage=keyCertSign,cRLSign%0AsubjectKeyIdentifier=hash%0A%5Betcd_client%5D%0AbasicConstraints=CA:FALSE%0AextendedKeyUsage=clientAuth%0AkeyUsage=digitalSignature,keyEncipherment%0A%5Betcd_server%5D%0AbasicConstraints=CA:FALSE%0AextendedKeyUsage=serverAuth%0AkeyUsage=digitalSignature,keyEncipherment%0AsubjectAltName=@sans%0A%5Bsans%5D%0ADNS.1=localhost%0AIP.1=127.0.0.1%0A" }, 98 "mode": 420 99 } 100 ] 101 } 102 }`), 103 Flags: []register.Flag{register.RequiresInternetAccess}, // Networking required 104 Distros: []string{"cl"}, 105 }) 106 } 107 108 func locksmithReboot(c cluster.TestCluster) { 109 // The machine should be able to reboot without etcd in the default mode 110 m := c.Machines()[0] 111 112 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) 113 defer cancel() 114 115 output, err := c.SSH(m, "sudo systemctl stop sshd.socket && locksmithctl send-need-reboot") 116 if _, ok := err.(*ssh.ExitMissingError); ok { 117 err = nil // A terminated session is perfectly normal during reboot. 118 } else if err == io.EOF { 119 err = nil // Sometimes copying command output returns EOF here. 120 } 121 if err != nil { 122 c.Fatalf("failed to run \"locksmithctl send-need-reboot\": output: %q status: %q", output, err) 123 } 124 125 err = platform.CheckMachine(ctx, m) 126 if err != nil { 127 c.Fatalf("failed to check rebooted machine: %v", err) 128 } 129 130 } 131 132 func locksmithCluster(c cluster.TestCluster) { 133 machs := c.Machines() 134 135 // Wait for all etcd cluster nodes to be ready. 136 if err := etcd.GetClusterHealth(c, machs[0], len(machs)); err != nil { 137 c.Fatalf("cluster health: %v", err) 138 } 139 140 c.MustSSH(machs[0], "locksmithctl status") 141 142 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) 143 defer cancel() 144 wg := worker.NewWorkerGroup(ctx, len(machs)) 145 146 // reboot all the things 147 for _, m := range machs { 148 worker := func(ctx context.Context) error { 149 cmd := "sudo systemctl stop sshd.socket && sudo locksmithctl send-need-reboot" 150 output, err := c.SSH(m, cmd) 151 if _, ok := err.(*ssh.ExitMissingError); ok { 152 err = nil // A terminated session is perfectly normal during reboot. 153 } else if err == io.EOF { 154 err = nil // Sometimes copying command output returns EOF here. 155 } 156 if err != nil { 157 return fmt.Errorf("failed to run %q: output: %q status: %q", cmd, output, err) 158 } 159 160 return platform.CheckMachine(ctx, m) 161 } 162 163 if err := wg.Start(worker); err != nil { 164 c.Fatal(wg.WaitError(err)) 165 } 166 } 167 168 if err := wg.Wait(); err != nil { 169 c.Fatal(err) 170 } 171 } 172 173 func locksmithTLS(c cluster.TestCluster) { 174 m := c.Machines()[0] 175 lCmd := "sudo locksmithctl --endpoint https://localhost:2379 --etcd-cafile /etc/ssl/certs/ca-etcd-cert.pem --etcd-certfile /etc/ssl/certs/locksmith-cert.pem --etcd-keyfile /etc/ssl/certs/locksmith-key.pem " 176 177 // First verify etcd has a valid TLS connection ready 178 output, err := c.SSH(m, "openssl s_client -showcerts -verify_return_error -verify_ip 127.0.0.1 -verify_hostname localhost -connect localhost:2379 -cert /etc/ssl/certs/locksmith-cert.pem -key /etc/ssl/certs/locksmith-key.pem 0</dev/null 2>&1") 179 if err != nil || !bytes.Contains(output, []byte("Verify return code: 0")) { 180 c.Fatalf("openssl s_client: %q: %v", output, err) 181 } 182 183 // Also verify locksmithctl understands the TLS connection 184 c.MustSSH(m, lCmd+"status") 185 186 // Stop locksmithd 187 c.MustSSH(m, "sudo systemctl stop locksmithd.service") 188 189 // Set the lock while locksmithd isn't looking 190 c.MustSSH(m, lCmd+"lock") 191 192 // Verify it is locked 193 output, err = c.SSH(m, lCmd+"status") 194 if err != nil || !bytes.HasPrefix(output, []byte("Available: 0\nMax: 1")) { 195 c.Fatalf("locksmithctl status (locked): %q: %v", output, err) 196 } 197 198 // Start locksmithd 199 c.MustSSH(m, "sudo systemctl start locksmithd.service") 200 201 // Verify it is unlocked (after locksmithd wakes up again) 202 checker := func() error { 203 output, err := c.SSH(m, lCmd+"status") 204 if err != nil || !bytes.HasPrefix(output, []byte("Available: 1\nMax: 1")) { 205 return fmt.Errorf("locksmithctl status (unlocked): %q: %v", output, err) 206 } 207 return nil 208 } 209 if err := util.Retry(10, 12*time.Second, checker); err != nil { 210 c.Fatal(err) 211 } 212 }