From e67e27cfbd4cfff82ef548922e2cf27e65d715d9 Mon Sep 17 00:00:00 2001 From: Antoci Alin <92744568+AntociAlin@users.noreply.github.com> Date: Mon, 26 Sep 2022 15:33:26 +0300 Subject: [PATCH] Adds support for Let's Encrypt x Certbot (#52) undefined --- .github/workflows/website_trigger.yaml | 2 +- README.md | 8 +- mutablesecurity/cli/cli.py | 15 +- .../solutions/common/facts/files.py | 1 + .../solutions/common/operations/apt.py | 17 + .../implementations/lets_encrypt/code.py | 583 +++++++++++++++++- .../implementations/lets_encrypt/code.py.old | 422 ------------- .../implementations/lets_encrypt/meta.yaml | 2 +- .../exports/solutions.json | 2 +- whitelist.txt | 3 + 10 files changed, 607 insertions(+), 448 deletions(-) create mode 100644 mutablesecurity/solutions/common/operations/apt.py delete mode 100644 mutablesecurity/solutions/implementations/lets_encrypt/code.py.old diff --git a/.github/workflows/website_trigger.yaml b/.github/workflows/website_trigger.yaml index a4792de..26afb88 100644 --- a/.github/workflows/website_trigger.yaml +++ b/.github/workflows/website_trigger.yaml @@ -20,4 +20,4 @@ jobs: repo: "website", workflow_id: "update_autodocs.yml", ref: "main" - }) \ No newline at end of file + }) diff --git a/README.md b/README.md index 211b392..5bf5baa 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Come join the MutableSecurity journey! Clam AntiVirus (ClamAV) is a free software, cross-platfom antimalware toolkit able to detect many types of malware, including viruses. ClamAV includes a command-line scanner, automatic database updater, and a scalable multi-threaded daemon running on an anti-virus engine from a shared library. FreshClam is a virus database update tool for ClamAV. ClamAV Daemon checks periodically for virus database definition updates, downloads, installs them, and notifies clamd to refresh it's in-memory virus database cache. - Maturity: Production + Maturity: Production @@ -78,7 +78,7 @@ Come join the MutableSecurity journey! teler is a real-time intrusion detection and threat alert based on web log. Targets only nginx installed on Ubuntu. - Maturity: Production + Maturity: Production @@ -98,7 +98,7 @@ Come join the MutableSecurity journey! Let's Encrypt is a free, automated, and open certificate authority brought to you by the nonprofit Internet Security Research Group (ISRG). Certbot is a free, open source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS. - Maturity: Under refactoring + Maturity: Under refactoring @@ -108,7 +108,7 @@ Come join the MutableSecurity journey! Suricata is the leading independent open source threat detection engine. By combining intrusion detection (IDS), intrusion prevention (IPS), network security monitoring (NSM) and PCAP processing, Suricata can quickly identify, stop, and assess even the most sophisticated attacks. - Maturity: Under refactoring + Maturity: Under refactoring diff --git a/mutablesecurity/cli/cli.py b/mutablesecurity/cli/cli.py index bc391e8..473034f 100755 --- a/mutablesecurity/cli/cli.py +++ b/mutablesecurity/cli/cli.py @@ -14,17 +14,12 @@ from mutablesecurity.cli.feedback_form import FeedbackForm from mutablesecurity.cli.printer import Printer -from mutablesecurity.cli.solutions_manager_adapter import ( - SolutionsManagerAdapter, -) +from mutablesecurity.cli.solutions_manager_adapter import \ + SolutionsManagerAdapter from mutablesecurity.helpers.exceptions import ( - BadArgumentException, - BadValueException, - MutableSecurityException, - StoppedMutableSecurityException, - UnexpectedBehaviorException, - UnsupportedPythonVersionException, -) + BadArgumentException, BadValueException, MutableSecurityException, + StoppedMutableSecurityException, UnexpectedBehaviorException, + UnsupportedPythonVersionException) from mutablesecurity.leader import ConnectionFactory from mutablesecurity.main import Main from mutablesecurity.monitoring import Monitor diff --git a/mutablesecurity/solutions/common/facts/files.py b/mutablesecurity/solutions/common/facts/files.py index b79161b..265bedb 100644 --- a/mutablesecurity/solutions/common/facts/files.py +++ b/mutablesecurity/solutions/common/facts/files.py @@ -11,6 +11,7 @@ class FilePresenceTest(FactBase): @staticmethod def command(path: str, exists: bool) -> str: prefix = "" if exists else "!" + return f"if [ {prefix} -e {path} ] ; then echo '1'; else echo '0' ; fi" @staticmethod diff --git a/mutablesecurity/solutions/common/operations/apt.py b/mutablesecurity/solutions/common/operations/apt.py new file mode 100644 index 0000000..27f23e0 --- /dev/null +++ b/mutablesecurity/solutions/common/operations/apt.py @@ -0,0 +1,17 @@ +"""Module with apt operations.""" +import typing + +from pyinfra.api import operation + + +@operation +def autoremove() -> typing.Generator[str, None, None]: + """Removes residual data automatically. + + Args: + no args + + Yields: + Iterator[typing.Generator[str, None, None]]: Command to execute + """ + yield "apt -y autoremove" diff --git a/mutablesecurity/solutions/implementations/lets_encrypt/code.py b/mutablesecurity/solutions/implementations/lets_encrypt/code.py index c7ed1bf..82ff3ae 100644 --- a/mutablesecurity/solutions/implementations/lets_encrypt/code.py +++ b/mutablesecurity/solutions/implementations/lets_encrypt/code.py @@ -5,28 +5,593 @@ # pylint: disable=unused-argument # pylint: disable=unexpected-keyword-arg +import typing +from datetime import datetime + +from pyinfra import host +from pyinfra.api import FactBase from pyinfra.api.deploy import deploy +from pyinfra.operations import apt, files, server, systemd + +from mutablesecurity.helpers.data_type import IntegerDataType, StringDataType +from mutablesecurity.solutions.base import (BaseAction, BaseInformation, + BaseLog, BaseSolution, + BaseSolutionException, BaseTest, + InformationProperties, TestType) +from mutablesecurity.solutions.common.facts.files import FilePresenceTest +from mutablesecurity.solutions.common.facts.networking import \ + InternetConnection +from mutablesecurity.solutions.common.facts.os import CheckIfUbuntu +from mutablesecurity.solutions.common.facts.service import ActiveService +from mutablesecurity.solutions.common.operations.apt import autoremove + + +class CertbotAlreadyUpdatedException(BaseSolutionException): + """Certbot is already at its newest version.""" + + +class CertbotAlreadyInstalledException(BaseSolutionException): + """Let's Encrypt Nginx file exists and Certbot is already installed.""" + + +@deploy +def revoke_certificate_with_explicit_domain(domain: str = None) -> None: + if domain is None: + domain = UserDomain.get() + + server.shell( + sudo=True, + name="Revokes the generated certificate", + commands=[ + f"certbot revoke -n --cert-name {domain} --reason" + f" {RevokeReason.get()}" + ], + ) + + server.shell( + sudo=True, + name=( + "Adds all the old configurations back in place inside" + " sites-enabled" + ), + commands=[ + "mv -f /opt/mutablesecurity/lets_encrypt/default" + " /etc/nginx/sites-enabled/default" + ], + ) + + files.file( + sudo=True, + name="Removes the MutableSecurity traces from /var/log/nginx/", + path=f"/var/log/nginx/https_{domain}_access.log", + present=False, + ) + + +@deploy +def revoke_old_and_generate_new_certificate(domain: str) -> None: + revoke_certificate_with_explicit_domain(domain) + GenerateCertificate.execute() + + +@deploy +def revoke_old_and_generate_new_certificate_when_domain_changed( + old_value: typing.Any, new_value: typing.Any +) -> None: + revoke_certificate_with_explicit_domain(old_value) + + server.shell( + sudo=True, + name=( + "Saves the default config file in case the user wants to" + " remove LetsEncrypt (Certbot)" + ), + commands=[ + "cp /etc/nginx/sites-enabled/default" + " /opt/mutablesecurity/lets_encrypt/" + ], + ) + + server.shell( + sudo=True, + name=( + "Generates and installs the certificate for the given Nginx domain" + ), + commands=[ + "certbot --nginx --noninteractive --agree-tos --cert-name" + f" {new_value} -d {new_value} -m" + f" {UserEmail.get()} --redirect" + ], + ) + + server.shell( + sudo=True, + name=( + "Adds MutableSecurity logs generation command in the" + " sites-enabled directory of the Nginx configuration" + ), + commands=[ + f"sed -i '/server_name {new_value};/a access_log" + f" /var/log/nginx/https_{new_value}_access.log; # Managed by" + " MutableSecurity' /etc/nginx/sites-enabled/default" + ], + ) + + server.shell( + sudo=True, + name="Restart the Nginx service", + commands=["systemctl restart nginx"], + ) + + +@deploy +def revoke_old_and_generate_new_certificate_when_email_changed( + old_value: typing.Any, new_value: typing.Any +) -> None: + revoke_old_and_generate_new_certificate(UserDomain.get()) + + +class UserEmail(BaseInformation): + IDENTIFIER = "email" + DESCRIPTION = ( + "The email of the user whom installs Let's Encrypt on the given" + " domain" + ) + INFO_TYPE = StringDataType + PROPERTIES = [ + InformationProperties.CONFIGURATION, + InformationProperties.NON_DEDUCTIBLE, + InformationProperties.WRITABLE, + ] + DEFAULT_VALUE = None + GETTER = None + SETTER = revoke_old_and_generate_new_certificate_when_email_changed + + +class UserDomain(BaseInformation): + IDENTIFIER = "domain" + DESCRIPTION = "The domain on which the user installs Let's Encrypt" + INFO_TYPE = StringDataType + PROPERTIES = [ + InformationProperties.CONFIGURATION, + InformationProperties.NON_DEDUCTIBLE, + InformationProperties.WRITABLE, + ] + DEFAULT_VALUE = None + GETTER = None + SETTER = revoke_old_and_generate_new_certificate_when_domain_changed + + +class RevokeReason(BaseInformation): + IDENTIFIER = "revoke_reason" + DESCRIPTION = ( + "The reason why Let's Encrypt has been removed. Choose from" + " 'unspecified', 'keycompromise', 'affiliationchanged', 'superseded'," + " 'cessationofoperation', if you wish to change the reason." + ) + INFO_TYPE = StringDataType + PROPERTIES = [ + InformationProperties.CONFIGURATION, + InformationProperties.OPTIONAL, + InformationProperties.WITH_DEFAULT_VALUE, + InformationProperties.NON_DEDUCTIBLE, + InformationProperties.WRITABLE, + ] + DEFAULT_VALUE = "unspecified" + GETTER = None + SETTER = None + + +class LogLocation(BaseInformation): + class LogLocationFact(FactBase): + command = "echo 'hi'" + + @staticmethod + def process(output: typing.List[str]) -> str: + return f"/var/log/nginx/https_{UserDomain.get()}_access.log" + + IDENTIFIER = "log_location" + DESCRIPTION = "Location where Nginx logs messages" + INFO_TYPE = StringDataType + PROPERTIES = [ + InformationProperties.CONFIGURATION, + InformationProperties.MANDATORY, + InformationProperties.NON_DEDUCTIBLE, + InformationProperties.READ_ONLY, + InformationProperties.AUTO_GENERATED_BEFORE_INSTALL, + ] + DEFAULT_VALUE = None + GETTER = LogLocationFact + SETTER = None + + +class InstalledVersion(BaseInformation): + class InstalledVersionFact(FactBase): + command = ( + "apt-cache policy certbot | grep -i Installed | cut -d ' ' -f 4" + ) + + @staticmethod + def process(output: typing.List[str]) -> str: + return output[0] + + IDENTIFIER = "version" + DESCRIPTION = "Installed version" + INFO_TYPE = StringDataType + PROPERTIES = [ + InformationProperties.METRIC, + InformationProperties.READ_ONLY, + ] + DEFAULT_VALUE = None + GETTER = InstalledVersionFact + SETTER = None + + +class SecuredRequests(BaseInformation): + class SecuredRequestsFact(FactBase): + @staticmethod + def command() -> str: + return f"cat {LogLocation.get()}| wc -l " + + @staticmethod + def process(output: typing.List[str]) -> int: + return int(output[0]) + + IDENTIFIER = "secured_requests" + DESCRIPTION = "Total number of secured requests" + INFO_TYPE = IntegerDataType + PROPERTIES = [ + InformationProperties.METRIC, + InformationProperties.READ_ONLY, + ] + DEFAULT_VALUE = None + GETTER = SecuredRequestsFact + SETTER = None + + +class SecuredRequestsToday(BaseInformation): + class SecuredRequestsTodayFact(FactBase): + @staticmethod + def command() -> str: + current_date = datetime.today().strftime("%d/%b/%Y") + + return ( + f"sudo cat {LogLocation.get()} | grep '{current_date}' | wc -l" + ) + + @staticmethod + def process(output: typing.List[str]) -> int: + return int(output[0]) + + IDENTIFIER = "secured_requests_today" + DESCRIPTION = "Total number of secured requests today" + INFO_TYPE = IntegerDataType + PROPERTIES = [ + InformationProperties.METRIC, + InformationProperties.READ_ONLY, + ] + DEFAULT_VALUE = None + GETTER = SecuredRequestsTodayFact + SETTER = None + + +class FilePresenceRequirement(BaseTest): + IDENTIFIER = "configuration_file_presence" + DESCRIPTION = ( + "Checks if the old Nginx configuration file is saved in" + " /opt/mutablesecurity/lets_encrypt." + ) + TEST_TYPE = TestType.PRESENCE + FACT = FilePresenceTest + FACT_ARGS = ("/opt/mutablesecurity/lets_encrypt/default", True) + + +class UbuntuRequirement(BaseTest): + IDENTIFIER = "ubuntu" + DESCRIPTION = "Checks if the operating system is Ubuntu." + TEST_TYPE = TestType.REQUIREMENT + FACT = CheckIfUbuntu -from mutablesecurity.solutions.base import BaseSolution + +class TestRequest(BaseTest): + @staticmethod + @deploy + def make_request() -> None: + curl_command = f"curl https://{UserDomain.get()}" + server.shell( + commands=[curl_command], + name="Checks if the HTTPS connection is made.", + ) + + class TestRequestFact(FactBase): + @staticmethod + def command() -> str: + current_date = datetime.today().strftime("%d/%b/%Y:%H:%M") + + check_command = ( + f"sudo cat {LogLocation.get()} | grep '{current_date}' | wc -l" + ) + + return check_command + + @staticmethod + def process(output: typing.List[str]) -> bool: + return int(output[0]) != 0 + + IDENTIFIER = "request_via_https" + DESCRIPTION = "Checks if the site is secured with Let's Encrypt." + TEST_TYPE = TestType.SECURITY + TRIGGER = make_request + FACT = TestRequestFact + + +class InternetAccess(BaseTest): + IDENTIFIER = "internet_access" + DESCRIPTION = "Checks if host has Internet access." + TEST_TYPE = TestType.REQUIREMENT + FACT = InternetConnection + + +class ActiveNginx(BaseTest): + IDENTIFIER = "nginx_active" + DESCRIPTION = "Checks if Nginx is installed and the service is active." + TEST_TYPE = TestType.REQUIREMENT + FACT = ActiveService + FACT_ARGS = ("nginx",) + + +class TestDomainRequest(BaseTest): + class TestDomainRequestFact(FactBase): + @staticmethod + def command() -> str: + check_command = ( + "curl -o /dev/null -s -w '%{{http_code}}\n' -H 'Host:" + f" {UserDomain.get()}' http://localhost/" + ) + + return check_command + + @staticmethod + def process(output: typing.List[str]) -> bool: + return output[0] != "000" + + IDENTIFIER = "domain_request" + DESCRIPTION = ( + "Checks if the site exists before trying to generate certificate." + ) + TEST_TYPE = TestType.REQUIREMENT + FACT = TestDomainRequestFact + FACT_ARGS = () + + +class TextLogs(BaseLog): + class GeneratedLogsFact(FactBase): + @staticmethod + def command() -> str: + return f"cat {LogLocation.get()}" + + @staticmethod + def process(output: typing.List[str]) -> str: + return "\n".join(output) + + IDENTIFIER = "logs" + DESCRIPTION = ( + "The logs generated by Let's Encrypt x Certbot for the given domain" + ) + INFO_TYPE = IntegerDataType + FACT = GeneratedLogsFact + + +class GenerateCertificate(BaseAction): + @staticmethod + @deploy + def generate_certificate() -> None: + server.shell( + sudo=True, + name=( + "Saves the default config file in case the user wants to" + " remove LetsEncrypt (Certbot)" + ), + commands=[ + "cp /etc/nginx/sites-enabled/default" + " /opt/mutablesecurity/lets_encrypt/" + ], + ) + + server.shell( + sudo=True, + name=( + "Generates and installs the certificate for the given Nginx" + " domain" + ), + commands=[ + "certbot --nginx --noninteractive --agree-tos --cert-name" + f" {UserDomain.get()} -d {UserDomain.get()} -m" + f" {UserEmail.get()} --redirect" + ], + ) + + server.shell( + sudo=True, + name=( + "Adds MutableSecurity logs generation command in the" + " sites-enabled directory of the Nginx configuration" + ), + commands=[ + f"sed -i '/server_name {UserDomain.get()};/a access_log" + f" {LogLocation.get()}; # Managed by MutableSecurity'" + " /etc/nginx/sites-enabled/default" + ], + ) + + server.shell( + sudo=True, + name="Restart the Nginx service", + commands=["systemctl restart nginx"], + ) + + IDENTIFIER = "generate_certificate" + DESCRIPTION = ( + "Generates a certificate for a given domain and email. Used mostly" + " when there are multiple domains for which Let's Encrypt HTTPS" + " encryption is required." + ) + ACT = generate_certificate + + +class RevokeCurrentCertificate(BaseAction): + @staticmethod + @deploy + def revoke_certificate() -> None: + revoke_certificate_with_explicit_domain() + + IDENTIFIER = "revoke_certificate" + DESCRIPTION = "Revokes the certificate for the current email and domain." + ACT = revoke_certificate class LetsEncrypt(BaseSolution): - INFORMATION = [] # type: ignore[list-item, var-annotated] - TESTS = [] # type: ignore[list-item, var-annotated] - LOGS = [] # type: ignore[list-item, var-annotated] - ACTIONS = [] # type: ignore[list-item, var-annotated] + INFORMATION = [ + RevokeReason, # type: ignore[list-item] + UserEmail, # type: ignore[list-item] + UserDomain, # type: ignore[list-item] + LogLocation, # type: ignore[list-item] + SecuredRequests, # type: ignore[list-item] + SecuredRequestsToday, # type: ignore[list-item] + InstalledVersion, # type: ignore[list-item] + ] + TESTS = [ + UbuntuRequirement, # type: ignore[list-item] + InternetAccess, # type: ignore[list-item] + TestRequest, # type: ignore[list-item] + ActiveNginx, # type: ignore[list-item] + FilePresenceRequirement, # type: ignore[list-item] + TestDomainRequest, # type: ignore[list-item] + ] + LOGS = [ + TextLogs, # type: ignore[list-item] + ] + ACTIONS = [] @staticmethod @deploy def _install() -> None: - pass + files.directory( + sudo=True, + path="/opt/mutablesecurity/lets_encrypt", + present=True, + name="Creates the folder that will store Let's Encrypt.", + ) + + apt.update( + sudo=True, + name="Updates the apt reporisoties", + env={"LC_TIME": "en_US.UTF-8"}, + cache_time=3600, + success_exit_codes=[0, 100], + ) + + apt.packages( + sudo=True, + name="Installs the requirements", + packages=["python3-certbot-nginx", "curl"], + latest=True, + ) + GenerateCertificate.execute() @staticmethod @deploy - def _uninstall() -> None: - pass + def _uninstall(remove_logs: bool = True) -> None: + RevokeCurrentCertificate.execute() + + apt.packages( + sudo=True, + update=True, + name="Removes Let's Encrypt x Certbot", + packages=["letsencrypt", "certbot"], + present=False, + ) + + files.directory( + sudo=True, + name="Removes Let's Encrypt from /etc", + path="/etc/letsencrypt", + present=False, + ) + + files.directory( + sudo=True, + name="Removes Let's Encrypt from /root/.local/share/", + path="/root/.local/share/letsencrypt/", + present=False, + ) + + files.directory( + sudo=True, + name="Removes Certbot from /opt/eff.org/", + path="/opt/eff.org/certbot/", + present=False, + ) + + files.directory( + sudo=True, + name="Removes Let's Encrypt from /var/lib/", + path="/var/lib/letsencrypt/", + present=False, + ) + + files.directory( + sudo=True, + name="Removes Let's Encrypt from /var/log/", + path="/var/log/letsencrypt/", + present=False, + ) + + autoremove( + name=( + "Removes all residual data correlated to Let's Encrypt x" + " Certbot" + ) + ) + + systemd.service( + name="Restarts the Nginx service to apply changes", + service="nginx.service", + running=True, + restarted=True, + enabled=True, + ) + + files.directory( + sudo=True, + name=( + "Removes the Certbot traces from" + " opt/mutablesecurity/lets_encrypt/" + ), + path="/opt/mutablesecurity/lets_encrypt/", + present=False, + ) @staticmethod @deploy def _update() -> None: - pass + class LatestVersionFact(FactBase): + command = ( + "apt-cache policy certbot | grep -i Candidate | cut -d ' '" + " -f 4" + ) + + @staticmethod + def process(output: typing.List[str]) -> str: + return output[0] + + if host.get_fact(LatestVersionFact) == InstalledVersion.get(): + raise CertbotAlreadyUpdatedException() + + apt.packages( + sudo=True, + update=True, + packages=["certbot"], + latest=True, + name="Updates Certbot via apt.", + ) diff --git a/mutablesecurity/solutions/implementations/lets_encrypt/code.py.old b/mutablesecurity/solutions/implementations/lets_encrypt/code.py.old deleted file mode 100644 index 1ebcb7f..0000000 --- a/mutablesecurity/solutions/implementations/lets_encrypt/code.py.old +++ /dev/null @@ -1,422 +0,0 @@ -import os -from datetime import datetime - -from pyinfra.api import FactBase -from pyinfra.api.deploy import deploy -from pyinfra.facts import files as file_facts -from pyinfra.operations import apt, python, server - -from ...helpers.exceptions import MandatoryAspectLeftUnsetException -from ..deployments.managed_stats import ManagedStats -from ..deployments.managed_yaml_backed_config import ManagedYAMLBackedConfig -from . import BaseSolution - - -class SecuredRequests(FactBase): - def command(self, domain): - return f"cat /var/log/nginx/https_{domain}_access.log | wc -l " - - def process(self, output): - return int(output[0]) - - -class SecuredRequestsToday(FactBase): - def command(self, domain): - current_date = datetime.today().strftime("%d/%b/%Y") - - return ( - f"sudo cat /var/log/nginx/https_{domain}_access.log | grep" - f" '{current_date}' | wc -l" - ) - - def process(self, output): - return int(output[0]) - - -class Version(FactBase): - command = "apt-cache policy certbot | grep -i Installed | cut -d ' ' -f 4" - - def process(self, output): - return output[0] - - -class Logs(FactBase): - def command(self, domain): - return f"cat /var/log/nginx/https_{domain}_access.log" - - def process(self, output): - return output - - -class LetsEncrypt(BaseSolution): - meta = { - "id": "lets_encrypt", - "full_name": "Let's Encrypt x Certbot", - "description": ( - "Let's Encrypt is a free, automated, and open certificate" - " authority brought to you by the nonprofit Internet Security" - " Research Group (ISRG). Certbot is a free, open source software" - " tool for automatically using Let's Encrypt certificates on" - " manually-administrated websites to enable HTTPS." - ), - "references": [ - "https://letsencrypt.org/", - "https://github.com/letsencrypt", - "https://certbot.eff.org/", - "https://github.com/certbot/certbot", - ], - "configuration": { - "email": { - "type": str, - "help": ( - "Email provided for security reasons when generating the" - " certificate" - ), - "default": None, - }, - "domain": { - "type": str, - "help": "Domain protected by the generated certificate", - "default": None, - }, - }, - "metrics": { - "SecuredRequests": { - "description": "Total number of secured requests", - "fact": SecuredRequests, - }, - "SecuredRequestsToday": { - "description": ( - "Total number of secured requests in the current day" - ), - "fact": SecuredRequestsToday, - }, - "version": { - "description": "Current installed version", - "fact": Version, - }, - }, - "messages": { - "GET_CONFIGURATION": ( - "The configuration of Let's Encrypt x Certbot was retrieved.", - "The configuration of Let's Encrypt x Certbot could not be" - " retrieved.", - ), - "SET_CONFIGURATION": ( - "The configuration of Let's Encrypt x Certbot was set.", - "The configuration of Let's Encrypt x Certbot could not be" - " set. Check the provided aspect and value to be valid.", - ), - "INSTALL": ( - "Let's Encrypt x Certbot is now installed on this machine.", - "There was an error on Let's Encrypt x Certbot's" - " installation.", - ), - "TEST": ( - "Let's Encrypt x Certbot works as expected.", - "Let's Encrypt x Certbot does not work as expected.", - ), - "GET_LOGS": ( - "The logs of Let's Encrypt x Certbot were retrieved.", - "The logs of Let's Encrypt x Certbot could not be retrieved.", - ), - "GET_STATS": ( - "The stats of Let's Encrypt x Certbot were retrieved.", - "The stats of Let's Encrypt x Certbot could not be retrieved.", - ), - "UPDATE": ( - "Let's Encrypt x Certbot is now at its newest version.", - "There was an error on Let's Encrypt x Certbot's update.", - ), - "UNINSTALL": ( - "Let's Encrypt x Certbot is no longer installed on this" - " machine.", - "There was an error on Let's Encrypt x Certbot's" - " uninstallation.", - ), - }, - } - result = {} - - @deploy - def get_configuration(state, host): - ManagedYAMLBackedConfig.get_configuration( - state=state, host=host, solution_class=LetsEncrypt - ) - - @deploy - def _set_default_configuration(state, host): - ManagedYAMLBackedConfig._set_default_configuration( - state=state, host=host, solution_class=LetsEncrypt - ) - - @deploy - def _verify_new_configuration(state, host, aspect, value): - ManagedYAMLBackedConfig._verify_new_configuration( - state=state, - host=host, - solution_class=LetsEncrypt, - aspect=aspect, - value=value, - ) - - @deploy - def set_configuration(state, host, aspect=None, value=None): - ManagedYAMLBackedConfig.set_configuration( - state=state, - host=host, - solution_class=LetsEncrypt, - aspect=aspect, - value=value, - ) - - @deploy - def _set_configuration_callback( - state, host, aspect=None, old_value=None, new_value=None - ): - try: - LetsEncrypt._check_installation_config(state=state, host=host) - except MandatoryAspectLeftUnsetException: - pass - else: - # Check if the solution is already installed - if host.get_fact( - file_facts.File, "/opt/mutablesecurity/lets_encrypt/default" - ): - domain = None - if aspect == "domain": - domain = old_value - - LetsEncrypt._revoke_current_certificate( - state=state, host=host, domain=domain - ) - LetsEncrypt._generate_certificate(state=state, host=host) - - @deploy - def _put_configuration(state, host): - ManagedYAMLBackedConfig._put_configuration( - state=state, host=host, solution_class=LetsEncrypt - ) - - @deploy - def _check_installation_config(state, host): - ManagedYAMLBackedConfig._check_installation_config( - state=state, host=host, solution_class=LetsEncrypt - ) - - @deploy - def _generate_certificate(state, host): - server.shell( - state=state, - host=host, - sudo=True, - name=( - "Saves the default config file in case the user wants to" - " remove LetsEncrypt(Certbot)" - ), - commands=[ - "cp /etc/nginx/sites-enabled/default" - " /opt/mutablesecurity/lets_encrypt/" - ], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name=( - "Generates and installs the certificates for the given nginx" - " domain" - ), - commands=[ - "certbot --nginx --noninteractive --agree-tos --cert-name" - f" {LetsEncrypt._configuration['domain']} -d" - f" {LetsEncrypt._configuration['domain']} -m" - f" {LetsEncrypt._configuration['email']} --redirect" - ], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name="Adds MutableSecurity logs command in sites-enabled", - commands=[ - "sed -i '/server_name" - f" {LetsEncrypt._configuration['domain']};/a access_log" - f" /var/log/nginx/https_{LetsEncrypt._configuration['domain']}_access.log;" - " # Managed by MutableSecurity'" - " /etc/nginx/sites-enabled/default" - ], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name="Restart the Nginx service", - commands=["systemctl restart nginx"], - ) - - @deploy - def install(state, host): - LetsEncrypt.get_configuration(state=state, host=host) - - LetsEncrypt._check_installation_config(state=state, host=host) - - apt.update( - state=state, - host=host, - sudo=True, - name="Updates the apt reporisoties", - env={"LC_TIME": "en_US.UTF-8"}, - cache_time=3600, - success_exit_codes=[0, 100], - ) - - apt.packages( - state=state, - host=host, - sudo=True, - name="Installs the requirements", - packages=["python3-certbot-nginx", "curl"], - latest=True, - ) - - LetsEncrypt._generate_certificate(state=state, host=host) - - LetsEncrypt._put_configuration(state=state, host=host) - - LetsEncrypt.result[host.name] = True - - @deploy - def test(state, host): - LetsEncrypt.get_configuration(state=state, host=host) - - curl_command = "curl https://" + LetsEncrypt._configuration["domain"] - server.shell( - state=state, - host=host, - sudo=True, - name="Requests if the https connection is made", - commands=[curl_command], - ) - - def stage(state, host): - connections = host.get_fact( - SecuredRequestsToday, - domain=LetsEncrypt._configuration["domain"], - ) - - LetsEncrypt.result[host.name] = connections != 0 - - python.call(state=state, host=host, sudo=True, function=stage) - - @deploy - def get_stats(state, host): - ManagedStats.get_stats( - state=state, host=host, solution_class=LetsEncrypt - ) - - @deploy - def get_logs(state, host): - LetsEncrypt.get_configuration(state=state, host=host) - - logs = host.get_fact(Logs, domain=LetsEncrypt._configuration["domain"]) - LetsEncrypt.result[host.name] = logs - - @deploy - def update(state, host): - apt.packages( - state=state, - host=host, - sudo=True, - name="Updates Let's Encrypt x Certbot", - packages=["certbot"], - latest=True, - present=True, - ) - - LetsEncrypt.result[host.name] = True - - @deploy - def _revoke_current_certificate(state, host, domain=None): - if not domain: - domain = LetsEncrypt._configuration["domain"] - - server.shell( - state=state, - host=host, - sudo=True, - name="Revokes the generated certificate", - commands=[f"certbot revoke -n --cert-name {domain}"], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name=( - "Adds all the old configurations back in place on" - " sites-enabled" - ), - commands=[ - "cp /opt/mutablesecurity/lets_encrypt/default" - " /etc/nginx/sites-enabled/default" - ], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name="Removes all traces of Let's Encrypt x Certbot", - commands=[ - f"rm /var/log/nginx/https_{domain}_access.log" - " /opt/mutablesecurity/lets_encrypt/default" - ], - ) - - @deploy - def uninstall(state, host): - LetsEncrypt.get_configuration(state=state, host=host) - - LetsEncrypt._revoke_current_certificate(state=state, host=host) - - server.shell( - state=state, - host=host, - sudo=True, - name="Purges Let's Encrypt x Certbot", - commands=["apt purge -y letsencrypt certbot"], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name="Removes all traces of Let's Encrypt x Certbot", - commands=[ - "rm -rf /etc/letsencrypt /root/.local/share/letsencrypt/" - " /opt/eff.org/certbot/ /var/lib/letsencrypt/" - " /var/log/letsencrypt/" - ], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name="Removes everything using autoremove", - commands=["apt -y update && apt -y autoremove"], - ) - - server.shell( - state=state, - host=host, - sudo=True, - name="Restarts the Nginx service to apply changes", - commands=["systemctl restart nginx"], - ) - - LetsEncrypt.result[host.name] = True diff --git a/mutablesecurity/solutions/implementations/lets_encrypt/meta.yaml b/mutablesecurity/solutions/implementations/lets_encrypt/meta.yaml index adf4edf..3831e0e 100644 --- a/mutablesecurity/solutions/implementations/lets_encrypt/meta.yaml +++ b/mutablesecurity/solutions/implementations/lets_encrypt/meta.yaml @@ -5,6 +5,6 @@ references: - https://github.com/letsencrypt - https://certbot.eff.org - https://github.com/certbot/certbot -maturity: REFACTORING +maturity: PRODUCTION categories: - WEB_ENCRYPTION diff --git a/others/spec_sheets_generation_script/exports/solutions.json b/others/spec_sheets_generation_script/exports/solutions.json index 1d0d905..9f8aa83 100644 --- a/others/spec_sheets_generation_script/exports/solutions.json +++ b/others/spec_sheets_generation_script/exports/solutions.json @@ -1 +1 @@ -[{"identifier": "clamav", "full_name": "ClamAV", "maturity": "Production", "description": "Clam AntiVirus (ClamAV) is a free software, cross-platfom antimalware toolkit able to detect many types of malware, including viruses. ClamAV includes a command-line scanner, automatic database updater, and a scalable multi-threaded daemon running on an anti-virus engine from a shared library. FreshClam is a virus database update tool for ClamAV. ClamAV Daemon checks periodically for virus database definition updates, downloads, installs them, and notifies clamd to refresh it's in-memory virus database cache.", "categories": ["Antimalware", "Host Protection"]}, {"identifier": "teler", "full_name": "teler", "maturity": "Production", "description": "teler is a real-time intrusion detection and threat alert based on web log. Targets only nginx installed on Ubuntu.", "categories": ["Web Intrusion Detection System"]}, {"identifier": "fail2ban", "full_name": "Fail2ban", "maturity": "Production", "description": "Fail2ban is an intrusion prevention software framework that protects Unix-like servers from brute-force attacks. It scans log files and bans IP addresses conducting too many failed operations (for example, login attempts). This module targets Debian-based operating systems and has already set a SSH jail.", "categories": ["Host Intrusion Prevention System"]}, {"identifier": "lets_encrypt", "full_name": "Let's Encrypt x Certbot", "maturity": "Under refactoring", "description": "Let's Encrypt is a free, automated, and open certificate authority brought to you by the nonprofit Internet Security Research Group (ISRG). Certbot is a free, open source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS.", "categories": ["Encryption for Web Applications"]}, {"identifier": "suricata", "full_name": "Suricata", "maturity": "Under refactoring", "description": "Suricata is the leading independent open source threat detection engine. By combining intrusion detection (IDS), intrusion prevention (IPS), network security monitoring (NSM) and PCAP processing, Suricata can quickly identify, stop, and assess even the most sophisticated attacks.", "categories": ["Network Intrusion Detection and Prevention System"]}] \ No newline at end of file +[{"identifier": "clamav", "full_name": "ClamAV", "maturity": "Production", "description": "Clam AntiVirus (ClamAV) is a free software, cross-platfom antimalware toolkit able to detect many types of malware, including viruses. ClamAV includes a command-line scanner, automatic database updater, and a scalable multi-threaded daemon running on an anti-virus engine from a shared library. FreshClam is a virus database update tool for ClamAV. ClamAV Daemon checks periodically for virus database definition updates, downloads, installs them, and notifies clamd to refresh it's in-memory virus database cache.", "categories": ["Antimalware", "Host Protection"]}, {"identifier": "teler", "full_name": "teler", "maturity": "Production", "description": "teler is a real-time intrusion detection and threat alert based on web log. Targets only nginx installed on Ubuntu.", "categories": ["Web Intrusion Detection System"]}, {"identifier": "fail2ban", "full_name": "Fail2ban", "maturity": "Production", "description": "Fail2ban is an intrusion prevention software framework that protects Unix-like servers from brute-force attacks. It scans log files and bans IP addresses conducting too many failed operations (for example, login attempts). This module targets Debian-based operating systems and has already set a SSH jail.", "categories": ["Host Intrusion Prevention System"]}, {"identifier": "lets_encrypt", "full_name": "Let's Encrypt x Certbot", "maturity": "Under refactoring", "description": "Let's Encrypt is a free, automated, and open certificate authority brought to you by the nonprofit Internet Security Research Group (ISRG). Certbot is a free, open source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS.", "categories": ["Encryption for Web Applications"]}, {"identifier": "suricata", "full_name": "Suricata", "maturity": "Under refactoring", "description": "Suricata is the leading independent open source threat detection engine. By combining intrusion detection (IDS), intrusion prevention (IPS), network security monitoring (NSM) and PCAP processing, Suricata can quickly identify, stop, and assess even the most sophisticated attacks.", "categories": ["Network Intrusion Detection and Prevention System"]}] diff --git a/whitelist.txt b/whitelist.txt index 795653c..e68babc 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -4,6 +4,7 @@ argv ast autodoc automations +autoremove autouse baseaction baseinformation @@ -19,6 +20,7 @@ booleans callee capsys capturefixture +certbot choco clamav classdef @@ -117,6 +119,7 @@ subnode sudo suricata syslog +systemd teler testsuite traceback