Пример #1
0
 def verify_startup_script(self, args, host_info):
     cmd = "cat /etc/yb-boot-script-complete"
     rc, stdout, stderr = remote_exec_command(
         host_info['ssh_host'], host_info['ssh_port'],
         host_info['ssh_user'], args.private_key_file, cmd
     )
     if rc != 0:
         raise YBOpsRuntimeError(
             'Failed to read /etc/yb-boot-script-complete {}\nSTDOUT: {}\nSTDERR: {}\n'.format(
                 args.search_pattern, stdout, stderr))
     if len(stdout) > 0:
         if stdout[0].rstrip(os.linesep) != args.boot_script_token:
             raise YBOpsRuntimeError(
                 '/etc/yb-boot-script-complete on {} has incorrect token {}'.format(
                     args.search_pattern, stdout))
Пример #2
0
    def compare_root_certs(self, extra_vars, ssh_options):
        has_openssl = True
        root_cert_path = extra_vars["root_cert_path"]
        certs_node_dir = extra_vars["certs_node_dir"]
        yb_root_cert_path = os.path.join(certs_node_dir, self.ROOT_CERT_NAME)
        remote_shell = RemoteShell(ssh_options)
        try:
            remote_shell.run_command('which openssl')
        except YBOpsRuntimeError as e:
            # No openssl, just compare files.
            has_openssl = False

        # Check if files exist. If not, let it error out (since the root files should be there.)
        remote_shell.run_command('test -f {}'.format(root_cert_path))
        remote_shell.run_command('test -f {}'.format(yb_root_cert_path))

        # Compare the openssl hash if openssl is present.
        if has_openssl:
            md5_cmd = "openssl x509 -noout -modulus -in '{}' | openssl md5"
            curr_root = remote_shell.run_command(md5_cmd.format(yb_root_cert_path))
            new_root = remote_shell.run_command(md5_cmd.format(root_cert_path))
            if curr_root.stdout != new_root.stdout:
                raise YBOpsRuntimeError("Root certs are different.")
        # Openssl not present, just compare the files after removing whitespace.
        else:
            # If there is an error code, that means the files are different. Should fail.
            remote_shell.run_command('diff -Z {} {}'.format(root_cert_path, yb_root_cert_path))
Пример #3
0
    def wait_for_ssh_ports(self, private_ip, instance_name, ssh_ports):
        sock = None
        retry_count = 0

        while retry_count < self.SSH_RETRY_COUNT:
            logging.info("[app] Waiting for ssh: {}:{}".format(
                private_ip, str(ssh_ports)))
            time.sleep(self.SSH_WAIT_SECONDS)
            # Try connecting with the given ssh ports in succession.
            for ssh_port in ssh_ports:
                ssh_port = int(ssh_port)
                logging.info("[app] Attempting ssh: {}:{}".format(
                    private_ip, str(ssh_port)))
                try:
                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    sock.settimeout(self.SSH_TIMEOUT_SECONDS)
                    result = sock.connect_ex((private_ip, ssh_port))

                    if result == 0:
                        logging.info("[app] Connected to {}:{}".format(
                            private_ip, str(ssh_port)))
                        return ssh_port
                finally:
                    if sock:
                        sock.close()
            # Increment retry only after attempts on all ports fail.
            retry_count += 1
        else:
            logging.error(
                "[app] Start instance {} exceeded maxRetries!".format(
                    instance_name))
            raise YBOpsRuntimeError(
                "Cannot reach the instance {} after its start at ports {}".
                format(instance_name, str(ssh_ports)))
Пример #4
0
    def wait_for_ssh_port(self, private_ip, instance_name, ssh_port):
        try:
            sock = None
            retry_count = 0

            ssh_port = int(ssh_port)

            while retry_count < self.SSH_RETRY_COUNT:
                logging.info("[app] Waiting for ssh: {}:{}".format(
                    private_ip, str(ssh_port)))
                time.sleep(self.SSH_WAIT_SECONDS)
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                result = sock.connect_ex((private_ip, ssh_port))

                if result == 0:
                    break

                retry_count += 1
            else:
                logging.error(
                    "[app] Start instance {} exceeded maxRetries!".format(
                        instance_name))
                raise YBOpsRuntimeError(
                    "Cannot reach the instance {} after its start at port {}".
                    format(instance_name, ssh_port))
        finally:
            if sock:
                sock.close()
