Пример #1
0
    def set_up_named(self, overwrite_config=True):
        """Setup an environment to run 'nginx'.

        - Creates the default configuration for 'nginx'.
        - Copies the 'nginx' executable inside homedir.  AppArmor won't
          let us run the installed version the way we want.
        """
        # Write main Nginx config file.
        if should_write(self.conf_file, overwrite_config):
            nginx_conf = self.NGINX_CONF_TEMPLATE.substitute(
                homedir=self.homedir,
                access_log_file=self.access_log_file,
                error_log_file=self.error_log_file,
                pid_file=self.pid_file,
            )
            atomic_write((GENERATED_HEADER + nginx_conf).encode("ascii"),
                         self.conf_file)

        # Copy named executable to home dir.  This is done to avoid
        # the limitations imposed by apparmor if the executable
        # is in /usr/sbin/nginx.
        if should_write(self.nginx_file, overwrite_config):
            nginx_path = self.NGINX_PATH
            assert os.path.exists(nginx_path), (
                "'%s' executable not found.  Install the package "
                "'nginx-core' or define an environment variable named "
                "NGINX_PATH with the path where the 'nginx-core' "
                "executable can be found." % nginx_path)
            copy(nginx_path, self.nginx_file)
Пример #2
0
 def write_privatekey(self, key):
     """Write the private key to disk."""
     logger.debug("Writing key: %s" % self.key_file)
     atomic_write(
         OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key),
         self.key_file,
     )
Пример #3
0
 def write_cert(self, cert):
     """Write the certificate to disk."""
     logger.debug("Writing certificate: %s" % self.pem_file)
     atomic_write(
         OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert),
         self.pem_file,
     )
Пример #4
0
 def test_atomic_write_does_not_leak_temp_file_when_not_overwriting(self):
     # If the file is not written because it already exists and
     # overwriting was disabled, atomic_write does not leak its
     # temporary file.
     filename = self.make_file()
     atomic_write(factory.make_bytes(), filename, overwrite=False)
     self.assertEqual([os.path.basename(filename)],
                      os.listdir(os.path.dirname(filename)))
Пример #5
0
 def test_atomic_write_sets_permissions(self):
     atomic_file = self.make_file()
     # Pick an unusual mode that is also likely to fall outside our
     # umask.  We want this mode set, not treated as advice that may
     # be tightened up by umask later.
     mode = 0o323
     atomic_write(factory.make_bytes(), atomic_file, mode=mode)
     self.assertEqual(mode, stat.S_IMODE(os.stat(atomic_file).st_mode))
Пример #6
0
def write_snapshot_metadata(snapshot, meta_file_content):
    """Write "maas.meta" file.

    :param meta_file_content: A Unicode string (`str`) containing JSON using
        only ASCII characters.
    """
    meta_file = os.path.join(snapshot, "maas.meta")
    atomic_write(meta_file_content.encode("ascii"), meta_file, mode=0o644)
Пример #7
0
 def test_atomic_write_does_not_leak_temp_file_on_failure(self):
     # If the overwrite fails, atomic_write does not leak its
     # temporary file.
     self.patch(fs_module, "rename", Mock(side_effect=OSError()))
     filename = self.make_file()
     with ExpectedException(OSError):
         atomic_write(factory.make_bytes(), filename)
     self.assertEqual([os.path.basename(filename)],
                      os.listdir(os.path.dirname(filename)))
Пример #8
0
 def test_result(self):
     proc_path = self.make_dir()
     pid = random.randint(1, 1000)
     pid_path = os.path.join(proc_path, str(pid))
     os.mkdir(pid_path)
     atomic_write(self.cgroup.encode("ascii"),
                  os.path.join(pid_path, "cgroup"))
     self.assertEqual(self.result,
                      is_pid_in_container(pid, proc_path=proc_path))
