示例#1
0
    def enable_redirect(self, save=True):
        backend_name = 'redirect'

        # remove any prevoius configureation
        self.disable_redirect(save=False)

        # Get or create frontend 80
        frontend = self.get_frontend(port=80)

        # Add use_backend section to the frontend
        use_backend = Config.UseBackend(backend_name=backend_name,
                                        operator='',
                                        backend_condition='',
                                        is_default=True)
        frontend.add_usebackend(use_backend)

        # Get the backend, create if not present
        backend = self.get_backend(backend_name)

        # Add redirect option to the backend
        redirect_config = Config.Config('redirect scheme https', '')
        backend.add_config(redirect_config)

        # Add server so clean won't remove it
        server = Config.Server(name=backend_name, host='127.0.0.1', port=0)
        backend.add_server(server)

        # Render new cfg file
        if save:
            self.save_config()
示例#2
0
    def __build_usebackend(self, usebackend_node):
        operator = usebackend_node.operator.text
        backendtype = usebackend_node.backendtype.text

        return config.UseBackend(
            backend_name=usebackend_node.backend_name.text,
            operator=operator,
            backend_condition=usebackend_node.backend_condition.text,
            is_default=(backendtype == 'default_backend'))
示例#3
0
    def get_backend(self, configuration, port):
        # cfg_parser = Parser(cf_path)
        # configuration = cfg_parser.build_configuration()

        name = HAProxyCtrl.get_frontend_name(port)
        exist_frontend = configuration.frontend(name)
        if exist_frontend:
            backend_name = exist_frontend.config_block['usebackends'][
                0].backend_name
            return configuration.backend(backend_name)

        backend_name = HAProxyCtrl.get_backend_name(port)
        exist_backend = configuration.backend(backend_name)
        if exist_backend:
            newbackend = exist_backend
        else:
            newbackend = config.Backend(
                backend_name, {
                    'configs': [('mode', 'http'), ('balance', 'roundrobin')],
                    'options': [('forwardfor', '')],
                    'servers': []
                })
            configuration.backends.append(newbackend)

        usebackend = config.UseBackend(newbackend.name, "", "", True)
        frontends = configuration.frontends
        newfrontend = config.Frontend(
            name, '*', port, {
                'usebackends': [
                    usebackend,
                ],
                'binds': [
                    config.Bind('*', port, None),
                ],
                'configs': [('mode', 'http')],
                'default_backend': []
            })
        frontends.append(newfrontend)
        return newbackend
示例#4
0
def add(config, frontend_name, domain, subdomain, host, port):
    frontend = config.frontend(frontend_name)
    if not frontend:
        raise ValueError('Could not locate')

    frontend.remove_acl(subdomain)
    frontend.add_acl(
        c.Acl(
            subdomain, 'hdr(host) -i {sub}.{domain}'.format(sub=subdomain,
                                                            domain=domain)))

    frontend.remove_usebackend(subdomain)
    frontend.add_usebackend(c.UseBackend(subdomain, 'if', subdomain))

    backend = config.backend(subdomain)
    if not backend:
        config.backends.append(
            c.Backend(subdomain, [c.Server(subdomain, host, port)]))
    else:
        backend.remove_server(subdomain)
        backend.add_server(c.Server(subdomain, host, port))

    return config
示例#5
0
    def enable_redirect(self, save=True):
        """Enable the generic redirect to HTTPS."""
        backend_name = "redirect"

        # remove any prevoius configureation
        self.disable_redirect(save=False)

        # Get or create frontend 80
        frontend = self.get_frontend(port=80)

        # Add use_backend section to the frontend
        use_backend = haproxy_config.UseBackend(
            backend_name=backend_name,
            operator="",
            backend_condition="",
            is_default=True,
        )
        frontend.add_usebackend(use_backend)

        # Get the backend, create if not present
        backend = self.get_backend(backend_name)

        # Add redirect option to the backend
        redirect_config = haproxy_config.Config("redirect scheme https", "")
        backend.add_config(redirect_config)

        # Add server so clean won't remove it
        server = haproxy_config.Server(name=backend_name,
                                       host="127.0.0.1",
                                       port=0)
        backend.add_server(server)

        # Render new cfg file

        if save:
            self.save_config()