Пример #5
0
 def check_rm_result(rm_result):
     if rm_result.exited and rm_result.stderr.find(
             "No such file or directory") == -1:
         raise YBOpsRuntimeError(
             "Remote shell command 'rm' failed with "
             "return code '{}' and error '{}'".format(
                 rm_result.stderr.encode('utf-8'), rm_result.exited))
Пример #6
0
    def __verify_certs_hostname(self, node_crt_path, ssh_options):
        host = ssh_options["ssh_host"]
        remote_shell = RemoteShell(ssh_options)
        logging.info("Verifying Subject for certs {}".format(node_crt_path))

        # Get readable text version of cert
        cert_text = remote_shell.run_command(
            "openssl x509 -noout -text -in {}".format(node_crt_path))
        if "Certificate:" not in cert_text.stdout:
            raise YBOpsRuntimeError(
                "Unable to decode the node cert: {}.".format(node_crt_path))

        # Extract commonName and subjectAltName from the cert text output
        regex_out = re.findall(
            " Subject:.*CN=([\\S]*)$| (DNS|IP Address):([\\S]*?)(,|$)",
            cert_text.stdout, re.M)
        # Hostname will be present in group 0 for CN and in group 1 and 2 for SAN
        cn_entry = [x[0] for x in regex_out if x[0] != '']
        san_entry = {(x[1], x[2]) for x in regex_out if x[0] == ''}

        # Create cert object following the below dictionary format
        # https://docs.python.org/3/library/ssl.html#ssl.SSLSocket.getpeercert
        cert_cn = {
            'subject':
            ((('commonName', cn_entry[0] if len(cn_entry) > 0 else ''), ), )
        }
        cert_san = {'subjectAltName': tuple(san_entry)}

        # Check if the provided hostname matches with either CN or SAN
        cn_matched = False
        san_matched = False
        try:
            ssl.match_hostname(cert_cn, host)
            cn_matched = True
        except ssl.CertificateError:
            pass
        try:
            ssl.match_hostname(cert_san, host)
            san_matched = True
        except ssl.CertificateError:
            pass

        if not cn_matched and not san_matched:
            raise YBOpsRuntimeError(
                "'{}' does not match with any entry in CN or SAN of the node cert: {}, "
                "cert_cn: {}, cert_san: {}".format(host, node_crt_path,
                                                   cert_cn, cert_san))
Пример #7
0
    def verify_certs(self, root_crt_path, node_crt_path, ssh_options, verify_hostname=False):
        remote_shell = RemoteShell(ssh_options)
        # Verify that both cert are present in FS and have read rights
        root_file_verify = remote_shell.run_command_raw("test -r {}".format(root_crt_path))
        if root_file_verify.exited == 1:
            raise YBOpsRuntimeError(
                "Root cert: {} is absent or is not readable.".format(root_crt_path))
        node_file_verify = remote_shell.run_command_raw("test -r {}".format(node_crt_path))
        if node_file_verify.exited == 1:
            raise YBOpsRuntimeError(
                "Node cert: {} is absent or is not readable.".format(node_crt_path))
        try:
            remote_shell.run_command('which openssl')
        except YBOpsRuntimeError:
            logging.debug("Openssl not found, skipping certificate verification.")
            return

        # Verify if root and node certs are valid
        cert_verify = remote_shell.run_command_raw(
            ("openssl crl2pkcs7 -nocrl -certfile {} -certfile {} "
                "| openssl pkcs7 -print_certs -text -noout")
            .format(root_crt_path, node_crt_path))
        if cert_verify.exited == 1:
            raise YBOpsRuntimeError(
                "Provided certs ({}, {}) are not valid."
                .format(root_crt_path, node_crt_path))

        # Verify if the node cert is not expired
        validity_verify = remote_shell.run_command_raw(
            "openssl x509 -noout -checkend 86400 -in {}".format(node_crt_path))
        if validity_verify.exited == 1:
            raise YBOpsRuntimeError(
                "Node cert: {} is expired or will expire in one day.".format(node_crt_path))

        # Verify if node cert has valid signature
        signature_verify = remote_shell.run_command_raw(
            "openssl verify -CAfile {} {} | egrep error".format(root_crt_path, node_crt_path))
        if signature_verify.exited != 1:
            raise YBOpsRuntimeError(
                "Node cert: {} is not signed by the provided root cert: {}.".format(node_crt_path,
                                                                                    root_crt_path))

        if verify_hostname:
            self.__verify_certs_hostname(node_crt_path, ssh_options)