Пример #9
0
 def make_process(self, proc_path, pid, in_container=False, command=None):
     cgroup = NOT_IN_CONTAINER
     if in_container:
         cgroup = random.choice([IN_DOCKER_CONTAINER, IN_LXC_CONTAINER])
     pid_path = os.path.join(proc_path, str(pid))
     os.mkdir(pid_path)
     atomic_write(cgroup.encode("ascii"), os.path.join(pid_path, "cgroup"))
     if command is not None:
         atomic_write(command.encode("ascii"),
                      os.path.join(pid_path, "comm"))
Пример #10
0
    def test_atomic_wrtie_does_not_leak_temp_file_set_permission_failure(self):
        # Make os.chown() raise PermissionError and verify the temporary file
        # was removed.
        self.patch(fs_module, "chown", Mock(side_effect=PermissionError()))
        filename = self.make_file()
        with ExpectedException(PermissionError):
            atomic_write(factory.make_bytes(), filename)

        pattern = os.path.join(os.path.dirname(filename),
                               ".%s.*" % os.path.basename(filename))
        matched_files = glob.glob(pattern)
        self.assertEqual(matched_files, [])
Пример #11
0
def set_up_options_conf(overwrite=True, **kwargs):
    """Write out the named.conf.options.inside.maas file.

    This file should be included by the top-level named.conf.options
    inside its 'options' block.  MAAS cannot write the options file itself,
    so relies on either the DNSFixture in the test suite, or the packaging.
    Both should set that file up appropriately to include our file.
    """
    template_path = get_dns_template_path(
        "named.conf.options.inside.maas.template")
    template = tempita.Template.from_filename(template_path, encoding="UTF-8")

    # Make sure "upstream_dns" is set at least to None. It's a special piece
    # of config and we don't want to require that every call site has to
    # specify it. If it's not set, the substitution will fail with the default
    # template that uses this value.
    kwargs.setdefault("upstream_dns")
    kwargs.setdefault("dnssec_validation", "auto")

    # Parse the options file and make sure MAAS doesn't define any options
    # that the user has already customized.
    allow_user_override_options = [
        "allow-query",
        "allow-recursion",
        "allow-query-cache",
    ]

    try:
        parsed_options = read_isc_file(
            compose_bind_config_path(NAMED_CONF_OPTIONS))
    except IOError:
        parsed_options = {}

    options = parsed_options.get('options', {})
    for option in allow_user_override_options:
        kwargs['upstream_' + option.replace('-', '_')] = option in options

    try:
        rendered = template.substitute(kwargs)
    except NameError as error:
        raise DNSConfigFail(*error.args)
    else:
        # The rendered configuration is Unicode text but should contain only
        # ASCII characters. Non-ASCII records should have been treated using
        # the rules for IDNA (Internationalized Domain Names in Applications).
        rendered = rendered.encode("ascii")

    target_path = compose_config_path(MAAS_NAMED_CONF_OPTIONS_INSIDE_NAME)
    atomic_write(rendered, target_path, overwrite=overwrite, mode=0o644)
Пример #12
0
    def set_up_named(self, overwrite_config=True):
        """Setup an environment to run 'named'.

        - Creates the default configuration for 'named' and sets up rndc.
        - Copies the 'named' executable inside homedir.  AppArmor won't
          let us run the installed version the way we want.
        """
        # Generate rndc configuration (rndc config and named snippet).
        # Disable remote administration for init scripts by suppressing the
        # "controls" statement.
        rndcconf, namedrndcconf = generate_rndc(
            port=self.rndc_port,
            key_name="dnsfixture-rndc-key",
            include_default_controls=False,
        )
        # Write main BIND config file.
        if should_write(self.conf_file, overwrite_config):
            named_conf = self.NAMED_CONF_TEMPLATE.substitute(
                homedir=self.homedir,
                port=self.port,
                log_file=self.log_file,
                include_in_options=self.include_in_options,
                extra=namedrndcconf,
            )
            atomic_write(
                (GENERATED_HEADER + named_conf).encode("ascii"), self.conf_file
            )
        # Write rndc config file.
        if should_write(self.rndcconf_file, overwrite_config):
            atomic_write(
                (GENERATED_HEADER + rndcconf).encode("ascii"),
                self.rndcconf_file,
            )

        # Copy named executable to home dir.  This is done to avoid
        # the limitations imposed by apparmor if the executable
        # is in /usr/sbin/named.
        # named's apparmor profile prevents loading of zone and
        # configuration files from outside of a restricted set,
        # none of which an ordinary user has write access to.
        if should_write(self.named_file, overwrite_config):
            named_path = self.NAMED_PATH
            assert os.path.exists(named_path), (
                "'%s' executable not found.  Install the package "
                "'bind9' or define an environment variable named "
                "NAMED_PATH with the path where the 'named' "
                "executable can be found." % named_path
            )
            copy(named_path, self.named_file)