示例#6
0
    def process_configs(self, configs):
        """Process related unit configuration."""
        for names, config in zip(self.get_config_names(configs), configs):
            remote_unit = names[0]
            backend_name = names[1]

            # For backwards compatibility and easy upgrades,
            # let's check, incase we've moved from the old <app>-<id>
            # style frontend names to the new multi-relation
            # <app>-<id>-<index> format - regex ^.*?(\d+)-(\d+)$
            legacy = self.legacy_name(backend_name)

            if legacy != backend_name:
                hookenv.log(
                    "Cleaning any legacy configs for {} ({})".format(
                        remote_unit, legacy),
                    "INFO",
                )
                self.clean_config(unit=legacy, backend_name=legacy, save=False)

            # Remove any prior configuration as it might have changed
            # do not write cfg file we still have edits to make
            hookenv.log(
                "Cleaning configs for remote {}, backend {}".format(
                    remote_unit, backend_name),
                "DEBUG",
            )
            self.clean_config(unit=remote_unit,
                              backend_name=backend_name,
                              save=False)

            # Get the frontend, create if not present
            frontend = self.get_frontend(config["external_port"])

            # urlbase use to accept / now they are added automatically
            # to avoid errors strip it from old configs

            if config["urlbase"]:
                config["urlbase"] = config["urlbase"].rstrip("/")

            hookenv.log("Checking frontend {}".format(str(frontend)), "DEBUG")

            if config["mode"] == "http":
                if not self.available_for_http(frontend):
                    return {
                        "cfg_good": False,
                        "msg": "Port not available for http routing",
                    }

                # Add ACL's to the frontend

                if config["urlbase"]:
                    acl = haproxy_config.Acl(name=remote_unit,
                                             value="path_beg {}/".format(
                                                 config["urlbase"]))
                    frontend.add_acl(acl)
                    acl = haproxy_config.Acl(name=remote_unit,
                                             value="path {}".format(
                                                 config["urlbase"]))
                    frontend.add_acl(acl)

                if config["subdomain"]:
                    acl = haproxy_config.Acl(
                        name=remote_unit,
                        value="hdr_beg(host) -i {}".format(
                            config["subdomain"]),
                    )
                    frontend.add_acl(acl)

                # Add use_backend section to the frontend
                use_backend = haproxy_config.UseBackend(
                    backend_name=backend_name,
                    operator="if",
                    backend_condition=remote_unit,
                    is_default=False,
                )
                frontend.add_usebackend(use_backend)

            if config["mode"] == "tcp":
                if not self.available_for_tcp(frontend, backend_name):
                    return {
                        "cfg_good":
                        False,
                        "msg": ("Frontend already in use "
                                "can not setup tcp mode"),
                    }

                mode_config = haproxy_config.Config("mode", "tcp")
                frontend.add_config(mode_config)

                # clean use backends for tcp backends, in case there is
                # any cruft left over from legacy configs

                for usebackend in frontend.usebackends():
                    frontend.remove_usebackend(usebackend.backend_name)

                use_backend = haproxy_config.UseBackend(
                    backend_name=backend_name,
                    operator="",
                    backend_condition="",
                    is_default=True,
                )
                frontend.add_usebackend(use_backend)

            # Get the backend, create if not present
            backend = self.get_backend(backend_name)

            # Set sensible connection checking parameter
            # by default. This will work for both TCP
            # and HTTP backends, if a group-id is specified,
            # nicer HTTP checks for HTTP backends will also
            # be enabled to perform HTTP requests as part of
            # checking backend health
            attributes = []

            if config["check"]:
                attributes = ["check fall 3 rise 2"]

            # Add server to the backend
            # Firstly, set the mode on the backedn to match
            # the frontend
            backend.add_config(haproxy_config.Config("mode", config["mode"]))

            # Now, for HTTP specific configuration

            if config["mode"] == "http":
                # Add cookie config if not already present
                cookie_found = False
                cookie = "cookie SERVERID insert indirect nocache"

                for test_config in backend.configs():
                    if cookie in test_config.keyword:
                        cookie_found = True

                if not cookie_found:
                    backend.add_config(haproxy_config.Config(cookie, ""))
                attributes.append("cookie {}".format(remote_unit))
                # Add httpchk option if not present

                if config["group_id"]:
                    httpchk_found = False
                    httpchk = "httpchk GET {} HTTP/1.0".format(
                        config["urlbase"] or "/")

                    for test_option in backend.options():
                        if httpchk in test_option.keyword:
                            httpchk_found = True

                    if not httpchk_found:
                        backend.add_option(haproxy_config.Option(httpchk, ""))
                    attributes.append("check")
                # Add rewrite-path if requested and not present

                if config["rewrite-path"] and config["urlbase"]:
                    rewrite_found = False
                    rewrite = ("http-request set-path "
                               "%[path,regsub(^{}/?,/)]").format(
                                   config["urlbase"])

                    for test_cfg in backend.configs():
                        if rewrite in test_cfg.keyword:
                            rewrite_found = True

                    if not rewrite_found:
                        backend.add_config(haproxy_config.Config(rewrite, ""))

                if config["acl-local"]:
                    if not backend.acl("local"):
                        backend.add_acl(
                            haproxy_config.Acl(
                                "local",
                                ("src 10.0.0.0/8 "
                                 "172.16.0.0/12 "
                                 "192.168.0.0/16 "
                                 "127.0.0.0/8 "
                                 "fd00::/8 "
                                 "fe80::/10 "
                                 "::1/128"),
                            ))
                        backend.add_config(
                            haproxy_config.Config(
                                "http-request deny if !local", ""))

                if config["proxypass"]:
                    proxy_found = False

                    for test_option in backend.options():
                        if "forwardfor" in test_cfg.keyword:
                            proxy_found = True

                    if not proxy_found:
                        backend.add_option(
                            haproxy_config.Option("forwardfor", ""))

                    if config["external_port"] == 443:
                        forward_for = ("http-request set-header "
                                       "X-Forwarded-Proto https")
                    else:
                        forward_for = ("http-request set-header "
                                       "X-Forwarded-Proto http")
                    backend.add_config(haproxy_config.Config(forward_for, ""))

                if config["ssl"]:
                    if config["ssl-verify"]:
                        ssl_attrib = "ssl"
                    else:
                        ssl_attrib = "ssl verify none"
                    attributes.append(ssl_attrib)
            server = haproxy_config.Server(
                name=remote_unit,
                host=config["internal_host"],
                port=config["internal_port"],
                attributes=attributes,
            )
            backend.add_server(server)

        # Render new cfg file
        self.save_config()

        return {"cfg_good": True, "msg": "configuration applied"}
