Welcome to mirror list, hosted at ThFree Co, Russian Federation.

tls_sni_01.py « certbot_apache « certbot-apache - github.com/certbot/certbot.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 65230cdcb3eaf6154d49f1029dcc7d235b7a5821 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"""A class that performs TLS-SNI-01 challenges for Apache"""

import os
import logging

from acme.magic_typing import Set  # pylint: disable=unused-import, no-name-in-module
from certbot.plugins import common
from certbot.errors import PluginError, MissingCommandlineFlag

from certbot_apache import obj

logger = logging.getLogger(__name__)


class ApacheTlsSni01(common.TLSSNI01):
    """Class that performs TLS-SNI-01 challenges within the Apache configurator

    :ivar configurator: ApacheConfigurator object
    :type configurator: :class:`~apache.configurator.ApacheConfigurator`

    :ivar list achalls: Annotated TLS-SNI-01
        (`.KeyAuthorizationAnnotatedChallenge`) challenges.

    :param list indices: Meant to hold indices of challenges in a
        larger array. ApacheTlsSni01 is capable of solving many challenges
        at once which causes an indexing issue within ApacheConfigurator
        who must return all responses in order.  Imagine ApacheConfigurator
        maintaining state about where all of the http-01 Challenges,
        TLS-SNI-01 Challenges belong in the response array.  This is an
        optional utility.

    :param str challenge_conf: location of the challenge config file

    """

    VHOST_TEMPLATE = """\
<VirtualHost {vhost}>
    ServerName {server_name}
    UseCanonicalName on
    SSLStrictSNIVHostCheck on

    LimitRequestBody 1048576

    Include {ssl_options_conf_path}
    SSLCertificateFile {cert_path}
    SSLCertificateKeyFile {key_path}

    DocumentRoot {document_root}
</VirtualHost>

"""

    def __init__(self, *args, **kwargs):
        super(ApacheTlsSni01, self).__init__(*args, **kwargs)

        self.challenge_conf = os.path.join(
            self.configurator.conf("challenge-location"),
            "le_tls_sni_01_cert_challenge.conf")

    def perform(self):
        """Perform a TLS-SNI-01 challenge."""
        if not self.achalls:
            return []
        # Save any changes to the configuration as a precaution
        # About to make temporary changes to the config
        self.configurator.save("Changes before challenge setup", True)

        # Prepare the server for HTTPS
        self.configurator.prepare_server_https(
            str(self.configurator.config.tls_sni_01_port), True)

        responses = []

        # Create all of the challenge certs
        for achall in self.achalls:
            responses.append(self._setup_challenge_cert(achall))

        # Setup the configuration
        addrs = self._mod_config()
        self.configurator.save("Don't lose mod_config changes", True)
        self.configurator.make_addrs_sni_ready(addrs)

        # Save reversible changes
        self.configurator.save("SNI Challenge", True)

        return responses

    def _mod_config(self):
        """Modifies Apache config files to include challenge vhosts.

        Result: Apache config includes virtual servers for issued challs

        :returns: All TLS-SNI-01 addresses used
        :rtype: set

        """
        addrs = set()  # type: Set[obj.Addr]
        config_text = "<IfModule mod_ssl.c>\n"

        for achall in self.achalls:
            achall_addrs = self._get_addrs(achall)
            addrs.update(achall_addrs)

            config_text += self._get_config_text(achall, achall_addrs)

        config_text += "</IfModule>\n"

        self.configurator.parser.add_include(
            self.configurator.parser.loc["default"], self.challenge_conf)
        self.configurator.reverter.register_file_creation(
            True, self.challenge_conf)

        logger.debug("writing a config file with text:\n %s", config_text)
        with open(self.challenge_conf, "w") as new_conf:
            new_conf.write(config_text)

        return addrs

    def _get_addrs(self, achall):
        """Return the Apache addresses needed for TLS-SNI-01."""
        # TODO: Checkout _default_ rules.
        addrs = set()
        default_addr = obj.Addr(("*", str(
            self.configurator.config.tls_sni_01_port)))

        try:
            vhost = self.configurator.choose_vhost(achall.domain,
                                                   create_if_no_ssl=False)
        except (PluginError, MissingCommandlineFlag):
            # We couldn't find the virtualhost for this domain, possibly
            # because it's a new vhost that's not configured yet
            # (GH #677). See also GH #2600.
            logger.warning("Falling back to default vhost %s...", default_addr)
            addrs.add(default_addr)
            return addrs

        for addr in vhost.addrs:
            if "_default_" == addr.get_addr():
                addrs.add(default_addr)
            else:
                addrs.add(
                    addr.get_sni_addr(
                        self.configurator.config.tls_sni_01_port))

        return addrs

    def _get_config_text(self, achall, ip_addrs):
        """Chocolate virtual server configuration text

        :param .KeyAuthorizationAnnotatedChallenge achall: Annotated
            TLS-SNI-01 challenge.

        :param list ip_addrs: addresses of challenged domain
            :class:`list` of type `~.obj.Addr`

        :returns: virtual host configuration text
        :rtype: str

        """
        ips = " ".join(str(i) for i in ip_addrs)
        document_root = os.path.join(
            self.configurator.config.work_dir, "tls_sni_01_page/")
        # TODO: Python docs is not clear how multiline string literal
        # newlines are parsed on different platforms. At least on
        # Linux (Debian sid), when source file uses CRLF, Python still
        # parses it as "\n"... c.f.:
        # https://docs.python.org/2.7/reference/lexical_analysis.html
        return self.VHOST_TEMPLATE.format(
            vhost=ips,
            server_name=achall.response(achall.account_key).z_domain.decode('ascii'),
            ssl_options_conf_path=self.configurator.mod_ssl_conf,
            cert_path=self.get_cert_path(achall),
            key_path=self.get_key_path(achall),
            document_root=document_root).replace("\n", os.linesep)