Пример #13
0
 def save(self):
     """Save the configuration."""
     try:
         stat = os.stat(self.path)
     except OSError:
         mode = default_file_mode
     else:
         mode = stat.st_mode
     # Write, retaining the file's mode.
     atomic_write(yaml.safe_dump(self.config,
                                 default_flow_style=False,
                                 encoding="utf-8"),
                  self.path,
                  mode=mode)
     self.dirty = False
Пример #14
0
    def test_atomic_write_sets_permissions_before_moving_into_place(self):

        recorded_modes = []

        def record_mode(source, dest):
            """Stub for os.rename: get source file's access mode."""
            recorded_modes.append(os.stat(source).st_mode)

        self.patch(fs_module, "rename", Mock(side_effect=record_mode))
        playground = self.make_dir()
        atomic_file = os.path.join(playground, factory.make_name("atomic"))
        mode = 0o323
        atomic_write(factory.make_bytes(), atomic_file, mode=mode)
        [recorded_mode] = recorded_modes
        self.assertEqual(mode, stat.S_IMODE(recorded_mode))
Пример #15
0
    def export_p12(self, key, cert, passphrase):
        """Create a pcks12 password protected container for the generated
        certificates.

        :param key: Key file to add to PFX file
        :param cert: Certificate file to add to PFX file
        :param passphrase: export passphrase for PFX file
        """
        p12 = OpenSSL.crypto.PKCS12()
        p12.set_certificate(cert)
        p12.set_privatekey(key)
        atomic_write(
            p12.export(passphrase=bytes(passphrase.encode("utf-8"))),
            self.pfx_file,
        )
Пример #16
0
def set_maas_id(system_id):
    """Set the system_id for this rack/region permanently for MAAS."""
    global _maas_id
    system_id = _normalise_maas_id(system_id)
    with _maas_id_lock:
        maas_id_path = get_maas_data_path("maas_id")
        if system_id is None:
            try:
                atomic_delete(maas_id_path)
            except FileNotFoundError:
                _maas_id = None  # Job done already.
            else:
                _maas_id = None
        else:
            atomic_write(system_id.encode("ascii"), maas_id_path)
            _maas_id = system_id
Пример #17
0
    def test_atomic_write_preserves_ownership_before_moving_into_place(self):
        atomic_file = self.make_file('atomic')

        self.patch(fs_module, 'isfile').return_value = True
        self.patch(fs_module, 'chown')
        self.patch(fs_module, 'rename')
        self.patch(fs_module, 'stat')

        ret_stat = fs_module.stat.return_value
        ret_stat.st_uid = sentinel.uid
        ret_stat.st_gid = sentinel.gid
        ret_stat.st_mode = stat.S_IFREG

        atomic_write(factory.make_bytes(), atomic_file)

        self.assertThat(fs_module.stat, MockCalledOnceWith(atomic_file))
        self.assertThat(fs_module.chown,
                        MockCalledOnceWith(ANY, sentinel.uid, sentinel.gid))
