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(&register.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(&register.Test{
    52  		Name:        "coreos.locksmith.reboot",
    53  		Run:         locksmithReboot,
    54  		ClusterSize: 1,
    55  		Distros:     []string{"cl"},
    56  	})
    57  	register.Register(&register.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  }