def test_generate_server_certificate_new_explicit_name(self):
        """Test that server cert generating function creates new certificate.

        This test uses specific name for the certificate, so the function
        should not default to using name "server" for generated files
        """
        common_name = "host1.local"
        ip_addr = "10.0.0.1"
        server = "host1"
        sans_arg = "--subject-alt-name=IP:{}".format(ip_addr)

        # simulate that certificate data changed and that it does not already
        # exist
        easyrsa.data_changed.return_value = True
        self.is_file_mock.return_value = False

        # command to generate new server certificate
        gen_cert_cmd = split(
            "./easyrsa --batch --req-cn={0} {1} build-server-full {2} "
            "nopass 2>&1".format(common_name, sans_arg, server)
        )

        # expect usage of explicit host name for certificate files
        cert_file = "pki/issued/{}.crt".format(server)
        key_file = "pki/private/{}.key".format(server)

        expected_file_opens = [
            call(cert_file, "r"),
            call(key_file, "r"),
        ]
        # mock opening of certificate and key files
        file_mock = mock_open()

        cert_file_mock = mock_open(read_data=self.server_cert)
        cert_file_handle = cert_file_mock()

        key_file_mock = mock_open(read_data=self.server_key)
        key_file_handle = key_file_mock()

        file_mock.side_effect = (cert_file_handle, key_file_handle)

        # execute function
        with patch("builtins.open", file_mock):
            new_cert, new_key = easyrsa.create_server_certificate(
                common_name, [ip_addr], server
            )

        # assert that new server certificate was generated
        self.chdir_mock.assert_called_once_with(easyrsa.easyrsa_directory)
        self.check_call_mock.assert_called_once_with(gen_cert_cmd)
        file_mock.assert_has_calls(expected_file_opens)
        self.assertEqual(new_cert, self.server_cert)
        self.assertEqual(new_key, self.server_key)
Exemple #2
0
def restore():
    """
    Implementation of easyrsa 'restore' action

    Backup restoration process can be summarized as following:

        * Selected backup is scanned and verified
        * Contents of the backup are unpacked into <cahrm_dir>/EasyRSA/pki
        * Data that are stored in the local database are updated
        * All units that have relation with this easyrsa unit will be notified
          about the certificate changes.
    """
    pki_dir = os.path.join(easyrsa_directory, "pki")
    backup_name = function_get("name")

    if backup_name is None:
        raise RuntimeError("Parameter 'name' is required.")

    log("Restoring pki from backup file {}".format(backup_name), hookenv.INFO)

    backup_path = os.path.join(PKI_BACKUP, backup_name)

    if not os.path.isfile(backup_path):
        log("Backup file '{}' does not exists.".format(backup_path), hookenv.ERROR)
        raise RuntimeError(
            "Backup with name '{}' does not exist. Use action "
            "'list-backups' to list all available "
            "backups".format(backup_name)
        )

    with tarfile.open(backup_path, "r:gz") as pki_tar:
        _verify_backup(pki_tar)
        _replace_pki(pki_tar, pki_dir)

    cert_dir = os.path.join(pki_dir, "issued")
    key_dir = os.path.join(pki_dir, "private")

    # Update CA and global client data stored in the local leader's database
    # NOTE(mkalcok): Easyrsa does not really support HA mode, so it's usually
    #                run as a single unit/model
    _update_leadership_data(pki_dir, cert_dir, key_dir)

    ca_cert = leader_get("certificate_authority")
    tls = endpoint_from_name("client")
    log("Sending CA certificate to all related units", hookenv.INFO)
    tls.set_ca(ca_cert)
    log("Sending global client certificate and key to all related units", hookenv.INFO)
    tls.set_client_cert(leader_get("client_certificate"), leader_get("client_key"))
    for client in tls.all_requests:
        try:
            cert_file = os.path.join(cert_dir, "{}.crt".format(client.common_name))
            key_file = os.path.join(key_dir, "{}.key".format(client.common_name))
            with open(cert_file, "r") as file:
                cert = file.read()
            with open(key_file, "r") as file:
                key = file.read()
            log(
                "Sending certificate for '{}' to unit"
                "'{}'".format(client.common_name, client.unit_name),
                hookenv.INFO,
            )
            log(cert, hookenv.DEBUG)
            client.set_cert(cert, key)

        except FileNotFoundError:
            log(
                "Certificate for '{}' not found in backup. " "Generating new one.",
                hookenv.INFO,
            )
            if client.cert_type == "client":
                cert, key = create_client_certificate(client.common_name)
            elif client.cert_type == "server":
                cert, key = create_server_certificate(
                    client.common_name, client.sans, client.common_name
                )
            else:
                # This use case should not really happen as easyrsa charm
                # does not support Application type certificates
                raise RuntimeError(
                    "Unrecognized certificate request type "
                    '"{}".'.format(client.cert_type)
                )
            log(
                "Sending certificate for '{}' to unit"
                "'{}'".format(client.common_name, client.unit_name),
                hookenv.INFO,
            )
            log(cert, hookenv.DEBUG)
            client.set_cert(cert, key)

    hookenv._run_atexit()
    def test_generate_server_certificate_with_revoke(self, remove_file_mock):
        """Test re-generating server certificate for host.

        This function is also expected to revoke old certificate.
        """
        common_name = "host1.local"
        ip_addr = "10.0.0.1"
        server = "host1"
        sans_arg = "--subject-alt-name=IP:{}".format(ip_addr)

        # simulate that certificate data changed and that older certificate
        # for the same host already exists
        easyrsa.data_changed.return_value = True
        self.is_file_mock.return_value = True

        # expected easyrsa commands
        revoke_cmd = split("./easyrsa --batch revoke {}".format(server))
        gen_cert_cmd = split(
            "./easyrsa --batch --req-cn={0} {1} build-server-full {2} "
            "nopass 2>&1".format(common_name, sans_arg, server)
        )
        expected_easyrsa_calls = [
            call(revoke_cmd),
            call(gen_cert_cmd),
        ]

        # expect usage of explicit host name for certificate files
        cert_file = "pki/issued/{}.crt".format(server)
        key_file = "pki/private/{}.key".format(server)
        req_file = "pki/reqs/{0}.req".format(server)

        expected_file_opens = [
            call(cert_file, "r"),
            call(key_file, "r"),
        ]

        expected_file_remove_calls = [
            call(cert_file),
            call(key_file),
            call(req_file),
        ]

        # mock opening of certificate and key files
        file_mock = mock_open()

        cert_file_mock = mock_open(read_data=self.server_cert)
        cert_file_handle = cert_file_mock()

        key_file_mock = mock_open(read_data=self.server_key)
        key_file_handle = key_file_mock()

        file_mock.side_effect = (cert_file_handle, key_file_handle)

        # execute function
        with patch("builtins.open", file_mock):
            new_cert, new_key = easyrsa.create_server_certificate(
                common_name, [ip_addr], server
            )

        # assert that old certificate is revoked and its files are removed
        # assert that new server certificate is generated
        self.chdir_mock.assert_called_once_with(easyrsa.easyrsa_directory)
        self.check_call_mock.assert_has_calls(expected_easyrsa_calls)
        remove_file_mock.assert_has_calls(expected_file_remove_calls)
        file_mock.assert_has_calls(expected_file_opens)
        self.assertEqual(new_cert, self.server_cert)
        self.assertEqual(new_key, self.server_key)