class Template(object): def __init__(self, check, context): self.check = check self.env = ImmutableSandboxedEnvironment() _ctx = copy.deepcopy(context) self.variables = _ctx.pop("variables", {}) self.ctx = {"depc": _ctx} def _recursive_render(self, tpl): """ A variable can contain itself another variable : we loop until the template is finally completely formed. """ new_tpl = self.env.from_string(tpl).render(**self.ctx) if new_tpl != tpl: return self._recursive_render(new_tpl) else: return tpl def _render_part(self, value): """ Recursively parse a dict and render every part. """ if isinstance(value, str): return self._recursive_render(value) elif isinstance(value, list): for i, l in enumerate(value): value[i] = self._render_part(l) elif isinstance(value, dict): for k, v in value.items(): value[k] = self._render_part(v) return value def render(self, context=None): if context: self.ctx["depc"] = context # Add custom filters self.env.filters["iso8601"] = iso8601_filter self.env.filters["regex_search"] = regex_search_filter self.env.filters["regex_search_bool"] = regex_search_bool_filter self.env.filters["bool"] = to_bool_filter # Add the variables self.ctx["depc"]["rule"] = self.variables.get("rule", {}) self.ctx["depc"]["team"] = self.variables.get("team", {}) self.ctx["depc"]["check"] = self.variables.get("checks", {}).get( self.check.name, {}) self.ctx["depc"]["source"] = self.variables.get("sources", {}).get( self.check.source.name, {}) parameters = copy.deepcopy(self.check.parameters) return self._render_part(parameters)
def render(template: str, context: Dict[str, Any] = {}) -> str: environment = ImmutableSandboxedEnvironment(undefined=StrictUndefined) environment.globals = context jinja_template = environment.from_string(template) output = None try: output = jinja_template.render() except UndefinedError as e: raise TemplateException(e.message) except TypeError as e: if str(e) == "no loader for this environment specified": raise TemplateException("Extending templates is not allowed") else: raise e return output
def test_immutable_environment(self): env = ImmutableSandboxedEnvironment() self.assert_raises(SecurityError, env.from_string('{{ [].append(23) }}').render) self.assert_raises(SecurityError, env.from_string('{{ {1:2}.clear() }}').render)
def test_immutable_environment(self, env): env = ImmutableSandboxedEnvironment() pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render) pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render)
class NginxConfigSDK: """NginxConfigSDK refreshes the configuration of the Nginx server based on default or given values. Args: args (dict, optional): A dictionary of configuration parameters to overide default action of class. Default is empty. Attributes: log (instance): Logging utility. nginx_conf (dict): Configuration parameter dictionary. env (instance): Jinja Sandbox enivornment. """ def __init__(self, args={}): self.log = RevSysLogger(args["verbose_debug"]) self.nginx_conf = {} self._set_default_values() self._interpret_arguments(args) self.env = ImmutableSandboxedEnvironment(line_statement_prefix=None, trim_blocks=True, lstrip_blocks=True, undefined=StrictUndefined) # Sets default location of JSON and Jinja template def _set_default_values(self): self.nginx_conf["jinja_template"] = script_configs.JINJA_TEMPLATE self.nginx_conf["jinja_conf_vars"] = script_configs.JINJA_CONF_VARS self.nginx_conf["conf_name"] = script_configs.CONF_NAME self.nginx_conf["tmp_location"] = script_configs.TMP_PATH self.nginx_conf["final_location"] = script_configs.NGINX_FINAL_LOCATION self.nginx_conf[ "backup_location"] = script_configs.NGINX_BACKUP_LOCATION # Reads the options provided by the command line call def _interpret_arguments(self, args): # override local arguments if a value was provided from outside for item in args: self.nginx_conf[item] = args[item] # Read contents of Jinja template def _read_jinja_template(self): self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) template_full_path = self.nginx_conf["jinja_template"] self.log.LOGD("Loading SDK Nginx template!") with open(template_full_path) as f: self.string_template = f.read() self.log.LOGD("Loaded SDK Nginx template: " + template_full_path) # Read JSON file and save them into class instance's config_vars variable def _read_sdk_config_files(self): """ input: reads the content of the configuration json file in class variable nginx_conf["jinja_conf_vars"] output: returns a status code and populates self.config_vars if everything is ok - 0 - if no problems have been encountered - 1 - if the provided file is not in the correct json file - 2 - if the provided file doesn't exist - 3 - unknown error case """ self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) conf_full_path = self.nginx_conf["jinja_conf_vars"] self.log.LOGD("Reading file from: " + conf_full_path) try: with open(conf_full_path) as f: try: self.config_vars = json.load(f) return 0 except BaseException: self.log.LOGE("Bad JSON format for file " + conf_full_path) return 1 except BaseException: self.log.LOGE("Can't find file " + conf_full_path) return 2 # Generates Nginx config file from jinja template stored in class variable # string_template and writes into final file. def _generate_final_nginx_config(self): self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) key_list = self.config_vars["configs"] template = self.env.from_string(self.string_template) final_nginx_config = template.render( configs=key_list, bpname=socket.gethostname().split('.')[0]) final_file = self.nginx_conf["tmp_location"] + \ self.nginx_conf["conf_name"] with open(final_file, 'w+') as f: f.write(final_nginx_config) shutil.copy2( self.nginx_conf["tmp_location"] + self.nginx_conf["conf_name"], self.nginx_conf["final_location"] + self.nginx_conf["conf_name"]) # Backs up the current Nginx apps Configuration found in # /etc/nginx/conf.d/revsw-apps.conf on edge server. def _backup_active_sdk_nginx_config(self): self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) # make backup for this file conf_final_path = self.nginx_conf["final_location"] + \ self.nginx_conf["conf_name"] conf_backup_path = self.nginx_conf["backup_location"] + \ self.nginx_conf["conf_name"] try: if not os.path.exists(self.nginx_conf["backup_location"]): os.makedirs(self.nginx_conf["backup_location"]) if os.path.exists(conf_final_path): shutil.copy2(conf_final_path, conf_backup_path) except BaseException: self.log.LOGE( "An error appeared while trying to backup the original file " + conf_final_path + " to " + conf_backup_path) raise # Remove the current Nginx Configuration def _remove_active_sdk_nginx_config(self): self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) # remove the active configuration file conf_final_path = self.nginx_conf["final_location"] + \ self.nginx_conf["conf_name"] try: if os.path.exists(conf_final_path): # remove the file os.remove(conf_final_path) except BaseException: self.log.LOGE( "An error appeared while removing the configuration file " + conf_final_path) raise # Restores the backup Nginx configuration def _restore_sdk_nginx_from_backup(self): self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) # restore file from tmp backup location try: shutil.copy2( self.nginx_conf["backup_location"] + self.nginx_conf["conf_name"], self.nginx_conf["final_location"] + self.nginx_conf["conf_name"]) except BaseException: self.log.LOGE( "An error appeared while trying to get backup file! Stop processing" ) raise # Reloads the running Nginx process def _load_new_configuration(self): self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) # check if configuration is correct p = subprocess.Popen('/etc/init.d/revsw-nginx configtest', shell=True) p.communicate() if p.returncode != 0: self.log.LOGE("Nginx configuration has a problem!") return p.returncode # nginx reload configuration if there are no errors p = subprocess.Popen('/etc/init.d/revsw-nginx reload', shell=True) p.communicate() return p.returncode def refresh_configuration(self): """Refreshes Nginx configuration based on Jinja template and JSON found in jinja_template and jinja_conf_vars class variables """ self.log.LOGI("Starting processing " + sys._getframe().f_code.co_name) # backup current configuration self._read_jinja_template() file_read_status = self._read_sdk_config_files() # in case that a problem appeared reading the configuration JSON file # remove the config file and reload nginx if file_read_status != 0: self._remove_active_sdk_nginx_config() result = self._load_new_configuration() if result != 0: exit(1) exit(0) flags_problem = 0 try: # check if configuration type is correct tmp = self.config_vars["configuration_type"] if tmp != "sdk_apps_config": self.log.LOGE("The provided configuration type is not valid!") raise except BaseException: self.log.LOGE("Key configuration_type must be defined!") flags_problem = 1 try: # check if operation is defined tmp = self.config_vars["operation"] except BaseException: self.log.LOGE("JSON file doesn't contain operation type!") flags_problem = 1 try: # check configs is defined tmp = self.config_vars["configs"] except BaseException: self.log.LOGE("JSON file doesn't contain configs parameter!") flags_problem = 1 if flags_problem == 1: self._remove_active_sdk_nginx_config() result = self._load_new_configuration() exit(1) if self.config_vars["operation"] != "app-update": self.log.LOGD( "Unknown operation was provided! Exiting gracefully!") exit(0) config_problem = 0 try: if not isinstance(self.config_vars["configs"], list): self.log.LOGE("Param configs should be a list!") raise if len(self.config_vars["configs"]) < 1: self.log.LOGE("At least one config should be defined!") raise except BaseException: config_problem = 1 if config_problem == 0: self._backup_active_sdk_nginx_config() self._generate_final_nginx_config() else: self._remove_active_sdk_nginx_config() result = self._load_new_configuration() if (result != 0) and (config_problem == 0): self.log.LOGE( "Problem loading new configuration - restoring original file") self._restore_sdk_nginx_from_backup() sys.exit(1)