Skip to content

Commit

Permalink
Merge pull request #183 from rtCamp/feat/dns-challenge-certbot
Browse files Browse the repository at this point in the history
Add bench specific cloudflare certbot config
  • Loading branch information
Xieyt authored May 29, 2024
2 parents 6832bfa + fd636a2 commit 4e52914
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 38 deletions.
19 changes: 15 additions & 4 deletions frappe_manager/site_manager/bench_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,28 @@ def import_from_toml(cls, path: Path) -> "BenchConfig":

data['root_path'] = str(path)

# Extract SSL data and remove it from the main data dictionary
ssl_data = data.get('ssl', None)

if ssl_data:
domain: str = data.get('name', None) # Set domain from main data if necessary
ssl_type = ssl_data.get('ssl_type', SUPPORTED_SSL_TYPES.none)

if ssl_type == SUPPORTED_SSL_TYPES.le:
email = ssl_data.get('email', None)

fm_config_manager = FMConfigManager.import_from_toml()

pref_challenge_data = data.get("preferred_challenge", None)
pref_challenge_data = ssl_data.get("preferred_challenge", None)

api_token = ssl_data.get('api_token', None)

if not api_token:
api_token = fm_config_manager.letsencrypt.api_token

api_key = ssl_data.get('api_key', None)

if not api_key:
api_key = fm_config_manager.letsencrypt.api_key

if not pref_challenge_data:
if fm_config_manager.letsencrypt.exists:
Expand All @@ -131,8 +142,8 @@ def import_from_toml(cls, path: Path) -> "BenchConfig":
ssl_type=ssl_type,
email=email,
preferred_challenge=preferred_challenge,
api_key=fm_config_manager.letsencrypt.api_key,
api_token=fm_config_manager.letsencrypt.api_token,
api_key=api_key,
api_token=api_token,
)
else:
ssl_instance = SSLCertificate(domain=domain, ssl_type=SUPPORTED_SSL_TYPES.none)
Expand Down
25 changes: 16 additions & 9 deletions frappe_manager/site_manager/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
BenchWorkersSupervisorConfigurtionGenerateError,
)
from frappe_manager.site_manager.workers_manager.SiteWorker import BenchWorkers
from frappe_manager.ssl_manager import SUPPORTED_SSL_TYPES
from frappe_manager.ssl_manager import LETSENCRYPT_PREFERRED_CHALLENGE, SUPPORTED_SSL_TYPES
from frappe_manager.ssl_manager.certificate import SSLCertificate
from frappe_manager.ssl_manager.nginxproxymanager import NginxProxyManager
from frappe_manager.ssl_manager.ssl_certificate_manager import SSLCertificateManager
Expand Down Expand Up @@ -671,13 +671,12 @@ def remove_certificate(self):
def update_certificate(self, certificate: SSLCertificate, raise_error: bool = True):
if certificate.ssl_type == SUPPORTED_SSL_TYPES.le:
if self.has_certificate():
if not raise_error:
return
raise BenchSSLCertificateAlreadyIssued(self.name)

self.certificate_manager.set_certificate(certificate)
self.bench_config.ssl = certificate
self.create_certificate()
if raise_error:
raise BenchSSLCertificateAlreadyIssued(self.name)
else:
self.certificate_manager.set_certificate(certificate)
self.bench_config.ssl = certificate
self.create_certificate()

