ZiadMansourM / cheapbot

Certbot plugin to automate dns-01 challenge for Namecheap.
MIT License
2 stars 0 forks source link

Enable SSL #1

Open ZiadMansourM opened 1 year ago

ZiadMansourM commented 1 year ago

Question

"""
This module defines a certbot plugin to automate the process of completing a
`dns-01` challenge (`~acme.challenges.DNS01`) by creating, and subsequently
removing, TXT records using the Namecheap API.
"""

import logging

import zope.interface
from certbot import errors, interfaces
from certbot.plugins import dns_common, dns_common_lexicon
from lexicon.providers import namecheap
from requests.exceptions import RequestException
import tldextract

logger = logging.getLogger(__name__)

@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(dns_common.DNSAuthenticator):
    """DNS Authenticator for Namecheap

    This Authenticator uses the Namecheap API to fulfill a dns-01 challenge.
    """

    description = ('Obtain certificates using a DNS TXT record (if you are '
                   'using Namecheap for DNS).')

    def __init__(self, *args, **kwargs):
        super(Authenticator, self).__init__(*args, **kwargs)
        self.credentials = None

    @classmethod
    def add_parser_arguments(cls, add):
        super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30)
        add('credentials', help='Namecheap credentials INI file.')

    def more_info(self):
        return ('This plugin configures a DNS TXT record to respond to a '
                'dns-01 challenge using the Namecheap API.')

    def _setup_credentials(self):
        self.credentials = self._configure_credentials(
            'credentials',
            'namecheap credentials INI file',
            {
                'api_user': 'Namecheap API user',
                'api_key': 'Namecheap API key',
                'client_ip': 'Your client IP address',
                'username': 'Namecheap username',
            }
        )

    def _perform(self, domain, validation_name, validation):
        return self.namecheap_client.add_txt_record(domain, validation_name, validation)

    def _cleanup(self, domain, validation_name, validation):
        return self.namecheap_client.del_txt_record(domain, validation_name, validation)

    @property
    def namecheap_client(self):
        return _NamecheapLexiconClient(credentials=self.credentials.conf)

class _NamecheapLexiconClient(dns_common_lexicon.LexiconClient):
    """Encapsulates all communication with Namecheap via Lexicon."""

    def __init__(self, credentials):
        super(_NamecheapLexiconClient, self).__init__()
        config = dns_common_lexicon.build_lexicon_config('namecheap', {}, {
            'auth_username': credentials('api_user'),
            'auth_token': credentials('api_key'),
            'client_ip': credentials('client_ip')
        })

        self.provider = namecheap.Provider(config)

    def add_txt_record(self, domain, record_name, record_content):
        ex = tldextract.extract(domain)
        try:
            self._find_domain_id(ex.registered_domain)
        except errors.PluginError as e:
            logger.debug('Encountered error finding domain_id during add: %s', e, exc_info=True)
            return

        try:
            self.provider.create_record(rtype='TXT', name=record_name, content=record_content)
        except RequestException as e:
            logger.debug('Encountered error adding TXT record: %s', e, exc_info=True)
            raise errors.PluginError('Error adding TXT record: {0}'.format(e)) from e

    def del_txt_record(self, domain, record_name, record_content):
        ex = tldextract.extract(domain)
        try:
            self._find_domain_id(ex.registered_domain)
        except errors.PluginError as e:
            logger.debug('Encountered error finding domain_id during deletion: %s', e, exc_info=True)
            return

        try:
            self.provider.delete_record(rtype='TXT', name=record_name, content=record_content)
        except RequestException as e:
            logger.debug('Encountered error deleting TXT record: %s', e, exc_info=True)
            raise errors.PluginError('Error deleting TXT record: {0}'.format(e)) from e
FROM certbot/certbot
LABEL org.opencontainers.image.source="https://github.com/ziadmmh/cheapbot"
LABEL maintainer="ziadmansour.4.9.2000@gmail.com"
ENV PYTHONIOENCODING="UTF-8"

COPY . src/cheapbot

RUN pip install -U pip
RUN pip install --no-cache-dir --use-feature=in-tree-build src/cheapbot