Пример #8
0
    def configure_secondary_interface(self, args, extra_vars, subnet_cidr):
        logging.info("[app] Configuring second NIC")
        subnet_network, subnet_netmask = subnet_cidr.split('/')
        # Copy and run script to configure routes
        scp_to_tmp(get_datafile_path('configure_nic.sh'),
                   extra_vars["ssh_host"], extra_vars["ssh_user"],
                   extra_vars["ssh_port"], args.private_key_file)
        cmd = ("sudo /tmp/configure_nic.sh "
               "--subnet_network {} --subnet_netmask {} --cloud {}").format(
                   subnet_network, subnet_netmask, self.name)
        rc, stdout, stderr = remote_exec_command(extra_vars["ssh_host"],
                                                 extra_vars["ssh_port"],
                                                 extra_vars["ssh_user"],
                                                 args.private_key_file, cmd)
        if rc:
            raise YBOpsRuntimeError(
                "Could not configure second nic {} {}".format(stdout, stderr))
        # Since this is on start, wait for ssh on default port
        # Reboot instance
        remote_exec_command(extra_vars["ssh_host"], extra_vars["ssh_port"],
                            extra_vars["ssh_user"], args.private_key_file,
                            'sudo reboot')
        self.wait_for_ssh_port(extra_vars["ssh_host"], args.search_pattern,
                               extra_vars["ssh_port"])
        # Make sure we can ssh into the node after the reboot as well.
        if wait_for_ssh(extra_vars["ssh_host"],
                        extra_vars["ssh_port"],
                        extra_vars["ssh_user"],
                        args.private_key_file,
                        num_retries=120):
            pass
        else:
            raise YBOpsRuntimeError("Could not ssh into node {}".format(
                extra_vars["ssh_host"]))

        # Verify that the command ran successfully:
        rc, stdout, stderr = remote_exec_command(extra_vars["ssh_host"],
                                                 extra_vars["ssh_port"],
                                                 extra_vars["ssh_user"],
                                                 args.private_key_file,
                                                 'ls /tmp/dhclient-script-*')
        if rc:
            raise YBOpsRuntimeError("Second nic not configured at start up")
Пример #9
0
    def run_control_script(self, process, command, args, extra_vars, host_info):
        updated_vars = {
            "process": process,
            "command": command
        }
        updated_vars.update(extra_vars)
        updated_vars.update(get_ssh_host_port(host_info, args.custom_ssh_port))
        remote_shell = RemoteShell(updated_vars)
        if args.num_volumes:
            volume_cnt = remote_shell.run_command(
                "df | awk '{{print $6}}' | egrep '^{}[0-9]+' | wc -l".format(
                    AbstractCloud.MOUNT_PATH_PREFIX
                )
            )
            if int(volume_cnt.stdout) < int(args.num_volumes):
                raise YBOpsRuntimeError(
                    "Not all data volumes attached: needed {} found {}".format(
                        args.num_volumes, volume_cnt.stdout
                    )
                )

        if process == "thirdparty" or process == "platform-services":
            self.setup_ansible(args).run("yb-server-ctl.yml", updated_vars, host_info)
            return

        if args.systemd_services:
            if command == "start":
                remote_shell.run_command(
                    "sudo systemctl enable yb-{}".format(process)
                )
            remote_shell.run_command(
                "sudo systemctl {} yb-{}".format(command, process)
            )
            if command == "stop":
                remote_shell.run_command(
                    "sudo systemctl disable yb-{}".format(process)
                )
            return

        if os.environ.get("YB_USE_FABRIC", False):
            file_path = os.path.join(YB_HOME_DIR, "bin/yb-server-ctl.sh")
            remote_shell.run_command(
                "{} {} {}".format(file_path, process, command)
            )
        else:
            self.setup_ansible(args).run("yb-server-ctl.yml", updated_vars, host_info)
Пример #10
0
    def execute_boot_script(self, args, extra_vars):
        dest_path = os.path.join("/tmp", os.path.basename(args.boot_script))

        # Make it executable, in case it isn't one.
        st = os.stat(args.boot_script)
        os.chmod(args.boot_script, st.st_mode | stat.S_IEXEC)

        scp_to_tmp(
            args.boot_script, extra_vars["ssh_host"],
            extra_vars["ssh_user"], extra_vars["ssh_port"], args.private_key_file)

        cmd = "sudo {}".format(dest_path)
        rc, stdout, stderr = remote_exec_command(
            extra_vars["ssh_host"], extra_vars["ssh_port"],
            extra_vars["ssh_user"], args.private_key_file, cmd)
        if rc:
            raise YBOpsRuntimeError(
                "[app] Could not run bootscript {} {}".format(stdout, stderr))