elif certificate.ssl_type == SUPPORTED_SSL_TYPES.none:
if self.has_certificate():
Expand All @@ -686,6 +685,7 @@ def update_certificate(self, certificate: SSLCertificate, raise_error: bool = Tr
if not raise_error:
return
raise BenchSSLCertificateNotIssued(self.name)

return True

def renew_certificate(self):
Expand Down Expand Up @@ -717,6 +717,13 @@ def info(self):

protocol = 'https' if self.has_certificate() else 'http'

ssl_service_type = f'{self.bench_config.ssl.ssl_type.value}'

if self.bench_config.ssl.ssl_type == SUPPORTED_SSL_TYPES.le:
ssl_service_type = (
f'[{self.bench_config.ssl.preferred_challenge.value}] {self.bench_config.ssl.ssl_type.value}'
)

data = {
"Bench Url": f"{protocol}://{self.name}",
"Bench Root": f"[link=file://{self.path.absolute()}]{self.path.absolute()}[/link]",
Expand All @@ -729,7 +736,7 @@ def info(self):
"DB User": db_user,
"DB Password": db_pass,
"Environment": self.bench_config.environment_type.value,
"HTTPS": f'{self.bench_config.ssl.ssl_type.value} ({format_ssl_certificate_time_remaining(self.certificate_manager.get_certficate_expiry())})'
"HTTPS": f'{ssl_service_type.upper()} ({format_ssl_certificate_time_remaining(self.certificate_manager.get_certficate_expiry())})'
if self.has_certificate()
else 'Not Enabled',
}
Expand Down
2 changes: 1 addition & 1 deletion frappe_manager/ssl_manager/letsencrypt_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class LetsencryptSSLCertificate(SSLCertificate):
email: EmailStr = Field(..., description="Email used by certbot.")
api_token: Optional[str] = Field(None, description="Cloudflare API token used by Certbot.")
api_key: Optional[str] = Field(None, description="Cloudflare Global API Key used by Certbot.")
toml_exclude: Optional[set] = {'domain', 'alias_domains', 'toml_exclude', 'api_token', 'api_key'}
toml_exclude: Optional[set] = {'domain', 'alias_domains', 'toml_exclude'}

@model_validator(mode="after")
def validate_credentials(self) -> Self:
Expand Down
18 changes: 9 additions & 9 deletions frappe_manager/ssl_manager/letsencrypt_certificate_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(
self.config_dir: Path = self.root_dir / "config"
self.work_dir: Path = self.root_dir / 'work'
self.logs_dir: Path = self.root_dir / 'logs'
self.dns_config_dir = self.root_dir / 'dns_configs'

self.base_command = f"--work-dir {self.work_dir} --config-dir {self.config_dir} --logs-dir {self.logs_dir}"
self.logger = log.get_logger()
Expand Down Expand Up @@ -84,19 +85,19 @@ def generate_certificate(self, certificate: LetsencryptSSLCertificate):

richprint.print(f"Using Let's Encrypt {certificate.preferred_challenge.value} challenge.")

import tempfile

temp_file = tempfile.NamedTemporaryFile(delete=False)
dns_config_path = self.dns_config_dir / f'{certificate.domain}.txt'

if certificate.preferred_challenge == LETSENCRYPT_PREFERRED_CHALLENGE.http01:
gen_command += f' --webroot -w {self.webroot_dir}'

elif certificate.preferred_challenge == LETSENCRYPT_PREFERRED_CHALLENGE.dns01:
self.dns_config_dir.mkdir(parents=True, exist_ok=True)

api_creds = certificate.get_cloudflare_dns_credentials()
temp_file.write(api_creds.encode())
temp_file.flush()
dns_config_path.write_text(api_creds)
dns_config_path.chmod(0o600)

gen_command += f' --dns-cloudflare --dns-cloudflare-credentials {temp_file.name}'
gen_command += f' --dns-cloudflare --dns-cloudflare-credentials {dns_config_path.absolute()}'

gen_command += f' --keep-until-expiring --expand'
gen_command += f' --agree-tos -m "{certificate.email}" --no-eff-email'
Expand All @@ -120,16 +121,15 @@ def generate_certificate(self, certificate: LetsencryptSSLCertificate):
self.logger.exception(e)
output = '\n'.join(line for line in self.console_output.getvalue().split('\n') if not line.startswith('!!'))
richprint.stdout.print(output)
dns_config_path.unlink()
raise SSLCertificateChallengeFailed(certificate.preferred_challenge)

except Exception as e:
self.logger.exception(e)
output = '\n'.join(line for line in self.console_output.getvalue().split('\n') if not line.startswith('!!'))
richprint.stdout.print(output)
dns_config_path.unlink()
raise SSLCertificateGenerateFailed()

finally:
temp_file.close()

richprint.print("Acquired Letsencrypt certificate: Done")
return self.get_certificate_paths(certificate)
34 changes: 24 additions & 10 deletions frappe_manager/templates/bench_config.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
name = "example.com"
# Denotes the bench name.
# Should not be edited by user.
name = "fm.com"

# when true enables frappe developer mode
# Enables Frappe developer mode for the bench.
developer_mode = false

# when true enables admin tools mode
# Enables admin tools (e.g., Mailhog and Adminer) for the bench.
admin_tools = false

# "prod" -> for bench prod environment
# "dev" -> for bench dev environment
# Sets the bench environment to either "prod" (production) or "dev" (development).
environment_type = "prod"

# enable ssl if defined
[ssl]
# ssl_type -> "letsencrypt" / "disable"
# Sets the SSL type to be used by the bench, in this case, "letsencrypt" for Let's Encrypt.
ssl_type = "letsencrypt"
# only applicable if ssl_type is "letsencrypt"

# Controls the HSTS (HTTP Strict Transport Security) header used by the bench.
# When set to "off", the HSTS header will not be included.
hsts = "off"
# only applicable if ssl_type is "letsencrypt"
email = "[email protected]"

# Specifies the preferred Certbot challenge method to be used for Let's Encrypt certificate validation.
preferred_challenge = "http01"

# The email address associated with Let's Encrypt.
# This is used for notifications, recovery, and is used with `api_key` for the Global API key of Cloudflare.
# For more information, see [this documentation](https://certbot-dns-cloudflare.readthedocs.io/en/stable/#certbot-cloudflare-key-ini).
email = "[email protected]"

# Cloudflare Global API Key for Let's Encrypt DNS01 Challenge.
api_token = "0123456789abcdef0123456789abcdef01234567"

# Cloudflare API token for Let's Encrypt DNS01 Challenge.
api_key = "0123456789abcdef0123456789abcdef01234"
18 changes: 14 additions & 4 deletions frappe_manager/templates/fm_config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Don't change the version no this will affect the migrations.
version = "0.13.0" # set by fm to the current version
# Denotes the current version of fm.
# Should not be edited by user.
version = "0.14.0"

# Email used by Let's Encrypt if set then fm will never ask email for Let's Encrypt.
le_email = '[email protected]'
[letsencrypt]
# The email address associated with Let's Encrypt.
# This is used for notifications, recovery, and is used with api_key for the Global API key of Cloudflare.
# For more information, see [this documentation](https://certbot-dns-cloudflare.readthedocs.io/en/stable/#certbot-cloudflare-key-ini).
email = '[email protected]'

# Cloudflare API token for Let's Encrypt DNS01 Challenge.
api_key = '0123456789abcdef0123456789abcdef01234'

# Cloudflare Global API Key for Let's Encrypt DNS01 Challenge.
api_token = '0123456789abcdef0123456789abcdef01234567'
Loading

0 comments on commit 4e52914

Please sign in to comment.