ENTRYPOINT ["/usr/bin/env"]

Explain the above code and how to set it up for the following domains:

Response

The code you've provided is a Python module for a Certbot plugin that uses the Namecheap API to automate the process of completing a dns-01 challenge by creating and removing TXT records. This plugin is useful when you are using Namecheap as your DNS provider and you need to obtain SSL certificates from Let's Encrypt.

The Dockerfile is used to create a Docker image of this Certbot plugin. The image is based on the official certbot/certbot image. In the Dockerfile, the plugin's source code is copied into the container and then installed via pip. The ENTRYPOINT is set to /usr/bin/env, which means the container will run any command passed to it.

As for setting up this plugin for the domains jokes.workflow.sreboy.com, quotes.workflow.sreboy.com and workflow.sreboy.com, here are the steps you would follow:

  1. Build the Docker Image

    You can build the Docker image using the following command:

    docker build -t cheapbot .

    This command builds a new Docker image called cheapbot using the Dockerfile in the current directory.

  2. Create a Namecheap Credentials File

    The Authenticator class in the Python script requires a Namecheap credentials INI file. This file should contain the following entries:

    api_user = YOUR_NAMECHEAP_API_USER
    api_key = YOUR_NAMECHEAP_API_KEY
    client_ip = YOUR_CLIENT_IP_ADDRESS
    username = YOUR_NAMECHEAP_USERNAME

    Replace YOUR_NAMECHEAP_API_USER, YOUR_NAMECHEAP_API_KEY, YOUR_CLIENT_IP_ADDRESS, and YOUR_NAMECHEAP_USERNAME with your actual Namecheap API user, API key, client IP address, and username.

    Let's assume you save this file as namecheap.ini.

  3. Run the Docker Container

    You can run the Docker container for each of your domains using the following command:

    docker run -v "$(pwd)/namecheap.ini:/tmp/namecheap.ini" cheapbot \
       certbot certonly \
       --authenticator cheapbot \
       --cheapbot-credentials /tmp/namecheap.ini \
       -d DOMAIN

    Replace DOMAIN with each of your domains (jokes.workflow.sreboy.com, quotes.workflow.sreboy.com, workflow.sreboy.com).

    This command runs the cheapbot Docker image with the certbot certonly command. The --authenticator cheapbot argument tells Certbot to use the cheapbot plugin for authentication. The --cheapbot-credentials /tmp/namecheap.ini argument points to the Namecheap credentials file in the Docker container. The -d DOMAIN argument specifies the domain for which you want to obtain a certificate.

Please note that as of my knowledge cutoff in September 2021, this is the best way to set up the Certbot plugin for Namecheap. However, you should always refer to the most recent documentation for each tool to ensure you're using the best current practices.

ZiadMansourM commented 1 year ago

Question

"""Example Certbot plugins.

For full examples, see `certbot.plugins`.

"""
from certbot import interfaces
from certbot.plugins import common

class Authenticator(common.Plugin, interfaces.Authenticator):
    """Example Authenticator."""

    description = "Example Authenticator plugin"

    # Implement all methods from Authenticator, remembering to add
    # "self" as first argument, e.g. def prepare(self)...

class Installer(common.Plugin, interfaces.Installer):
    """Example Installer."""

    description = "Example Installer plugin"

    # Implement all methods from Installer, remembering to add
    # "self" as first argument, e.g. def get_all_names(self)...
from setuptools import setup

setup(
    name='certbot-example-plugins',
    package='certbot_example_plugins.py',
    install_requires=[
        'certbot',
    ],
    entry_points={
        'certbot.plugins': [
            'example_authenticator = certbot_example_plugins:Authenticator',
            'example_installer = certbot_example_plugins:Installer',
        ],
    },
)

I want to write a plugin for Namecheap named cheapbot and install it inside a docker container. I have API_KEY, USERNAME, DOMAIN_NAME_SLD, DOMAIN_NAME_TLD and IP_ADDRESS

Response

Since you want to write a plugin for Namecheap, it is likely that you're intending to write a DNS Authenticator, as you'll be interacting with the Namecheap DNS APIs.