Пример #18
0
    def write_config():
        allowed_subnets = Subnet.objects.filter(allow_proxy=True)
        cidrs = [subnet.cidr for subnet in allowed_subnets]
        config = Config.objects.get_configs([
            'http_proxy', 'maas_proxy_port', 'use_peer_proxy',
            'prefer_v4_proxy', 'enable_http_proxy'
        ])

        http_proxy = config["http_proxy"]
        upstream_proxy_enabled = (config["use_peer_proxy"] and http_proxy)
        context = {
            'allowed': allowed_subnets,
            'modified': str(datetime.date.today()),
            'fqdn': socket.getfqdn(),
            'cidrs': cidrs,
            'running_in_snap': snappy.running_in_snap(),
            'snap_path': snappy.get_snap_path(),
            'snap_data_path': snappy.get_snap_data_path(),
            'snap_common_path': snappy.get_snap_common_path(),
            'upstream_peer_proxy': upstream_proxy_enabled,
            'dns_v4_first': config['prefer_v4_proxy'],
            'maas_proxy_port': config['maas_proxy_port'],
        }

        proxy_enabled = config["enable_http_proxy"]
        if proxy_enabled and upstream_proxy_enabled:
            http_proxy_hostname = urlparse(http_proxy).hostname
            http_proxy_port = urlparse(http_proxy).port
            context.update({
                'upstream_proxy_address': http_proxy_hostname,
                'upstream_proxy_port': http_proxy_port,
            })

        template_path = locate_template('proxy', MAAS_PROXY_CONF_TEMPLATE)
        template = tempita.Template.from_filename(template_path,
                                                  encoding="UTF-8")
        try:
            content = template.substitute(context)
        except NameError as error:
            raise ProxyConfigFail(*error.args)
        # Squid prefers ascii.
        content = content.encode("ascii")
        target_path = get_proxy_config_path()
        atomic_write(content, target_path, overwrite=True, mode=0o644)
Пример #19
0
def write_config(
    allowed_cidrs,
    peer_proxies=None,
    prefer_v4_proxy=False,
    maas_proxy_port=8000,
):
    """Write the proxy configuration."""
    if peer_proxies is None:
        peer_proxies = []

    snap_paths = snap.SnapPaths.from_environ()
    context = {
        "modified": str(datetime.date.today()),
        "fqdn": socket.getfqdn(),
        "cidrs": allowed_cidrs,
        "running_in_snap": snap.running_in_snap(),
        "snap_path": snap_paths.snap,
        "snap_data_path": snap_paths.data,
        "snap_common_path": snap_paths.common,
        "dns_v4_first": prefer_v4_proxy,
        "maas_proxy_port": maas_proxy_port,
    }

    formatted_peers = []
    for peer in peer_proxies:
        formatted_peers.append({
            "address": urlparse(peer).hostname,
            "port": urlparse(peer).port
        })
    context["peers"] = formatted_peers

    template_path = locate_template("proxy", MAAS_PROXY_CONF_TEMPLATE)
    template = tempita.Template.from_filename(template_path, encoding="UTF-8")
    try:
        content = template.substitute(context)
    except NameError as error:
        raise ProxyConfigFail(*error.args)

    # Squid prefers ascii.
    content = content.encode("ascii")
    target_path = get_proxy_config_path()
    atomic_write(content, target_path, overwrite=True, mode=0o644)
Пример #20
0
 def test_returns_interface_name_with_address(self):
     proc_path = self.make_dir()
     leases_path = self.make_dir()
     running_pids = set(random.randint(2, 999) for _ in range(3))
     self.patch(
         dhclient_module, "get_running_pids_with_command"
     ).return_value = running_pids
     interfaces = {}
     for pid in running_pids:
         interface_name = factory.make_name("eth")
         address = factory.make_ipv4_address()
         interfaces[interface_name] = address
         lease_path = os.path.join(leases_path, "%s.lease" % interface_name)
         lease_data = (
             dedent(
                 """\
             lease {
               interface "%s";
               fixed-address %s;
             }
             """
             )
             % (interface_name, address)
         )
         atomic_write(lease_data.encode("ascii"), lease_path)
         cmdline_path = os.path.join(proc_path, str(pid), "cmdline")
         cmdline = [
             "/sbin/dhclient",
             "-d",
             "-q",
             "-pf",
             "/run/dhclient-%s.pid" % interface_name,
             "-lf",
             lease_path,
             "-cf",
             "/var/lib/dhclient/dhclient-%s.conf" % interface_name,
             interface_name,
         ]
         cmdline = "\x00".join(cmdline) + "\x00"
         os.mkdir(os.path.join(proc_path, str(pid)))
         atomic_write(cmdline.encode("ascii"), cmdline_path)
     self.assertEquals(interfaces, get_dhclient_info(proc_path=proc_path))
