Merge pull request #5516 from matrix-org/rav/acme_key_path
Allow configuration of the path used for ACME account keys.pull/5523/head
commit
cf7aef1114
|
@ -0,0 +1 @@
|
||||||
|
Allow configuration of the path used for ACME account keys.
|
|
@ -0,0 +1 @@
|
||||||
|
Allow configuration of the path used for ACME account keys.
|
|
@ -1 +0,0 @@
|
||||||
Factor acme bits out to a separate file.
|
|
|
@ -0,0 +1 @@
|
||||||
|
Allow configuration of the path used for ACME account keys.
|
|
@ -1 +0,0 @@
|
||||||
Pass config_dir_path and data_dir_path into Config.read_config.
|
|
|
@ -402,6 +402,13 @@ acme:
|
||||||
#
|
#
|
||||||
#domain: matrix.example.com
|
#domain: matrix.example.com
|
||||||
|
|
||||||
|
# file to use for the account key. This will be generated if it doesn't
|
||||||
|
# exist.
|
||||||
|
#
|
||||||
|
# If unspecified, we will use CONFDIR/client.key.
|
||||||
|
#
|
||||||
|
account_key_file: DATADIR/acme_account.key
|
||||||
|
|
||||||
# List of allowed TLS fingerprints for this server to publish along
|
# List of allowed TLS fingerprints for this server to publish along
|
||||||
# with the signing keys for this server. Other matrix servers that
|
# with the signing keys for this server. Other matrix servers that
|
||||||
# make HTTPS requests to this server will check that the TLS
|
# make HTTPS requests to this server will check that the TLS
|
||||||
|
|
|
@ -414,9 +414,6 @@ class Config(object):
|
||||||
|
|
||||||
Returns: dict
|
Returns: dict
|
||||||
"""
|
"""
|
||||||
# FIXME: get rid of this
|
|
||||||
self.config_dir_path = config_dir_path
|
|
||||||
|
|
||||||
# first we read the config files into a dict
|
# first we read the config files into a dict
|
||||||
specified_config = {}
|
specified_config = {}
|
||||||
for config_file in config_files:
|
for config_file in config_files:
|
||||||
|
|
|
@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TlsConfig(Config):
|
class TlsConfig(Config):
|
||||||
def read_config(self, config, **kwargs):
|
def read_config(self, config, config_dir_path, **kwargs):
|
||||||
|
|
||||||
acme_config = config.get("acme", None)
|
acme_config = config.get("acme", None)
|
||||||
if acme_config is None:
|
if acme_config is None:
|
||||||
|
@ -50,6 +50,10 @@ class TlsConfig(Config):
|
||||||
self.acme_reprovision_threshold = acme_config.get("reprovision_threshold", 30)
|
self.acme_reprovision_threshold = acme_config.get("reprovision_threshold", 30)
|
||||||
self.acme_domain = acme_config.get("domain", config.get("server_name"))
|
self.acme_domain = acme_config.get("domain", config.get("server_name"))
|
||||||
|
|
||||||
|
self.acme_account_key_file = self.abspath(
|
||||||
|
acme_config.get("account_key_file", config_dir_path + "/client.key")
|
||||||
|
)
|
||||||
|
|
||||||
self.tls_certificate_file = self.abspath(config.get("tls_certificate_path"))
|
self.tls_certificate_file = self.abspath(config.get("tls_certificate_path"))
|
||||||
self.tls_private_key_file = self.abspath(config.get("tls_private_key_path"))
|
self.tls_private_key_file = self.abspath(config.get("tls_private_key_path"))
|
||||||
|
|
||||||
|
@ -213,11 +217,12 @@ class TlsConfig(Config):
|
||||||
if sha256_fingerprint not in sha256_fingerprints:
|
if sha256_fingerprint not in sha256_fingerprints:
|
||||||
self.tls_fingerprints.append({"sha256": sha256_fingerprint})
|
self.tls_fingerprints.append({"sha256": sha256_fingerprint})
|
||||||
|
|
||||||
def default_config(self, config_dir_path, server_name, **kwargs):
|
def default_config(self, config_dir_path, server_name, data_dir_path, **kwargs):
|
||||||
base_key_name = os.path.join(config_dir_path, server_name)
|
base_key_name = os.path.join(config_dir_path, server_name)
|
||||||
|
|
||||||
tls_certificate_path = base_key_name + ".tls.crt"
|
tls_certificate_path = base_key_name + ".tls.crt"
|
||||||
tls_private_key_path = base_key_name + ".tls.key"
|
tls_private_key_path = base_key_name + ".tls.key"
|
||||||
|
default_acme_account_file = os.path.join(data_dir_path, "acme_account.key")
|
||||||
|
|
||||||
# this is to avoid the max line length. Sorrynotsorry
|
# this is to avoid the max line length. Sorrynotsorry
|
||||||
proxypassline = (
|
proxypassline = (
|
||||||
|
@ -343,6 +348,13 @@ class TlsConfig(Config):
|
||||||
#
|
#
|
||||||
#domain: matrix.example.com
|
#domain: matrix.example.com
|
||||||
|
|
||||||
|
# file to use for the account key. This will be generated if it doesn't
|
||||||
|
# exist.
|
||||||
|
#
|
||||||
|
# If unspecified, we will use CONFDIR/client.key.
|
||||||
|
#
|
||||||
|
account_key_file: %(default_acme_account_file)s
|
||||||
|
|
||||||
# List of allowed TLS fingerprints for this server to publish along
|
# List of allowed TLS fingerprints for this server to publish along
|
||||||
# with the signing keys for this server. Other matrix servers that
|
# with the signing keys for this server. Other matrix servers that
|
||||||
# make HTTPS requests to this server will check that the TLS
|
# make HTTPS requests to this server will check that the TLS
|
||||||
|
|
|
@ -47,7 +47,7 @@ class AcmeHandler(object):
|
||||||
self._issuer = acme_issuing_service.create_issuing_service(
|
self._issuer = acme_issuing_service.create_issuing_service(
|
||||||
self.reactor,
|
self.reactor,
|
||||||
acme_url=self.hs.config.acme_url,
|
acme_url=self.hs.config.acme_url,
|
||||||
pem_path=self.hs.config.config_dir_path,
|
account_key_file=self.hs.config.acme_account_key_file,
|
||||||
well_known_resource=well_known,
|
well_known_resource=well_known,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -21,28 +21,34 @@ This file contains the unconditional imports on the acme and cryptography bits t
|
||||||
only need (and may only have available) if we are doing ACME, so is designed to be
|
only need (and may only have available) if we are doing ACME, so is designed to be
|
||||||
imported conditionally.
|
imported conditionally.
|
||||||
"""
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from josepy import JWKRSA
|
||||||
from josepy.jwa import RS256
|
from josepy.jwa import RS256
|
||||||
from txacme.challenges import HTTP01Responder
|
from txacme.challenges import HTTP01Responder
|
||||||
from txacme.client import Client
|
from txacme.client import Client
|
||||||
from txacme.endpoint import load_or_create_client_key
|
|
||||||
from txacme.interfaces import ICertificateStore
|
from txacme.interfaces import ICertificateStore
|
||||||
from txacme.service import AcmeIssuingService
|
from txacme.service import AcmeIssuingService
|
||||||
|
from txacme.util import generate_private_key
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.python.filepath import FilePath
|
from twisted.python.filepath import FilePath
|
||||||
from twisted.python.url import URL
|
from twisted.python.url import URL
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def create_issuing_service(reactor, acme_url, pem_path, well_known_resource):
|
|
||||||
|
def create_issuing_service(reactor, acme_url, account_key_file, well_known_resource):
|
||||||
"""Create an ACME issuing service, and attach it to a web Resource
|
"""Create an ACME issuing service, and attach it to a web Resource
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reactor: twisted reactor
|
reactor: twisted reactor
|
||||||
acme_url (str): URL to use to request certificates
|
acme_url (str): URL to use to request certificates
|
||||||
pem_path (str): where to store the client key
|
account_key_file (str): where to store the account key
|
||||||
well_known_resource (twisted.web.IResource): web resource for .well-known.
|
well_known_resource (twisted.web.IResource): web resource for .well-known.
|
||||||
we will attach a child resource for "acme-challenge".
|
we will attach a child resource for "acme-challenge".
|
||||||
|
|
||||||
|
@ -61,7 +67,7 @@ def create_issuing_service(reactor, acme_url, pem_path, well_known_resource):
|
||||||
lambda: Client.from_url(
|
lambda: Client.from_url(
|
||||||
reactor=reactor,
|
reactor=reactor,
|
||||||
url=URL.from_text(acme_url),
|
url=URL.from_text(acme_url),
|
||||||
key=load_or_create_client_key(FilePath(pem_path)),
|
key=load_or_create_client_key(account_key_file),
|
||||||
alg=RS256,
|
alg=RS256,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -82,3 +88,30 @@ class ErsatzStore(object):
|
||||||
def store(self, server_name, pem_objects):
|
def store(self, server_name, pem_objects):
|
||||||
self.certs[server_name] = [o.as_bytes() for o in pem_objects]
|
self.certs[server_name] = [o.as_bytes() for o in pem_objects]
|
||||||
return defer.succeed(None)
|
return defer.succeed(None)
|
||||||
|
|
||||||
|
|
||||||
|
def load_or_create_client_key(key_file):
|
||||||
|
"""Load the ACME account key from a file, creating it if it does not exist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key_file (str): name of the file to use as the account key
|
||||||
|
"""
|
||||||
|
# this is based on txacme.endpoint.load_or_create_client_key, but doesn't
|
||||||
|
# hardcode the 'client.key' filename
|
||||||
|
acme_key_file = FilePath(key_file)
|
||||||
|
if acme_key_file.exists():
|
||||||
|
logger.info("Loading ACME account key from '%s'", acme_key_file)
|
||||||
|
key = serialization.load_pem_private_key(
|
||||||
|
acme_key_file.getContent(), password=None, backend=default_backend()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("Saving new ACME account key to '%s'", acme_key_file)
|
||||||
|
key = generate_private_key("rsa")
|
||||||
|
acme_key_file.setContent(
|
||||||
|
key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.NoEncryption(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return JWKRSA(key=key)
|
||||||
|
|
Loading…
Reference in New Issue