示例#7
0
    def enable_letsencrypt(self):
        """Enable certbot for TLS certificate generation."""
        hookenv.log("Enabling letsencrypt", "DEBUG")
        unit_name = "letsencrypt"
        backend_name = "letsencrypt-backend"

        frontend = self.get_frontend(80)

        if not self.available_for_http(frontend):
            hookenv.log("Port 80 not available for http use by letsencrypt",
                        "ERROR")

            return  # TODO: Should I error here, or is returning a log ok?

        # Only configure the rest if we haven't already done so to avoid
        # checking every change for already existing
        first_run = True

        for acl in frontend.acls():
            if acl.name == unit_name:
                first_run = False

        if first_run:
            # Add ACL to the frontend
            acl = haproxy_config.Acl(
                name=unit_name,
                value="path_beg -i /.well-known/acme-challenge/")
            frontend.add_acl(acl)
            # Add usebackend
            use_backend = haproxy_config.UseBackend(
                backend_name=backend_name,
                operator="if",
                backend_condition=unit_name,
                is_default=False,
            )
            frontend.add_usebackend(use_backend)

            # Get the backend, create if not present
            backend = self.get_backend(backend_name)

            # Add server to the backend
            attributes = [""]
            server = haproxy_config.Server(
                name=unit_name,
                host="127.0.0.1",
                port=self.letsencrypt_config["port"],
                attributes=attributes,
            )
            backend.add_server(server)

            # Render new cfg file
            self.save_config()

        # Call the register function from the letsencrypt layer
        hookenv.log(
            "Letsencrypt port: {}".format(self.letsencrypt_config["port"]),
            "DEBUG")
        hookenv.log(
            "Letsencrypt domains: {}".format(
                self.charm_config["letsencrypt-domains"]),
            "DEBUG",
        )

        if letsencrypt.register_domains() > 0:
            hookenv.log(
                ("Failed letsencrypt registration see "
                 "/var/log/letsencrypt/letsencrypt.log"),
                "ERROR",
            )

            return  # TODO: Should I error here or is just returning a log ok?

        # create the merged .pem for HAProxy
        self.merge_letsencrypt_cert()

        # Configure the frontend 443
        frontend = self.get_frontend(443)

        if not len(frontend.binds()[0].attributes):
            frontend.binds()[0].attributes.append("ssl crt {}".format(
                self.cert_file))

        if self.supports_http2():
            frontend.binds()[0].attributes.append("alpn h2,http/1.1")

        if first_run:
            frontend.add_acl(acl)
            frontend.add_usebackend(use_backend)

            if self.charm_config["destination-https-rewrite"]:
                frontend.add_config(
                    haproxy_config.Config(
                        "reqirep",
                        "Destination:\\ https(.*) Destination:\\ http\\\\1 "))
            self.save_config()

        # Add cron for renew
        self.add_cert_cron()
