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()
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'))
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
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
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()
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"}
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()
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"})
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()