Пример #11
0
 def validate_credentials(self):
     potential_env_vars = self.metadata.get('credential_vars')
     missing_var = None
     if potential_env_vars:
         for var in potential_env_vars:
             if var not in os.environ:
                 missing_var = var
                 break
     # If we found cloud credentials, then we're good to go and will explicitly use those!
     if missing_var is None:
         logging.info("Found {} cloud credentials in env.".format(self.name))
         return
     # If no cloud credentials, see if we have credentials on the machine itself.
     if self.has_machine_credentials():
         logging.info("Found {} cloud credentials in machine metadata.".format(self.name))
         return
     raise YBOpsRuntimeError(
         "Cloud {} missing {} and has no machine credentials to default to.".format(
             self.name, missing_var))
Пример #12
0
    def _wait_for_ssh_port(self, private_ip, instance_name, ssh_port):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            retry_count = 0

            while retry_count < self.SSH_RETRY_COUNT:
                time.sleep(self.SSH_WAIT_SECONDS)
                retry_count = retry_count + 1
                result = sock.connect_ex((private_ip, ssh_port))
                if result == 0:
                    break
            else:
                logging.error("Start instance {} exceeded maxRetries!".format(
                    instance_name))
                raise YBOpsRuntimeError(
                    "Cannot reach the instance {} after its start at port {}".
                    format(instance_name, ssh_port))
        finally:
            sock.close()
Пример #13
0
    def generate_client_cert(self, extra_vars, ssh_options):
        node_ip = ssh_options["ssh_host"]
        root_cert_path = extra_vars["rootCA_cert"]
        root_key_path = extra_vars["rootCA_key"]
        certs_node_dir = extra_vars["certs_node_dir"]
        with open(root_cert_path, 'r') as cert_in:
            certlines = cert_in.read()
        root_cert = x509.load_pem_x509_certificate(certlines, default_backend())
        with open(root_key_path, 'r') as key_in:
            keylines = key_in.read()
        root_key = load_pem_private_key(keylines, None, default_backend())
        private_key = rsa.generate_private_key(
            public_exponent=self.PUBLIC_EXPONENT,
            key_size=self.KEY_SIZE,
            backend=default_backend()
        )
        public_key = private_key.public_key()
        builder = x509.CertificateBuilder()
        builder = builder.subject_name(x509.Name([
            x509.NameAttribute(NameOID.COMMON_NAME, six.text_type(node_ip)),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, six.text_type(extra_vars["org_name"]))
        ]))
        builder = builder.issuer_name(root_cert.subject)
        builder = builder.not_valid_before(datetime.datetime.utcnow())
        builder = builder.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(
            extra_vars["cert_valid_duration"]))
        builder = builder.serial_number(x509.random_serial_number())
        builder = builder.public_key(public_key)
        builder = builder.add_extension(x509.BasicConstraints(ca=False, path_length=None),
                                        critical=True)
        certificate = builder.sign(private_key=root_key, algorithm=hashes.SHA256(),
                                   backend=default_backend())
        # Write private key to file
        pem = private_key.private_bytes(
            encoding=Encoding.PEM,
            format=PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=NoEncryption()
        )
        key_file = 'node.{}.key'.format(node_ip)
        cert_file = 'node.{}.crt'.format(node_ip)
        common_path = '{}/{}'.format(self.CERTS_TEMP_DIR, node_ip)
        try:
            os.makedirs(common_path)
        except OSError as exc:  # Guard against race condition
            if exc.errno != errno.EEXIST:
                raise YBOpsRuntimeError(common_path + " could not be  be created")
        with open(os.path.join(common_path, key_file), 'wb') as pem_out:
            pem_out.write(pem)
        # Write certificate to file
        pem = certificate.public_bytes(encoding=Encoding.PEM)
        with open(os.path.join(common_path, cert_file), 'wb') as pem_out:
            pem_out.write(pem)
        # Copy files over to node
        remote_shell = RemoteShell(ssh_options)
        remote_shell.run_command('mkdir -p ' + certs_node_dir)
        # Give write permission in case file exists. If the command fails, ignore.
        remote_shell.run_command('chmod -f 666 {}/* || true'.format(certs_node_dir))
        remote_shell.put_file(os.path.join(common_path, key_file),
                              os.path.join(certs_node_dir, key_file))
        remote_shell.put_file(os.path.join(common_path, cert_file),
                              os.path.join(certs_node_dir, cert_file))
        remote_shell.put_file(root_cert_path, os.path.join(certs_node_dir, self.ROOT_CERT_NAME))
        remote_shell.run_command('chmod 400 {}/*'.format(certs_node_dir))

        if "client_cert" in extra_vars:
            client_cert_path = extra_vars["client_cert"]
            client_key_path = extra_vars["client_key"]
            remote_shell.run_command('mkdir -p ' + self.YSQLSH_CERT_DIR)
            # Give write permission in case file exists. If the command fails, ignore.
            remote_shell.run_command('chmod -f 666 {}/* || true'.format(self.YSQLSH_CERT_DIR))
            remote_shell.put_file(root_cert_path, os.path.join(self.YSQLSH_CERT_DIR,
                                                               self.CLIENT_ROOT_NAME))
            remote_shell.put_file(client_cert_path, os.path.join(self.YSQLSH_CERT_DIR,
                                                                 self.CLIENT_CERT_NAME))
            remote_shell.put_file(client_key_path, os.path.join(self.YSQLSH_CERT_DIR,
                                                                self.CLIENT_KEY_NAME))
            remote_shell.run_command('chmod 400 {}/*'.format(self.YSQLSH_CERT_DIR))

        try:
            shutil.rmtree(common_path)
        except OSError as e:
            raise YBOpsRuntimeError("Error: %s - %s." % (e.filename, e.strerror))