示例#8
0
    def process_configs(self, configs):
        ''' Note this requires a remote unit '''
        for names, config in zip(self.get_config_names(configs), configs):
            remote_unit = names[0]
            backend_name = names[1]

            # For backwards compatibility and easy upgrades,
            # let's check, incase we've moved from the old <app>-<id>
            # style frontend names to the new multi-relation
            # <app>-<id>-<index> format - regex ^.*?(\d+)-(\d+)$
            legacy = self.legacy_name(backend_name)
            if legacy != backend_name:
                hookenv.log(
                    'Cleaning any legacy configs for {} ({})'.format(
                        remote_unit, legacy), 'INFO')
                self.clean_config(unit=legacy, backend_name=legacy, save=False)

            # Remove any prior configuration as it might have changed
            # do not write cfg file we still have edits to make
            hookenv.log(
                'Cleaning configs for remote {}, backend {}'.format(
                    remote_unit, backend_name), 'DEBUG')
            self.clean_config(unit=remote_unit,
                              backend_name=backend_name,
                              save=False)

            # Get the frontend, create if not present
            frontend = self.get_frontend(config['external_port'])

            # urlbase use to accept / now they are added automatically
            # to avoid errors strip it from old configs
            if config['urlbase']:
                config['urlbase'] = config['urlbase'].strip('/')

            hookenv.log('Checking frontend {}'.format(str(frontend)), 'DEBUG')

            if config['mode'] == 'http':
                if not self.available_for_http(frontend):
                    return ({
                        "cfg_good": False,
                        "msg": "Port not available for http routing"
                    })

                # Add ACL's to the frontend
                if config['urlbase']:
                    acl = Config.Acl(name=remote_unit,
                                     value='path_beg /{}/'.format(
                                         config['urlbase']))
                    frontend.add_acl(acl)
                    acl = Config.Acl(name=remote_unit,
                                     value='path /{}'.format(
                                         config['urlbase']))
                    frontend.add_acl(acl)
                if config['subdomain']:
                    acl = Config.Acl(name=remote_unit,
                                     value='hdr_beg(host) -i {}'.format(
                                         config['subdomain']))
                    frontend.add_acl(acl)

                # Add use_backend section to the frontend
                use_backend = Config.UseBackend(backend_name=backend_name,
                                                operator='if',
                                                backend_condition=remote_unit,
                                                is_default=False)
                frontend.add_usebackend(use_backend)

            if config['mode'] == 'tcp':
                if not self.available_for_tcp(frontend, backend_name):
                    return ({
                        "cfg_good":
                        False,
                        "msg": ("Frontend already in use "
                                "can not setup tcp mode")
                    })

                mode_config = Config.Config('mode', 'tcp')
                frontend.add_config(mode_config)

                # clean use backends for tcp backends, in case there is
                # any cruft left over from legacy configs
                for usebackend in frontend.usebackends():
                    frontend.remove_usebackend(usebackend.backend_name)

                use_backend = Config.UseBackend(backend_name=backend_name,
                                                operator='',
                                                backend_condition='',
                                                is_default=True)
                frontend.add_usebackend(use_backend)

            # Get the backend, create if not present
            backend = self.get_backend(backend_name)

            # Set sensible connection checking parameter
            # by default. This will work for both TCP
            # and HTTP backends, if a group-id is specified,
            # nicer HTTP checks for HTTP backends will also
            # be enabled to perform HTTP requests as part of
            # checking backend health
            attributes = ['check fall 3 rise 2']

            # Add server to the backend
            # Firstly, set the mode on the backedn to match
            # the frontend
            backend.add_config(Config.Config('mode', config['mode']))

            # Now, for HTTP specific configuration
            if config['mode'] == 'http':
                # Add cookie config if not already present
                cookie_found = False
                cookie = 'cookie SERVERID insert indirect nocache'
                for test_config in backend.configs():
                    if cookie in test_config.keyword:
                        cookie_found = True
                if not cookie_found:
                    backend.add_config(Config.Config(cookie, ''))
                attributes.append('cookie {}'.format(remote_unit))
                # Add httpchk option if not present
                if config['group_id']:
                    httpchk_found = False
                    httpchk = 'httpchk GET {} HTTP/1.0'.format(
                        config['urlbase'] or '/')
                    for test_option in backend.options():
                        if httpchk in test_option.keyword:
                            httpchk_found = True
                    if not httpchk_found:
                        backend.add_option(Config.Option(httpchk, ''))
                    attributes.append('check')
                # Add rewrite-path if requested and not present
                if config['rewrite-path'] and config['urlbase']:
                    rewrite_found = False
                    rewrite = ("http-request set-path "
                               "%[path,regsub(^/{}/?,/)]").format(
                                   config['urlbase'])
                    for test_cfg in backend.configs():
                        if rewrite in test_cfg.keyword:
                            rewrite_found = True
                    if not rewrite_found:
                        backend.add_config(Config.Config(rewrite, ''))
                if config['acl-local']:
                    if not backend.acl('local'):
                        backend.add_acl(
                            Config.Acl('local', ("src 10.0.0.0/8 "
                                                 "192.168.0.0/16 "
                                                 "127.0.0.0/8")))
                        backend.add_config(
                            Config.Config('http-request deny if !local', ''))
                if config['proxypass']:
                    proxy_found = False
                    for test_option in backend.options():
                        if 'forwardfor' in test_cfg.keyword:
                            proxy_found = True
                    if not proxy_found:
                        backend.add_option(Config.Option('forwardfor', ''))
                    if config['external_port'] == 443:
                        forward_for = ("http-request set-header "
                                       "X-Forwarded-Proto https")
                    else:
                        forward_for = ("http-request set-header "
                                       "X-Forwarded-Proto http")
                    backend.add_config(Config.Config(forward_for, ''))
                if config['ssl']:
                    if config['ssl-verify']:
                        ssl_attrib = 'ssl'
                    else:
                        ssl_attrib = 'ssl verify none'
                    attributes.append(ssl_attrib)
            server = Config.Server(name=remote_unit,
                                   host=config['internal_host'],
                                   port=config['internal_port'],
                                   attributes=attributes)
            backend.add_server(server)

        # Render new cfg file
        self.save_config()
        return ({"cfg_good": True, "msg": "configuration applied"})