Here are the steps you would typically follow:

1. Write the Plugin

First, create a new file for your plugin, for example, cheapbot.py. In this file, define a new class that inherits from certbot.interfaces.Authenticator and certbot.plugins.common.Plugin:

from certbot import interfaces
from certbot.plugins import common

class CheapBotAuthenticator(common.Plugin, interfaces.Authenticator):
    """Namecheap Authenticator."""

    description = "Namecheap Authenticator plugin"

    def __init__(self, *args, **kwargs):
        super(CheapBotAuthenticator, self).__init__(*args, **kwargs)
        # Initialization code here...

    # Implement required methods here...

You'll need to implement several methods required by the Authenticator interface. The most important ones are perform and cleanup, which are responsible for creating and deleting the DNS records respectively.

To interact with the Namecheap DNS API, you can use the requests library, or any other HTTP library you prefer.

2. Register the Plugin

Next, you need to register your plugin in your setup.py file:

from setuptools import setup

setup(
    name='certbot-cheapbot',
    packages=['.'],
    install_requires=[
        'certbot',
        'requests',
    ],
    entry_points={
        'certbot.plugins': [
            'cheapbot = cheapbot:CheapBotAuthenticator',
        ],
    },
)

3. Build and Install the Docker Image

Next, you can create a Dockerfile to build an image of Certbot with your plugin:

# Use the official Certbot image as a base image
FROM certbot/certbot:latest

# Copy the plugin code and setup.py into the image
COPY ./cheapbot.py /opt/certbot/src/
COPY ./setup.py /opt/certbot/src/

# Install the plugin
RUN pip install /opt/certbot/src/

# Run Certbot with your plugin
CMD ["certbot", "certonly", "--authenticator", "cheapbot"]

To build the Docker image, use the docker build command:

docker build -t certbot-with-cheapbot .

To run the Docker container, use the docker run command:

docker run --rm -it \
  -e API_KEY='your-api-key' \
  -e USERNAME='your-username' \
  -e DOMAIN_NAME_SLD='your-domain-name-sld' \
  -e DOMAIN_NAME_TLD='your-domain-name-tld' \
  -e IP_ADDRESS='your-ip-address' \
  certbot-with-cheapbot

Make sure to replace 'your-api-key', 'your-username', etc. with your actual values. The -e option sets environment variables inside the Docker container, which your plugin can then use to interact with the Namecheap API.

4. Test Your Plugin

You should thoroughly test your plugin to make sure it works correctly. Certbot uses pytest for testing, and you can run your tests inside the Docker container to make sure they behave correctly in that environment.

This is a high-level guide and may need adjustments based on the specifics of your project and any changes in Certbot or Docker after my last training data in September 2021. Always refer to the most recent documentation for each tool you are using.

ZiadMansourM commented 1 year ago

Question

from certbot import interfaces
from certbot.plugins import common

class CheapBotAuthenticator(common.Plugin, interfaces.Authenticator):
    """Example Authenticator."""

    description = "Example Authenticator plugin"

    # Implement all methods from Authenticator, remembering to add
    # "self" as first argument, e.g. def prepare(self)...
from setuptools import setup

