PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/csf.py |
""" Support for Config Server Firewall (CSF) ======================================== :maintainer: Mostafa Hussein <mostafa.hussein91@gmail.com> :maturity: new :platform: Linux """ import re import salt.utils.path from salt.exceptions import CommandExecutionError, SaltInvocationError def __virtual__(): """ Only load if csf exists on the system """ if salt.utils.path.which("csf") is None: return (False, "The csf execution module cannot be loaded: csf unavailable.") else: return True def _temp_exists(method, ip): """ Checks if the ip exists as a temporary rule based on the method supplied, (tempallow, tempdeny). """ _type = method.replace("temp", "").upper() cmd = ( "csf -t | awk -v code=1 -v type={_type} -v ip={ip} '$1==type && $2==ip {{code=0}}" " END {{exit code}}'".format(_type=_type, ip=ip) ) exists = __salt__["cmd.run_all"](cmd) return not bool(exists["retcode"]) def _exists_with_port(method, rule): path = f"/etc/csf/csf.{method}" return __salt__["file.contains"](path, rule) def exists( method, ip, port=None, proto="tcp", direction="in", port_origin="d", ip_origin="d", ttl=None, comment="", ): """ Returns true a rule for the ip already exists based on the method supplied. Returns false if not found. CLI Example: .. code-block:: bash salt '*' csf.exists allow 1.2.3.4 salt '*' csf.exists tempdeny 1.2.3.4 """ if method.startswith("temp"): return _temp_exists(method, ip) if port: rule = _build_port_rule( ip, port, proto, direction, port_origin, ip_origin, comment ) return _exists_with_port(method, rule) exists = __salt__["cmd.run_all"](f"egrep ^'{ip} +' /etc/csf/csf.{method}") return not bool(exists["retcode"]) def __csf_cmd(cmd): """ Execute csf command """ csf_cmd = "{} {}".format(salt.utils.path.which("csf"), cmd) out = __salt__["cmd.run_all"](csf_cmd) if out["retcode"] != 0: if not out["stderr"]: ret = out["stdout"] else: ret = out["stderr"] raise CommandExecutionError(f"csf failed: {ret}") else: ret = out["stdout"] return ret def _status_csf(): """ Return True if csf is running otherwise return False """ cmd = "test -e /etc/csf/csf.disable" out = __salt__["cmd.run_all"](cmd) return bool(out["retcode"]) def _get_opt(method): """ Returns the cmd option based on a long form argument. """ opts = { "allow": "-a", "deny": "-d", "unallow": "-ar", "undeny": "-dr", "tempallow": "-ta", "tempdeny": "-td", "temprm": "-tr", } return opts[method] def _build_args(method, ip, comment): """ Returns the cmd args for csf basic allow/deny commands. """ opt = _get_opt(method) args = f"{opt} {ip}" if comment: args += f" {comment}" return args def _access_rule( method, ip=None, port=None, proto="tcp", direction="in", port_origin="d", ip_origin="d", comment="", ): """ Handles the cmd execution for allow and deny commands. """ if _status_csf(): if ip is None: return {"error": "You must supply an ip address or CIDR."} if port is None: args = _build_args(method, ip, comment) return __csf_cmd(args) else: if method not in ["allow", "deny"]: return { "error": ( "Only allow and deny rules are allowed when specifying a port." ) } return _access_rule_with_port( method=method, ip=ip, port=port, proto=proto, direction=direction, port_origin=port_origin, ip_origin=ip_origin, comment=comment, ) def _build_port_rule(ip, port, proto, direction, port_origin, ip_origin, comment): kwargs = { "ip": ip, "port": port, "proto": proto, "direction": direction, "port_origin": port_origin, "ip_origin": ip_origin, } rule = "{proto}|{direction}|{port_origin}={port}|{ip_origin}={ip}".format(**kwargs) if comment: rule += f" #{comment}" return rule def _remove_access_rule_with_port( method, ip, port, proto="tcp", direction="in", port_origin="d", ip_origin="d", ttl=None, ): rule = _build_port_rule( ip, port=port, proto=proto, direction=direction, port_origin=port_origin, ip_origin=ip_origin, comment="", ) rule = rule.replace("|", "[|]") rule = rule.replace(".", "[.]") result = __salt__["file.replace"]( f"/etc/csf/csf.{method}", pattern=f"^{rule}(( +)?\\#.*)?$\n", # pylint: disable=W1401 repl="", ) return result def _csf_to_list(option): """ Extract comma-separated values from a csf.conf option and return a list. """ result = [] line = get_option(option) if line: csv = line.split("=")[1].replace(" ", "").replace('"', "") result = csv.split(",") return result def split_option(option): return re.split(r"(?: +)?\=(?: +)?", option) def get_option(option): pattern = rf'^{option}(\ +)?\=(\ +)?".*"$' # pylint: disable=W1401 grep = __salt__["file.grep"]("/etc/csf/csf.conf", pattern, "-E") if "stdout" in grep and grep["stdout"]: line = grep["stdout"] return line return None def set_option(option, value): current_option = get_option(option) if not current_option: return {"error": "No such option exists in csf.conf"} result = __salt__["file.replace"]( "/etc/csf/csf.conf", pattern=rf'^{option}(\ +)?\=(\ +)?".*"', # pylint: disable=W1401 repl=f'{option} = "{value}"', ) return result def get_skipped_nics(ipv6=False): if ipv6: option = "ETH6_DEVICE_SKIP" else: option = "ETH_DEVICE_SKIP" skipped_nics = _csf_to_list(option) return skipped_nics def skip_nic(nic, ipv6=False): nics = get_skipped_nics(ipv6=ipv6) nics.append(nic) return skip_nics(nics, ipv6) def skip_nics(nics, ipv6=False): if ipv6: ipv6 = "6" else: ipv6 = "" nics_csv = ",".join(map(str, nics)) result = __salt__["file.replace"]( "/etc/csf/csf.conf", # pylint: disable=anomalous-backslash-in-string pattern=rf'^ETH{ipv6}_DEVICE_SKIP(\ +)?\=(\ +)?".*"', # pylint: enable=anomalous-backslash-in-string repl=f'ETH{ipv6}_DEVICE_SKIP = "{nics_csv}"', ) return result def _access_rule_with_port( method, ip, port, proto="tcp", direction="in", port_origin="d", ip_origin="d", ttl=None, comment="", ): results = {} if direction == "both": directions = ["in", "out"] else: directions = [direction] for direction in directions: _exists = exists( method, ip, port=port, proto=proto, direction=direction, port_origin=port_origin, ip_origin=ip_origin, ttl=ttl, comment=comment, ) if not _exists: rule = _build_port_rule( ip, port=port, proto=proto, direction=direction, port_origin=port_origin, ip_origin=ip_origin, comment=comment, ) path = f"/etc/csf/csf.{method}" results[direction] = __salt__["file.append"](path, rule) return results def _tmp_access_rule( method, ip=None, ttl=None, port=None, direction="in", port_origin="d", ip_origin="d", comment="", ): """ Handles the cmd execution for tempdeny and tempallow commands. """ if _status_csf(): if ip is None: return {"error": "You must supply an ip address or CIDR."} if ttl is None: return {"error": "You must supply a ttl."} args = _build_tmp_access_args(method, ip, ttl, port, direction, comment) return __csf_cmd(args) def _build_tmp_access_args(method, ip, ttl, port, direction, comment): """ Builds the cmd args for temporary access/deny opts. """ opt = _get_opt(method) args = f"{opt} {ip} {ttl}" if port: args += f" -p {port}" if direction: args += f" -d {direction}" if comment: args += f" #{comment}" return args def running(): """ Check csf status CLI Example: .. code-block:: bash salt '*' csf.running """ return _status_csf() def disable(): """ Disable csf permanently CLI Example: .. code-block:: bash salt '*' csf.disable """ if _status_csf(): return __csf_cmd("-x") def enable(): """ Activate csf if not running CLI Example: .. code-block:: bash salt '*' csf.enable """ if not _status_csf(): return __csf_cmd("-e") def reload(): """ Restart csf CLI Example: .. code-block:: bash salt '*' csf.reload """ return __csf_cmd("-r") def tempallow(ip=None, ttl=None, port=None, direction=None, comment=""): """ Add an rule to the temporary ip allow list. See :func:`_access_rule`. 1- Add an IP: CLI Example: .. code-block:: bash salt '*' csf.tempallow 127.0.0.1 3600 port=22 direction='in' comment='# Temp dev ssh access' """ return _tmp_access_rule("tempallow", ip, ttl, port, direction, comment) def tempdeny(ip=None, ttl=None, port=None, direction=None, comment=""): """ Add a rule to the temporary ip deny list. See :func:`_access_rule`. 1- Add an IP: CLI Example: .. code-block:: bash salt '*' csf.tempdeny 127.0.0.1 300 port=22 direction='in' comment='# Brute force attempt' """ return _tmp_access_rule("tempdeny", ip, ttl, port, direction, comment) def allow( ip, port=None, proto="tcp", direction="in", port_origin="d", ip_origin="s", ttl=None, comment="", ): """ Add an rule to csf allowed hosts See :func:`_access_rule`. 1- Add an IP: CLI Example: .. code-block:: bash salt '*' csf.allow 127.0.0.1 salt '*' csf.allow 127.0.0.1 comment="Allow localhost" """ return _access_rule( "allow", ip, port=port, proto=proto, direction=direction, port_origin=port_origin, ip_origin=ip_origin, comment=comment, ) def deny( ip, port=None, proto="tcp", direction="in", port_origin="d", ip_origin="d", ttl=None, comment="", ): """ Add an rule to csf denied hosts See :func:`_access_rule`. 1- Deny an IP: CLI Example: .. code-block:: bash salt '*' csf.deny 127.0.0.1 salt '*' csf.deny 127.0.0.1 comment="Too localhosty" """ return _access_rule( "deny", ip, port, proto, direction, port_origin, ip_origin, comment ) def remove_temp_rule(ip): opt = _get_opt("temprm") args = f"{opt} {ip}" return __csf_cmd(args) def unallow(ip): """ Remove a rule from the csf denied hosts See :func:`_access_rule`. 1- Deny an IP: CLI Example: .. code-block:: bash salt '*' csf.unallow 127.0.0.1 """ return _access_rule("unallow", ip) def undeny(ip): """ Remove a rule from the csf denied hosts See :func:`_access_rule`. 1- Deny an IP: CLI Example: .. code-block:: bash salt '*' csf.undeny 127.0.0.1 """ return _access_rule("undeny", ip) def remove_rule( method, ip, port=None, proto="tcp", direction="in", port_origin="d", ip_origin="s", ttl=None, comment="", ): if method.startswith("temp") or ttl: return remove_temp_rule(ip) if not port: if method == "allow": return unallow(ip) elif method == "deny": return undeny(ip) if port: return _remove_access_rule_with_port( method=method, ip=ip, port=port, proto=proto, direction=direction, port_origin=port_origin, ip_origin=ip_origin, ) def allow_ports(ports, proto="tcp", direction="in"): """ Fully replace the incoming or outgoing ports line in the csf.conf file - e.g. TCP_IN, TCP_OUT, UDP_IN, UDP_OUT, etc. CLI Example: .. code-block:: bash salt '*' csf.allow_ports ports="[22,80,443,4505,4506]" proto='tcp' direction='in' """ results = [] ports = set(ports) ports = list(ports) proto = proto.upper() direction = direction.upper() _validate_direction_and_proto(direction, proto) ports_csv = ",".join(map(str, ports)) directions = build_directions(direction) for direction in directions: result = __salt__["file.replace"]( "/etc/csf/csf.conf", # pylint: disable=anomalous-backslash-in-string pattern=rf'^{proto}_{direction}(\ +)?\=(\ +)?".*"$', # pylint: enable=anomalous-backslash-in-string repl=f'{proto}_{direction} = "{ports_csv}"', ) results.append(result) return results def get_ports(proto="tcp", direction="in"): """ Lists ports from csf.conf based on direction and protocol. e.g. - TCP_IN, TCP_OUT, UDP_IN, UDP_OUT, etc.. CLI Example: .. code-block:: bash salt '*' csf.allow_port 22 proto='tcp' direction='in' """ proto = proto.upper() direction = direction.upper() results = {} _validate_direction_and_proto(direction, proto) directions = build_directions(direction) for direction in directions: option = f"{proto}_{direction}" results[direction] = _csf_to_list(option) return results def _validate_direction_and_proto(direction, proto): if direction.upper() not in ["IN", "OUT", "BOTH"]: raise SaltInvocationError("You must supply a direction of in, out, or both") if proto.upper() not in ["TCP", "UDP", "TCP6", "UDP6"]: raise SaltInvocationError( "You must supply tcp, udp, tcp6, or udp6 for the proto keyword" ) return def build_directions(direction): direction = direction.upper() if direction == "BOTH": directions = ["IN", "OUT"] else: directions = [direction] return directions def allow_port(port, proto="tcp", direction="both"): """ Like allow_ports, but it will append to the existing entry instead of replacing it. Takes a single port instead of a list of ports. CLI Example: .. code-block:: bash salt '*' csf.allow_port 22 proto='tcp' direction='in' """ ports = get_ports(proto=proto, direction=direction) direction = direction.upper() _validate_direction_and_proto(direction, proto) directions = build_directions(direction) results = [] for direction in directions: _ports = ports[direction] _ports.append(port) results += allow_ports(_ports, proto=proto, direction=direction) return results def get_testing_status(): testing = _csf_to_list("TESTING")[0] return testing def _toggle_testing(val): if val == "on": val = "1" elif val == "off": val = "0" else: raise SaltInvocationError("Only valid arg is 'on' or 'off' here.") result = __salt__["file.replace"]( "/etc/csf/csf.conf", pattern=r'^TESTING(\ +)?\=(\ +)?".*"', # pylint: disable=W1401 repl=f'TESTING = "{val}"', ) return result def enable_testing_mode(): return _toggle_testing("on") def disable_testing_mode(): return _toggle_testing("off")