示例#9
0
    def enable_letsencrypt(self):
        hookenv.log("Enabling letsencrypt", "DEBUG")
        unit_name = 'letsencrypt'
        backend_name = 'letsencrypt-backend'

        frontend = self.get_frontend(80)
        if not self.available_for_http(frontend):
            hookenv.log("Port 80 not available for http use by letsencrypt",
                        "ERROR")
            return  # TODO: Should I error here, or is returning a log ok?

        # Only configure the rest if we haven't already done so to avoid
        # checking every change for already existing
        first_run = True
        for acl in frontend.acls():
            if acl.name == unit_name:
                first_run = False
        if first_run:
            # Add ACL to the frontend
            acl = Config.Acl(name=unit_name,
                             value='path_beg -i /.well-known/acme-challenge/')
            frontend.add_acl(acl)
            # Add usebackend
            use_backend = Config.UseBackend(backend_name=backend_name,
                                            operator='if',
                                            backend_condition=unit_name,
                                            is_default=False)
            frontend.add_usebackend(use_backend)

            # Get the backend, create if not present
            backend = self.get_backend(backend_name)

            # Add server to the backend
            attributes = ['']
            server = Config.Server(name=unit_name,
                                   host='127.0.0.1',
                                   port=self.letsencrypt_config['port'],
                                   attributes=attributes)
            backend.add_server(server)

            # Render new cfg file
            self.save_config()

        # Call the register function from the letsencrypt layer
        hookenv.log(
            "Letsencrypt port: {}".format(self.letsencrypt_config['port']),
            'DEBUG')
        hookenv.log(
            "Letsencrypt domains: {}".format(
                self.charm_config['letsencrypt-domains']), 'DEBUG')
        if letsencrypt.register_domains() > 0:
            hookenv.log(("Failed letsencrypt registration see "
                         "/var/log/letsencrypt/letsencrypt.log"), "ERROR")
            return  # TODO: Should I error here or is just returning a log ok?

        # create the merged .pem for HAProxy
        self.merge_letsencrypt_cert()

        # Configure the frontend 443
        frontend = self.get_frontend(443)
        if not len(frontend.binds()[0].attributes):
            frontend.binds()[0].attributes.append('ssl crt {}'.format(
                self.cert_file))
        if first_run:
            frontend.add_acl(acl)
            frontend.add_usebackend(use_backend)
            if self.charm_config['destination-https-rewrite']:
                frontend.add_config(
                    Config.Config(
                        'reqirep',
                        'Destination:\\ https(.*) Destination:\\ http\\\\1 '))
            self.save_config()

        # Add cron for renew
        self.add_cert_cron()