Пример #21
0
    def _configure(self, upstream_http):
        """Update the HTTP configuration for the rack."""
        template = load_template('http', 'rackd.nginx.conf.template')
        try:
            rendered = template.substitute({
                'upstream_http':
                list(sorted(upstream_http)),
                'resource_root':
                self._resource_root,
            })
        except NameError as error:
            raise HTTPConfigFail(*error.args)
        else:
            # The rendered configuration is Unicode text but should contain
            # only ASCII characters.
            rendered = rendered.encode("ascii")

        target_path = compose_http_config_path('rackd.nginx.conf')
        os.makedirs(os.path.dirname(target_path), exist_ok=True)
        atomic_write(rendered, target_path, overwrite=True, mode=0o644)
Пример #22
0
def write_config(
        allowed_cidrs, peer_proxies=None,
        prefer_v4_proxy=False, maas_proxy_port=8000):
    """Write the proxy configuration."""
    if peer_proxies is None:
        peer_proxies = []

    context = {
        'modified': str(datetime.date.today()),
        'fqdn': socket.getfqdn(),
        'cidrs': allowed_cidrs,
        'running_in_snap': snappy.running_in_snap(),
        'snap_path': snappy.get_snap_path(),
        'snap_data_path': snappy.get_snap_data_path(),
        'snap_common_path': snappy.get_snap_common_path(),
        'dns_v4_first': prefer_v4_proxy,
        'maas_proxy_port': maas_proxy_port,
    }

    formatted_peers = []
    for peer in peer_proxies:
        formatted_peers.append({
            'address': urlparse(peer).hostname,
            'port': urlparse(peer).port
        })
    context['peers'] = formatted_peers

    template_path = locate_template('proxy', MAAS_PROXY_CONF_TEMPLATE)
    template = tempita.Template.from_filename(
        template_path, encoding="UTF-8")
    try:
        content = template.substitute(context)
    except NameError as error:
        raise ProxyConfigFail(*error.args)

    # Squid prefers ascii.
    content = content.encode("ascii")
    target_path = get_proxy_config_path()
    atomic_write(content, target_path, overwrite=True, mode=0o644)
Пример #23
0
    def write_config(self, overwrite=True, **kwargs):
        """Write out this DNS config file.

        :raises DNSConfigDirectoryMissing: if the DNS configuration directory
            does not exist.
        """
        trusted_networks = kwargs.pop("trusted_networks", "")
        context = {
            'zones': self.zones,
            'DNS_CONFIG_DIR': get_dns_config_dir(),
            'named_rndc_conf_path': get_named_rndc_conf_path(),
            'trusted_networks': trusted_networks,
            'modified': str(datetime.today()),
        }
        content = render_dns_template(self.template_file_name, kwargs, context)
        # The rendered configuration is Unicode text but should contain only
        # ASCII characters. Non-ASCII records should have been treated using
        # the rules for IDNA (Internationalized Domain Names in Applications).
        content = content.encode("ascii")
        target_path = compose_config_path(self.target_file_name)
        with report_missing_config_dir():
            atomic_write(content, target_path, overwrite=overwrite, mode=0o644)
