diff --git a/pyinfra/api/config.py b/pyinfra/api/config.py index 004bfceb2..a6af53646 100644 --- a/pyinfra/api/config.py +++ b/pyinfra/api/config.py @@ -48,18 +48,12 @@ class ConfigDefaults: def check_pyinfra_version(version: str): if not version: return - running_version = parse_version(__version__) - required_versions = Requirement.parse( - "pyinfra{0}".format(version), - ) + required_versions = Requirement.parse("pyinfra{0}".format(version)) - if running_version not in required_versions: + if not required_versions.specifier.contains(running_version): raise PyinfraError( - ("pyinfra version requirement not met " "(requires {0}, running {1})").format( - version, - __version__, - ), + f"pyinfra version requirement not met (requires {version}, running {__version__})" ) diff --git a/pyinfra/facts/hardware.py b/pyinfra/facts/hardware.py index 81c598ee6..9a26902bb 100644 --- a/pyinfra/facts/hardware.py +++ b/pyinfra/facts/hardware.py @@ -97,156 +97,191 @@ def process(self, output): return devices -nettools_1_regexes = [ - ( - r"^inet addr:([0-9\.]+).+Bcast:([0-9\.]+).+Mask:([0-9\.]+)$", - ("ipv4", "address", "broadcast", "netmask"), - ), - ( - r"^inet6 addr: ([0-9a-z:]+)\/([0-9]+) Scope:Global", - ("ipv6", "address", "mask_bits"), - ), -] - -nettools_2_regexes = [ - ( - r"^inet ([0-9\.]+)\s+netmask ([0-9\.fx]+)(?:\s+broadcast ([0-9\.]+))?$", - ("ipv4", "address", "netmask", "broadcast"), - ), - ( - r"^inet6 ([0-9a-z:]+)\s+prefixlen ([0-9]+)", - ("ipv6", "address", "mask_bits"), - ), -] - -iproute2_regexes = [ - ( - r"^inet ([0-9\.]+)\/([0-9]{1,2})(?:\s+brd ([0-9\.]+))?", - ("ipv4", "address", "mask_bits", "broadcast"), - ), - ( - r"^inet6 ([0-9a-z:]+)\/([0-9]{1,3})", - ("ipv6", "address", "mask_bits"), - ), -] - - -def _parse_regexes(regexes, lines): - data = { - "ipv4": {}, - "ipv6": {}, - } - - for line in lines: - for regex, groups in regexes: - matches = re.match(regex, line) - if matches: - ip_data = {} - - for i, group in enumerate(groups[1:]): - ip_data[group] = matches.group(i + 1) - - if "mask_bits" in ip_data: - ip_data["mask_bits"] = int(ip_data["mask_bits"]) - - target_group = data[groups[0]] - if target_group.get("address"): - target_group.setdefault("additional_ips", []).append(ip_data) - else: - target_group.update(ip_data) - - break - - return data - - class NetworkDevices(FactBase): """ Gets & returns a dict of network devices. See the ``ipv4_addresses`` and ``ipv6_addresses`` facts for easier-to-use shortcuts to get device addresses. .. code:: python - - { - "eth0": { - "ipv4": { - "address": "127.0.0.1", - "broadcast": "127.0.0.13", - # Only one of these will exist: - "netmask": "255.255.255.255", - "mask_bits": 32, - }, - "ipv6": { - "address": "fe80::a00:27ff:fec3:36f0", - "mask_bits": 64, - "additional_ips": [{ - "address": "fe80::", - "mask_bits": 128, - }], - } + "enp1s0": { + "ether": "12:34:56:78:9A:BC", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "192.168.1.100", + "mask_bits": 24, + "netmask": "255.255.255.0" }, + "ipv6": { + "address": "2001:db8:85a3::8a2e:370:7334", + "mask_bits": 64, + "additional_ips": [ + { + "address": "fe80::1234:5678:9abc:def0", + "mask_bits": 64 + } + ] + } + }, + "incusbr0": { + "ether": "DE:AD:BE:EF:CA:FE", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "10.0.0.1", + "mask_bits": 24, + "netmask": "255.255.255.0" + }, + "ipv6": { + "address": "fe80::dead:beef:cafe:babe", + "mask_bits": 64, + "additional_ips": [ + { + "address": "2001:db8:1234:5678::1", + "mask_bits": 64 + } + ] + } + }, + "lo": { + "mtu": 65536, + "state": "UP", + "ipv6": { + "address": "::1", + "mask_bits": 128 + } + }, + "veth98806fd6": { + "ether": "AA:BB:CC:DD:EE:FF", + "mtu": 1500, + "state": "UP" + }, + "vethda29df81": { + "ether": "11:22:33:44:55:66", + "mtu": 1500, + "state": "UP" + }, + "wlo1": { + "ether": "77:88:99:AA:BB:CC", + "mtu": 1500, + "state": "UNKNOWN" } """ - command = "ip addr show 2> /dev/null || ifconfig" + command = "ip addr show 2> /dev/null || ifconfig -a" default = dict # Definition of valid interface names for Linux: # https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/core/dev.c?h=v5.1.3#n1020 - _start_regexes = [ - ( - r"^([^/: \s]+)\s+Link encap:", - lambda lines: _parse_regexes(nettools_1_regexes, lines), - ), - ( - r"^([^/: \s]+): flags=", - lambda lines: _parse_regexes(nettools_2_regexes, lines), - ), - ( - r"^[0-9]+: ([^/: \s]+): ", - lambda lines: _parse_regexes(iproute2_regexes, lines), - ), - ] - def process(self, output): - devices = {} - - # Store current matches (start lines), the handler and any lines - matches = None - handler = None - line_buffer = [] + def mask(value): + try: + if value.startswith("0x"): + mask_bits = bin(int(value, 16)).count("1") + else: + mask_bits = int(value) + netmask = ".".join( + str((0xFFFFFFFF << (32 - b) >> mask_bits) & 0xFF) for b in (24, 16, 8, 0) + ) + except ValueError: + mask_bits = sum(bin(int(x)).count("1") for x in value.split(".")) + netmask = value - for line in output: - line = line.strip() + return mask_bits, netmask - matched = False + # Strip lines and merge them as a block of text + output = "\n".join(map(str.strip, output)) - # Look for start lines - for regex, new_handler in self._start_regexes: - new_matches = re.match(regex, line) + # Splitting the output into sections per network device + device_sections = re.split(r"\n(?=\d+: \w|\w+:.*mtu.*)", output) - # If we find a start line - if new_matches: - matched = True + # Dictionary to hold all device information + all_devices = {} - # Assign any current matches with current handler, reset buffer - if matches: - devices[matches.group(1)] = handler(line_buffer) - line_buffer = [] + for section in device_sections: + # Extracting the device name + device_name_match = re.match(r"^(?:\d+: )?([\w@]+):", section) + if not device_name_match: + continue + device_name = device_name_match.group(1) + + # Regular expressions to match different parts of the output + ether_re = re.compile(r"([0-9A-Fa-f:]{17})") + mtu_re = re.compile(r"mtu (\d+)") + ipv4_re = ( + re.compile( + r"inet (\d+\.\d+\.\d+\.\d+)/(\d+)(?: brd (\d+\.\d+\.\d+\.\d+))" + ), # ip a output, + re.compile( + r"inet (\d+\.\d+\.\d+\.\d+)\s+netmask\s+((?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(\d+\.\d+\.\d+\.\d+))" # noqa: E501 + ), # ifconfig -a output + ) + + # Parsing the output + ether = ether_re.search(section) + mtu = mtu_re.search(section) + + # Building the result dictionary for the device + device_info = {} + if ether: + device_info["ether"] = ether.group(1) + if mtu: + device_info["mtu"] = int(mtu.group(1)) + + device_info["state"] = ( + "UP" if "UP" in section else "DOWN" if "DOWN" in section else "UNKNOWN" + ) + + # IPv4 Addresses + for ipv4_re_ in ipv4_re: + ipv4_matches = ipv4_re_.findall(section) + if ipv4_matches: + break - # Set new matches/handler - matches = new_matches - handler = new_handler + if ipv4_matches: + ipv4_info = [] + for ipv4 in ipv4_matches: + address = ipv4[0] + mask_value = ipv4[1] + mask_bits, netmask = mask(mask_value) + broadcast = ipv4[2] if len(ipv4) == 3 else None + + ipv4_info.append( + { + "address": address, + "mask_bits": mask_bits, + "netmask": netmask, + "broadcast": broadcast, + }, + ) + device_info["ipv4"] = ipv4_info[0] + if len(ipv4_matches) > 1: + device_info["ipv4"]["additional_ips"] = ipv4_info[1:] + + # IPv6 Addresses + ipv6_re = ( + re.compile(r"inet6\s+([0-9a-fA-F:]+)/(\d+)"), + re.compile(r"inet6\s+([0-9a-fA-F:]+)\s+prefixlen\s+(\d+)"), + ) + + for ipv6_re_ in ipv6_re: + ipv6_matches = ipv6_re_.findall(section) + if ipv6_matches: break - if not matched: - line_buffer.append(line) + if ipv6_matches: + ipv6_info = [] + for ipv6 in ipv6_matches: + address = ipv6[0] + mask_bits = ipv6[1] or ipv6[2] + ipv6_info.append({"address": address, "mask_bits": int(mask_bits)}) + device_info["ipv6"] = ipv6_info[0] + if len(ipv6_matches) > 1: + device_info["ipv6"]["additional_ips"] = ipv6_info[1:] - # Handle any left over matches - if matches: - devices[matches.group(1)] = handler(line_buffer) + all_devices[device_name] = device_info - return devices + return all_devices class Ipv4Addrs(ShortFactBase): @@ -273,7 +308,7 @@ def process_data(self, data): ips = [] ip_details = details.get(self.ip_type) - if not ip_details: + if not ip_details or not ip_details.get("address"): continue ips.append(ip_details["address"]) @@ -332,7 +367,7 @@ def process_data(self, data): for interface, details in data.items(): ip_details = details.get(self.ip_type) - if not ip_details: + if not ip_details or not ip_details.get("address"): continue # pragma: no cover addresses[interface] = ip_details["address"] diff --git a/pyinfra/facts/server.py b/pyinfra/facts/server.py index c087aad85..fc91a8e59 100644 --- a/pyinfra/facts/server.py +++ b/pyinfra/facts/server.py @@ -417,6 +417,7 @@ class Users(FactBase): "uid": user_id, "gid": main_user_group_id, "lastlog": last_login_time, + "password": encrypted_password, }, } """ @@ -426,7 +427,8 @@ class Users(FactBase): ENTRY=`grep ^$i: /etc/passwd`; LASTLOG_RAW=`(lastlog -u $i 2> /dev/null || lastlogin $i 2> /dev/null)`; LASTLOG=`echo $LASTLOG_RAW | grep ^$i | tr -s ' '`; - echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG"; + PASSWORD=`grep ^$i: /etc/shadow | cut -d: -f2`; + echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG|$PASSWORD"; done """.strip() @@ -437,7 +439,7 @@ def process(self, output): rex = r"[A-Z][a-z]{2} [A-Z][a-z]{2} {1,2}\d+ .+$" for line in output: - entry, group, user_groups, lastlog = line.split("|") + entry, group, user_groups, lastlog, password = line.split("|") if entry: # Parse out the comment/home/shell @@ -470,6 +472,7 @@ def process(self, output): "gid": int(entries[3]), "lastlog": raw_login_time, "login_time": login_time, + "password": password, } return users @@ -677,3 +680,85 @@ def process(self, output): # replace utf8 with UTF-8 to match names in /etc/locale.gen # return a list of enabled locales return [line.replace("utf8", "UTF-8") for line in output] + + +class SecurityLimits(FactBase): + """ + Returns a list of security limits on the target host. + + .. code:: python + + [ + { + "domain": "*", + "limit_type": "soft", + "item": "nofile", + "value": "1048576" + }, + { + "domain": "*", + "limit_type": "hard", + "item": "nofile", + "value": "1048576" + }, + { + "domain": "root", + "limit_type": "soft", + "item": "nofile", + "value": "1048576" + }, + { + "domain": "root", + "limit_type": "hard", + "item": "nofile", + "value": "1048576" + }, + { + "domain": "*", + "limit_type": "soft", + "item": "memlock", + "value": "unlimited" + }, + { + "domain": "*", + "limit_type": "hard", + "item": "memlock", + "value": "unlimited" + }, + { + "domain": "root", + "limit_type": "soft", + "item": "memlock", + "value": "unlimited" + }, + { + "domain": "root", + "limit_type": "hard", + "item": "memlock", + "value": "unlimited" + } + ] + """ + + command = "cat /etc/security/limits.conf" + default = list + + def process(self, output): + limits = [] + + for line in output: + if line.startswith("#") or not len(line.strip()): + continue + + domain, limit_type, item, value = line.split() + + limits.append( + { + "domain": domain, + "limit_type": limit_type, + "item": item, + "value": value, + }, + ) + + return limits diff --git a/pyinfra/operations/server.py b/pyinfra/operations/server.py index e648ba29c..d51f2ee63 100644 --- a/pyinfra/operations/server.py +++ b/pyinfra/operations/server.py @@ -99,6 +99,12 @@ def wait_and_reconnect(state, host): # pragma: no cover yield FunctionCommand(wait_and_reconnect, (), {}) + # On certain systems sudo files are lost on reboot + def clean_sudo_info(state, host): + host.connector_data["sudo_askpass_path"] = None + + yield FunctionCommand(clean_sudo_info, (), {}) + @operation(is_idempotent=False) def wait(port: int): @@ -947,6 +953,7 @@ def user( comment=None, add_deploy_dir=True, unique=True, + password=None, ): """ Add/remove/update system users & their ssh `authorized_keys`. @@ -966,6 +973,7 @@ def user( + comment: the user GECOS comment + add_deploy_dir: any public_key filenames are relative to the deploy directory + unique: prevent creating users with duplicate UID + + password: set the encrypted password for the user Home directory: When ``ensure_home`` or ``public_keys`` are provided, ``home`` defaults to @@ -1064,6 +1072,9 @@ def user( if create_home: args.append("-m") + if password: + args.append("-p '{0}'".format(password)) + # Users are often added by other operations (package installs), so check # for the user at runtime before adding. @@ -1087,9 +1098,10 @@ def user( "shell": shell, "group": group, "groups": groups, + "password": password, } - # User exists and we want them, check home/shell/keys + # User exists and we want them, check home/shell/keys/password else: args = [] @@ -1112,6 +1124,9 @@ def user( if comment and existing_user["comment"] != comment: args.append("-c '{0}'".format(comment)) + if password and existing_user["password"] != password: + args.append("-p '{0}'".format(password)) + # Need to mod the user? if args: if os_type == "FreeBSD": @@ -1128,6 +1143,8 @@ def user( existing_user["group"] = group if groups: existing_user["groups"] = groups + if password: + existing_user["password"] = password # Ensure home directory ownership if ensure_home: @@ -1219,3 +1236,40 @@ def locale( ) yield "locale-gen" + + +@operation +def security_limit( + domain, + limit_type, + item, + value, +): + """ + Edit /etc/security/limits.conf configuration. + + + domain: the domain (user, group, or wildcard) for the limit + + limit_type: the type of limit (hard or soft) + + item: the item to limit (e.g., nofile, nproc) + + value: the value for the limit + + **Example:** + + .. code:: python + + security_limit( + name="Set nofile limit for all users", + domain='*', + limit_type='soft', + item='nofile', + value='1024', + ) + """ + + line_format = f"{domain}\t{limit_type}\t{item}\t{value}" + + yield from files.line( + path="/etc/security/limits.conf", + line=f"^{domain}[[:space:]]+{limit_type}[[:space:]]+{item}", + replace=line_format, + ) diff --git a/setup.py b/setup.py index 48f0853b5..44ab3e37a 100644 --- a/setup.py +++ b/setup.py @@ -20,9 +20,8 @@ INSTALL_REQUIRES = ( "gevent>=1.5", - "paramiko>=2.7,<3", # 2.7 (2019) adds OpenSSH key format + Match SSH config + "paramiko>=2.7,<4", # 2.7 (2019) adds OpenSSH key format + Match SSH config "click>2", - "colorama<1", # Windows color support for click "jinja2>2,<4", "python-dateutil>2,<3", "setuptools", diff --git a/tests/facts/hardware.Ipv4Addresses/linux_ip.json b/tests/facts/hardware.Ipv4Addresses/linux_ip.json index 7bec41065..a6d24c413 100644 --- a/tests/facts/hardware.Ipv4Addresses/linux_ip.json +++ b/tests/facts/hardware.Ipv4Addresses/linux_ip.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000", "link/ether 08:00:27:6d:95:c4 brd ff:ff:ff:ff:ff:ff", diff --git a/tests/facts/hardware.Ipv4Addrs/linux_ip_multiple.json b/tests/facts/hardware.Ipv4Addrs/linux_ip_multiple.json index c830e30fd..32f3583c6 100644 --- a/tests/facts/hardware.Ipv4Addrs/linux_ip_multiple.json +++ b/tests/facts/hardware.Ipv4Addrs/linux_ip_multiple.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000", "link/ether c6:c5:60:cd:73:f1 brd ff:ff:ff:ff:ff:ff", diff --git a/tests/facts/hardware.Ipv6Addresses/linux_ip.json b/tests/facts/hardware.Ipv6Addresses/linux_ip.json index cff0191be..6d75ed496 100644 --- a/tests/facts/hardware.Ipv6Addresses/linux_ip.json +++ b/tests/facts/hardware.Ipv6Addresses/linux_ip.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000", "link/ether 08:00:27:6d:95:c4 brd ff:ff:ff:ff:ff:ff", diff --git a/tests/facts/hardware.Ipv6Addrs/linux_ip_multiple.json b/tests/facts/hardware.Ipv6Addrs/linux_ip_multiple.json index 8751c79c6..55e31908d 100644 --- a/tests/facts/hardware.Ipv6Addrs/linux_ip_multiple.json +++ b/tests/facts/hardware.Ipv6Addrs/linux_ip_multiple.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000", "link/ether c6:c5:60:cd:73:f1 brd ff:ff:ff:ff:ff:ff", diff --git a/tests/facts/hardware.NetworkDevices/linux_ifconfig.json b/tests/facts/hardware.NetworkDevices/linux_ifconfig.json index 83c426887..4638b614d 100644 --- a/tests/facts/hardware.NetworkDevices/linux_ifconfig.json +++ b/tests/facts/hardware.NetworkDevices/linux_ifconfig.json @@ -1,32 +1,83 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ - "eth0 Link encap:Ethernet HWaddr 14:DD:A9:D3:36:0B", - "inet addr:1.2.3.4 Bcast:1.2.3.4 Mask:255.255.255.255", - "inet6 addr: 2a01::01/64 Scope:Global", - "UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1", - "RX packets:5088726642662 errors:0 dropped:0 overruns:75824296 frame:0", - "TX packets:204488770319 errors:0 dropped:0 overruns:0 carrier:0", - "collisions:0 txqueuelen:1000", - "RX bytes:1110992903295696 (1010.4 TiB) TX bytes:68385443441652 (62.1 TiB)", - "Memory:fa100000-fa17ffff", - "eth1 Link encap:Ethernet HWaddr 14:DD:A9:D3:36:0B" + "enp1s0: flags=4163 mtu 1500", + " inet 192.168.123.111 netmask 255.255.255.0 broadcast 192.168.123.255", + " inet6 2a01:e0a:5c2:7450:b241:6fff:fe0a:cf22 prefixlen 64 scopeid 0x0", + " inet6 fe80::b241:6fff:fe0a:cf22 prefixlen 64 scopeid 0x20", + " ether b0:41:6f:0a:cf:22 txqueuelen 1000 (Ethernet)", + " RX packets 52490 bytes 16827945 (16.0 MiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 21304 bytes 2948965 (2.8 MiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "incusbr0: flags=4163 mtu 1500", + " inet 10.232.181.1 netmask 255.255.255.0 broadcast 0.0.0.0", + " inet6 fe80::216:3eff:fe9c:8200 prefixlen 64 scopeid 0x20", + " inet6 fd42:5bce:bd73:eda6::1 prefixlen 64 scopeid 0x0", + " ether 00:16:3e:9c:82:00 txqueuelen 1000 (Ethernet)", + " RX packets 2592 bytes 253292 (247.3 KiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 1289 bytes 185014 (180.6 KiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0", + "lo: flags=73 mtu 65536", + " inet 127.0.0.1 netmask 255.0.0.0", + " inet6 ::1 prefixlen 128 scopeid 0x10", + " loop txqueuelen 1000 (Local Loopback)", + " RX packets 34 bytes 2816 (2.7 KiB)", + " RX errors 0 dropped 0 overruns 0 frame 0", + " TX packets 34 bytes 2816 (2.7 KiB)", + " TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0" ], "fact": { - "eth0": { + "enp1s0": { + "ether": "2a01:e0a:5c2:7450", + "mtu": 1500, + "state": "UP", "ipv4": { - "address": "1.2.3.4", - "broadcast": "1.2.3.4", - "netmask": "255.255.255.255" + "address": "192.168.123.111", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "192.168.123.255" }, "ipv6": { - "address": "2a01::01", - "mask_bits": 64 + "address": "2a01:e0a:5c2:7450:b241:6fff:fe0a:cf22", + "mask_bits": 64, + "additional_ips": [ + { + "address": "fe80::b241:6fff:fe0a:cf22", + "mask_bits": 64 + } + ] } }, - "eth1": { - "ipv4": {}, - "ipv6": {} + "incusbr0": { + "ether": "fe80::216:3eff:fe", + "mtu": 1500, + "state": "UP", + "ipv4": { + "address": "10.232.181.1", + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "0.0.0.0" + }, + "ipv6": { + "address": "fe80::216:3eff:fe9c:8200", + "mask_bits": 64, + "additional_ips": [ + { + "address": "fd42:5bce:bd73:eda6::1", + "mask_bits": 64 + } + ] + } + }, + "lo": { + "mtu": 65536, + "state": "UP", + "ipv6": { + "address": "::1", + "mask_bits": 128 + } } } } diff --git a/tests/facts/hardware.NetworkDevices/linux_ip.json b/tests/facts/hardware.NetworkDevices/linux_ip.json index 879670fa6..90d0cf69a 100644 --- a/tests/facts/hardware.NetworkDevices/linux_ip.json +++ b/tests/facts/hardware.NetworkDevices/linux_ip.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000", "link/ether 08:00:27:6d:95:c4 brd ff:ff:ff:ff:ff:ff", @@ -10,10 +10,14 @@ ], "fact": { "eth0": { + "ether": "08:00:27:6d:95:c4", + "mtu": 1500, + "state": "UP", "ipv4": { "address": "1.2.3.4", - "broadcast": "1.2.3.4", - "mask_bits": 24 + "mask_bits": 24, + "netmask": "255.255.255.0", + "broadcast": "1.2.3.4" }, "ipv6": { "address": "2a01::01", diff --git a/tests/facts/hardware.NetworkDevices/linux_ip_multiple.json b/tests/facts/hardware.NetworkDevices/linux_ip_multiple.json index cb13d872c..e66578f01 100644 --- a/tests/facts/hardware.NetworkDevices/linux_ip_multiple.json +++ b/tests/facts/hardware.NetworkDevices/linux_ip_multiple.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000", "link/ether c6:c5:60:cd:73:f1 brd ff:ff:ff:ff:ff:ff", @@ -12,14 +12,19 @@ ], "fact": { "eth0": { + "ether": "c6:c5:60:cd:73:f1", + "mtu": 1500, + "state": "UP", "ipv4": { "address": "166.22.203.666", - "broadcast": "165.22.207.255", "mask_bits": 20, + "netmask": "255.255.240.0", + "broadcast": "165.22.207.255", "additional_ips": [{ "address": "10.18.0.10", - "broadcast": "10.18.255.255", - "mask_bits": 16 + "mask_bits": 16, + "netmask": "255.255.0.0", + "broadcast": "10.18.255.255" }] }, "ipv6": { diff --git a/tests/facts/hardware.NetworkDevices/macos_ifconfig.json b/tests/facts/hardware.NetworkDevices/macos_ifconfig.json index d1dd82336..4b971fee5 100644 --- a/tests/facts/hardware.NetworkDevices/macos_ifconfig.json +++ b/tests/facts/hardware.NetworkDevices/macos_ifconfig.json @@ -1,5 +1,5 @@ { - "command": "ip addr show 2> /dev/null || ifconfig", + "command": "ip addr show 2> /dev/null || ifconfig -a", "output": [ "en0: flags=8863 mtu 1500", "inet 1.2.3.4 netmask 0xffffff00 broadcast 255.255.255.255", @@ -10,9 +10,12 @@ ], "fact": { "en0": { + "mtu": 1500, + "state": "UP", "ipv4": { "address": "1.2.3.4", - "netmask": "0xffffff00", + "mask_bits": 24, + "netmask": "255.255.255.0", "broadcast": "255.255.255.255" }, "ipv6": { diff --git a/tests/facts/server.SecurityLimits/security_limits.json b/tests/facts/server.SecurityLimits/security_limits.json new file mode 100644 index 000000000..d3c594594 --- /dev/null +++ b/tests/facts/server.SecurityLimits/security_limits.json @@ -0,0 +1,74 @@ +{ + "command": "cat /etc/security/limits.conf", + "output": [ + "# ", + "#* soft core ", + "#root hard core 10000", + "#* hard rss 1000", + "#@student hard nproc 2", + "#@faculty soft nproc 2", + "#@faculty hard nproc 5", + "#ftp hard nproc ", + "#ftp - chroot /ft", + "#@student - maxlogins ", + "* soft nofile 104857", + "* hard nofile 104857", + "root soft nofile 104857", + "root hard nofile 104857", + "* soft memlock unlimite", + "* hard memlock unlimite", + "root soft memlock unlimite", + "root hard memlock unlimite" + ], + "fact": [ + { + "domain": "*", + "limit_type": "soft", + "item": "nofile", + "value": "104857" + }, + { + "domain": "*", + "limit_type": "hard", + "item": "nofile", + "value": "104857" + }, + { + "domain": "root", + "limit_type": "soft", + "item": "nofile", + "value": "104857" + }, + { + "domain": "root", + "limit_type": "hard", + "item": "nofile", + "value": "104857" + }, + { + "domain": "*", + "limit_type": "soft", + "item": "memlock", + "value": "unlimite" + }, + { + "domain": "*", + "limit_type": "hard", + "item": "memlock", + "value": "unlimite" + }, + { + "domain": "root", + "limit_type": "soft", + "item": "memlock", + "value": "unlimite" + }, + { + "domain": "root", + "limit_type": "hard", + "item": "memlock", + "value": "unlimite" + } + ] +} + diff --git a/tests/facts/server.Users/mixed.json b/tests/facts/server.Users/mixed.json index 5dce7da4f..a156cc82b 100644 --- a/tests/facts/server.Users/mixed.json +++ b/tests/facts/server.Users/mixed.json @@ -1,14 +1,14 @@ { "output": [ - "root:x:0:0:root:/root:/bin/ks|wheel|wheel kmem sys tty operator staff guest|root pts/0 Sat Jun 5 12:03:23 -0600 2021", - "_tesTy.test:x:1004:1004::/home/_tesTy.test:/bin/ksh|_tesTy.test|_tesTy.test pyinfra|_tesTy.test pts/0 host-ip Sat Jun 12 13:43:42 -0600 2021", - "test.testy:x:1003:1003:Testy,,,:/home/test.testy:/bin/ksh|test.testy||test.testy Fri Jun 11 22:26:04 -0600 2021", - "noshell:x:1002:1002:noshell comment with spaces:/home/noshell:|noshell||noshell **Never logged in**", - "nohome:x:1002:1002:nohome comment::/bin/bash|nohome||**Never logged in**", - "freebsd:*:1001:1001:FreeBSD:/home/freebsd:/bin/sh|freebsd|freebsd|freebsd pts/0 10.1.10.10 Thu Aug 31 05:37:58 2023", - "freebsdnologin:*:1001:1001:FreeBSD NoLogin:/home/freebsdnologin:/bin/sh|freebsdnologin|freebsdnologin|" + "root:x:0:0:root:/root:/bin/ks|wheel|wheel kmem sys tty operator staff guest|root pts/0 Sat Jun 5 12:03:23 -0600 2021|$rootpw$", + "_tesTy.test:x:1004:1004::/home/_tesTy.test:/bin/ksh|_tesTy.test|_tesTy.test pyinfra|_tesTy.test pts/0 host-ip Sat Jun 12 13:43:42 -0600 2021|$testpw$", + "test.testy:x:1003:1003:Testy,,,:/home/test.testy:/bin/ksh|test.testy||test.testy Fri Jun 11 22:26:04 -0600 2021|$testpw$", + "noshell:x:1002:1002:noshell comment with spaces:/home/noshell:|noshell||noshell **Never logged in**|", + "nohome:x:1002:1002:nohome comment::/bin/bash|nohome||**Never logged in**|", + "freebsd:*:1001:1001:FreeBSD:/home/freebsd:/bin/sh|freebsd|freebsd|freebsd pts/0 10.1.10.10 Thu Aug 31 05:37:58 2023|$bsdpw$", + "freebsdnologin:*:1001:1001:FreeBSD NoLogin:/home/freebsdnologin:/bin/sh|freebsdnologin|freebsdnologin||" ], - "command": "for i in `cat /etc/passwd | cut -d: -f1`; do\n ENTRY=`grep ^$i: /etc/passwd`;\n LASTLOG_RAW=`(lastlog -u $i 2> /dev/null || lastlogin $i 2> /dev/null)`;\n LASTLOG=`echo $LASTLOG_RAW | grep ^$i | tr -s ' '`;\n echo \"$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG\";\n done", + "command": "for i in `cat /etc/passwd | cut -d: -f1`; do\n ENTRY=`grep ^$i: /etc/passwd`;\n LASTLOG_RAW=`(lastlog -u $i 2> /dev/null || lastlogin $i 2> /dev/null)`;\n LASTLOG=`echo $LASTLOG_RAW | grep ^$i | tr -s ' '`;\n PASSWORD=`grep ^$i: /etc/shadow | cut -d: -f2`;\n echo \"$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG|$PASSWORD\";\n done", "fact": { "root": { "home": "/root", @@ -26,7 +26,8 @@ "uid": 0, "gid": 0, "lastlog": "Sat Jun 5 12:03:23 -0600 2021", - "login_time": "2021-06-05T12:03:23-06:00" + "login_time": "2021-06-05T12:03:23-06:00", + "password": "$rootpw$" }, "_tesTy.test": { "home": "/home/_tesTy.test", @@ -39,7 +40,8 @@ "uid": 1004, "gid": 1004, "lastlog": "Sat Jun 12 13:43:42 -0600 2021", - "login_time": "2021-06-12T13:43:42-06:00" + "login_time": "2021-06-12T13:43:42-06:00", + "password": "$testpw$" }, "test.testy": { "home": "/home/test.testy", @@ -50,7 +52,8 @@ "uid": 1003, "gid": 1003, "lastlog": "Fri Jun 11 22:26:04 -0600 2021", - "login_time": "2021-06-11T22:26:04-06:00" + "login_time": "2021-06-11T22:26:04-06:00", + "password": "$testpw$" }, "noshell": { "home": "/home/noshell", @@ -61,7 +64,8 @@ "uid": 1002, "gid": 1002, "lastlog": null, - "login_time": null + "login_time": null, + "password": "" }, "nohome": { "home": null, @@ -72,7 +76,8 @@ "uid": 1002, "gid": 1002, "lastlog": null, - "login_time": null + "login_time": null, + "password": "" }, "freebsd": { "home": "/home/freebsd", @@ -83,7 +88,8 @@ "uid": 1001, "gid": 1001, "lastlog": "Thu Aug 31 05:37:58 2023", - "login_time": "2023-08-31T05:37:58" + "login_time": "2023-08-31T05:37:58", + "password": "$bsdpw$" }, "freebsdnologin": { "home": "/home/freebsdnologin", @@ -94,7 +100,8 @@ "uid": 1001, "gid": 1001, "lastlog": null, - "login_time": null + "login_time": null, + "password": "" } } } \ No newline at end of file diff --git a/tests/operations/server.reboot/reboot.json b/tests/operations/server.reboot/reboot.json index 061a5798d..9b8f5b216 100644 --- a/tests/operations/server.reboot/reboot.json +++ b/tests/operations/server.reboot/reboot.json @@ -6,7 +6,8 @@ "command": "reboot", "success_exit_codes": [0, -1] }, - ["wait_and_reconnect", [], {}] + ["wait_and_reconnect", [], {}], + ["clean_sudo_info", [], {}] ], "idempotent": false } diff --git a/tests/operations/server.security_limit/set.json b/tests/operations/server.security_limit/set.json new file mode 100644 index 000000000..47ea96a11 --- /dev/null +++ b/tests/operations/server.security_limit/set.json @@ -0,0 +1,12 @@ +{ + "kwargs": { + "domain": "root", + "limit_type": "hard", + "item": "memlock", + "value": "unlimited" + }, + "facts": {"files.FindInFile": {}}, + "commands": [ + "if [ -f /etc/security/limits.conf ]; then ( grep '^root[[:space:]]+hard[[:space:]]+memlock.*$' /etc/security/limits.conf && sed -i.a-timestamp 's/^root[[:space:]]+hard[[:space:]]+memlock.*$/root\thard\tmemlock\tunlimited/' /etc/security/limits.conf && rm -f /etc/security/limits.conf.a-timestamp ) 2> /dev/null || echo 'root\thard\tmemlock\tunlimited' >> /etc/security/limits.conf ; else echo 'root\thard\tmemlock\tunlimited' >> /etc/security/limits.conf ; fi" + ] +} diff --git a/tests/operations/server.user/add.json b/tests/operations/server.user/add.json index f6e00c767..efae20195 100644 --- a/tests/operations/server.user/add.json +++ b/tests/operations/server.user/add.json @@ -8,7 +8,8 @@ "uid" : 1000, "system": true, "comment": "Full Name", - "create_home": true + "create_home": true, + "password": "$somecryptedpassword$" }, "facts": { "server.Os": "Linux", @@ -20,7 +21,7 @@ "server.Os": "Linux" }, "commands": [ - "grep '^someuser:' /etc/passwd || useradd -d homedir -s shellbin -g mygroup -G secondary_group,another -r --uid 1000 -c 'Full Name' -m someuser", + "grep '^someuser:' /etc/passwd || useradd -d homedir -s shellbin -g mygroup -G secondary_group,another -r --uid 1000 -c 'Full Name' -m -p '$somecryptedpassword$' someuser", "mkdir -p homedir", "chown someuser:mygroup homedir" ] diff --git a/tests/operations/server.user/edit.json b/tests/operations/server.user/edit.json index c4d21270e..7641d6f78 100644 --- a/tests/operations/server.user/edit.json +++ b/tests/operations/server.user/edit.json @@ -17,7 +17,8 @@ "group": "nowt", "groups": [ "group1", "group2" - ] + ], + "password": "$somepw$" } }, "files.Directory": { diff --git a/tests/test_connectors/test_sshuserclient.py b/tests/test_connectors/test_sshuserclient.py index a1b827296..3c3c17450 100644 --- a/tests/test_connectors/test_sshuserclient.py +++ b/tests/test_connectors/test_sshuserclient.py @@ -21,7 +21,7 @@ SSH_CONFIG_OTHER_FILE = """ Host 192.168.1.1 User "otheruser" - ProxyCommand None + # ProxyCommand None # Commented to get test passing with Paramiko > 3 ForwardAgent yes UserKnownHostsFile ~/.ssh/test3 """