Пример #14
0
    def copy_server_certs(self, ssh_options, root_cert_path, server_cert_path,
                          server_key_path, certs_location, certs_dir,
                          rotate_certs, skip_cert_validation):
        remote_shell = RemoteShell(ssh_options)
        node_ip = ssh_options["ssh_host"]
        cert_file = 'node.{}.crt'.format(node_ip)
        key_file = 'node.{}.key'.format(node_ip)
        yb_root_cert_path = os.path.join(certs_dir, self.ROOT_CERT_NAME)
        yb_server_cert_path = os.path.join(certs_dir, cert_file)
        yb_server_key_path = os.path.join(certs_dir, key_file)

        copy_root = True
        if rotate_certs:
            root_cert_command = remote_shell.run_command_raw(
                "cat '{}'".format(yb_root_cert_path))
            # In case of tls toggle root cert might not be present
            if not root_cert_command.exited:
                root_cert = root_cert_command.stdout
                root_cert_new = None
                if certs_location == self.CERT_LOCATION_NODE:
                    root_cert_new = remote_shell.run_command(
                        "cat '{}'".format(root_cert_path)).stdout
                if certs_location == self.CERT_LOCATION_PLATFORM:
                    with open(root_cert_path) as file:
                        root_cert_new = file.read()
                if root_cert is not None and root_cert_new is not None:
                    compare_result = self.compare_certs(
                        root_cert_new, root_cert)
                    if compare_result == 0 or compare_result == 1:
                        # Don't copy root certs if the new root cert is
                        # same or subset of the existing root cert
                        copy_root = False
                else:
                    raise YBOpsRuntimeError(
                        "Unable to fetch the certificate {}".format(
                            root_cert_path))

        logging.info("Moving server certs located at {}, {}, {}.".format(
            root_cert_path, server_cert_path, server_key_path))

        remote_shell.run_command('mkdir -p ' + certs_dir)
        # Give write permissions. If the command fails, ignore.
        remote_shell.run_command('chmod -f 666 {}/* || true'.format(certs_dir))

        if certs_location == self.CERT_LOCATION_NODE:
            if skip_cert_validation == 'ALL':
                logging.info(
                    "Skipping all validations for certs for node {}".format(
                        node_ip))
            else:
                verify_hostname = True
                if skip_cert_validation == 'HOSTNAME':
                    logging.info(
                        "Skipping host name validation for certs for node {}".
                        format(node_ip))
                    verify_hostname = False
                self.verify_certs(root_cert_path, server_cert_path,
                                  ssh_options, verify_hostname)
            if copy_root:
                remote_shell.run_command("cp '{}' '{}'".format(
                    root_cert_path, yb_root_cert_path))
            remote_shell.run_command("cp '{}' '{}'".format(
                server_cert_path, yb_server_cert_path))
            remote_shell.run_command("cp '{}' '{}'".format(
                server_key_path, yb_server_key_path))
        if certs_location == self.CERT_LOCATION_PLATFORM:
            if copy_root:
                remote_shell.put_file(root_cert_path, yb_root_cert_path)
            remote_shell.put_file(server_cert_path, yb_server_cert_path)
            remote_shell.put_file(server_key_path, yb_server_key_path)

        # Reset the write permission as a sanity check.
        remote_shell.run_command('chmod 400 {}/*'.format(certs_dir))