Пример #24
0
def write_config(write_local, forwarders=None, port=None):
    """Write the syslog configuration."""
    context = {
        'user':
        '******',
        'group':
        'maas',
        'drop_priv':
        True,
        'work_dir':
        get_syslog_workdir_path(),
        'log_dir':
        get_syslog_log_path(),
        'write_local':
        write_local,
        'port':
        port if port else 5247,
        'forwarders': (sorted(forwarders, key=itemgetter('name'))
                       if forwarders is not None else []),
    }

    # Running inside the snap rsyslog is root.
    if snappy.running_in_snap():
        context['user'] = '******'
        context['group'] = 'root'
        context['drop_priv'] = False

    template_path = locate_template('syslog', MAAS_SYSLOG_CONF_TEMPLATE)
    template = tempita.Template.from_filename(template_path, encoding="UTF-8")
    try:
        content = template.substitute(context)
    except NameError as error:
        raise SyslogConfigFail(*error.args)

    # Squid prefers ascii.
    content = content.encode("ascii")
    target_path = get_syslog_config_path()
    atomic_write(content, target_path, overwrite=True, mode=0o644)
Пример #25
0
def write_config(write_local, forwarders=None, port=None):
    """Write the syslog configuration."""
    context = {
        "user":
        "******",
        "group":
        "maas",
        "drop_priv":
        True,
        "work_dir":
        get_syslog_workdir_path(),
        "log_dir":
        get_syslog_log_path(),
        "write_local":
        write_local,
        "port":
        port if port else 5247,
        "forwarders": (sorted(forwarders, key=itemgetter("name"))
                       if forwarders is not None else []),
    }

    # Running inside the snap rsyslog is root.
    if snappy.running_in_snap():
        context["user"] = "******"
        context["group"] = "root"
        context["drop_priv"] = False

    template_path = locate_template("syslog", MAAS_SYSLOG_CONF_TEMPLATE)
    template = tempita.Template.from_filename(template_path, encoding="UTF-8")
    try:
        content = template.substitute(context)
    except NameError as error:
        raise SyslogConfigFail(*error.args)

    # Squid prefers ascii.
    content = content.encode("ascii")
    target_path = get_syslog_config_path()
    atomic_write(content, target_path, overwrite=True, mode=0o644)
Пример #26
0
    def _configure(self, upstream_http):
        """Update the HTTP configuration for the rack."""
        template = load_template("http", "rackd.nginx.conf.template")
        try:
            rendered = template.substitute({
                "upstream_http":
                list(sorted(upstream_http)),
                "resource_root":
                self._resource_root,
                "machine_resources":
                os.path.join(snappy.get_snap_path(), "usr/share/maas") if
                (snappy.running_in_snap()) else "/usr/share/maas",
            })
        except NameError as error:
            raise HTTPConfigFail(*error.args)
        else:
            # The rendered configuration is Unicode text but should contain
            # only ASCII characters.
            rendered = rendered.encode("ascii")

        target_path = compose_http_config_path("rackd.nginx.conf")
        os.makedirs(os.path.dirname(target_path), exist_ok=True)
        atomic_write(rendered, target_path, overwrite=True, mode=0o644)
Пример #27
0
 def save(cls, config, filename=None):
     """Save a YAML configuration to `filename`, or to the default file."""
     if filename is None:
         filename = cls.DEFAULT_FILENAME
     dump = yaml.safe_dump(config, encoding="utf-8")
     atomic_write(dump, filename)
Пример #28
0
 def test_atomic_write_overwrites_dest_file(self):
     content = factory.make_bytes()
     filename = self.make_file(contents=factory.make_string())
     atomic_write(content, filename)
     self.assertThat(filename, FileContains(content))
Пример #29
0
 def test_atomic_write_does_not_overwrite_file_if_overwrite_false(self):
     content = factory.make_bytes()
     random_content = factory.make_bytes()
     filename = self.make_file(contents=random_content)
     atomic_write(content, filename, overwrite=False)
     self.assertThat(filename, FileContains(random_content))
Пример #30
0
 def test_atomic_write_writes_file_if_no_file_present(self):
     filename = os.path.join(self.make_dir(), factory.make_string())
     content = factory.make_bytes()
     atomic_write(content, filename, overwrite=False)
     self.assertThat(filename, FileContains(content))