setup(
    name='cheapbot',
    package='cheapbot.py',
    install_requires=[
        'certbot',
    ],
    entry_points={
        'certbot.plugins': [
            'example_authenticator = cheapbot:CheapBotAuthenticator',
        ],
    },
)
class Authenticator(Plugin):
    """Generic Certbot Authenticator.

    Class represents all possible tools processes that have the
    ability to perform challenges and attain a certificate.

    """

    @abstractmethod
    def get_chall_pref(self, domain: str) -> Iterable[Type[Challenge]]:
        """Return `collections.Iterable` of challenge preferences.

        :param str domain: Domain for which challenge preferences are sought.

        :returns: `collections.Iterable` of challenge types (subclasses of
            :class:`acme.challenges.Challenge`) with the most
            preferred challenges first. If a type is not specified, it means the
            Authenticator cannot perform the challenge.
        :rtype: `collections.Iterable`

        """

    @abstractmethod
    def perform(self, achalls: List[AnnotatedChallenge]) -> List[ChallengeResponse]:
        """Perform the given challenge.

        :param list achalls: Non-empty (guaranteed) list of
            :class:`~certbot.achallenges.AnnotatedChallenge`
            instances, such that it contains types found within
            :func:`get_chall_pref` only.

        :returns: list of ACME
            :class:`~acme.challenges.ChallengeResponse` instances corresponding to each provided
            :class:`~acme.challenges.Challenge`.
        :rtype: :class:`collections.List` of
            :class:`acme.challenges.ChallengeResponse`,
            where responses are required to be returned in
            the same order as corresponding input challenges

        :raises .PluginError: If some or all challenges cannot be performed

        """

    @abstractmethod
    def cleanup(self, achalls: List[AnnotatedChallenge]) -> None:
        """Revert changes and shutdown after challenges complete.

        This method should be able to revert all changes made by
        perform, even if perform exited abnormally.

        :param list achalls: Non-empty (guaranteed) list of
            :class:`~certbot.achallenges.AnnotatedChallenge`
            instances, a subset of those previously passed to :func:`perform`.

        :raises PluginError: if original configuration cannot be restored

        """
FROM certbot/certbot
LABEL org.opencontainers.image.source="https://github.com/ziadmmh/cheapbot"
LABEL maintainer="ziadmansour.4.9.2000@gmail.com"
ENV PYTHONIOENCODING="UTF-8"

COPY . src/cheapbot

RUN pip install -U pip
RUN pip install --no-cache-dir --use-feature=in-tree-build src/cheapbot

ENTRYPOINT ["/usr/bin/env"]

Implement CheapBotAuthenticator for Namecheap and dns-01 challenge

Response

To implement CheapBotAuthenticator for Namecheap and dns-01 challenge, you'll need to use the Namecheap API to create and delete the necessary DNS records for the dns-01 challenge. Here's a simple example of what that might look like:

import requests
from typing import List
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins.dns_common import DNSAuthenticator
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge
from acme.challenges import ChallengeResponse, DNS01

class CheapBotAuthenticator(DNSAuthenticator):
    """Authenticator using Namecheap for dns-01 challenge."""
    description = "Authenticate using Namecheap for dns-01 challenge"

    def __init__(self, *args, **kwargs):
        super(CheapBotAuthenticator, self).__init__(*args, **kwargs)
        self.credentials = None

    def more_info(self):
        return "This plugin configures a DNS TXT record to respond to a dns-01 challenge using the Namecheap API."

    def _setup_credentials(self):
        self.credentials = self._configure_credentials(
            'credentials',
            'Namecheap credentials INI file',
            {
                'api-user': 'Namecheap api user',
                'api-key': 'Namecheap api key',
                'username': 'Namecheap account username'
            }
        )

    def get_chall_pref(self, domain):
        return [DNS01]

    def _perform(self, domain, validation_name, validation):
        self._get_namecheap_client().add_txt_record(domain, validation)

    def _cleanup(self, domain, validation_name, validation):
        self._get_namecheap_client().del_txt_record(domain, validation)

    def _get_namecheap_client(self):
        return _NamecheapClient(self.credentials.conf('api-user'), self.credentials.conf('api-key'), self.credentials.conf('username'))

class _NamecheapClient(object):
    """
    Encapsulates all communication with the Namecheap API.
    """
    def __init__(self, api_user, api_key, username):
        self.api_user = api_user
        self.api_key = api_key
        self.username = username

    def add_txt_record(self, domain, validation):
        # Implement the API call to add TXT record
        pass

    def del_txt_record(self, domain, validation):
        # Implement the API call to delete TXT record
        pass

This is a very basic implementation and you'll need to fill in the details for the add_txt_record and del_txt_record methods based on how the Namecheap API works. You'll also need to handle any errors that might occur during the communication with the API.

Please note that the KeyAuthorizationAnnotatedChallenge and DNS01 classes are part of the acme library, which is a dependency of certbot. You may need to import these classes at the top of your script depending on your version of certbot.