* Wed Feb 12 2025 Jean-Philippe Pialasse <jpp@koozali.org> 11.0-2.sme

- move smanager panel in package [SME: 12916]
- add Requires
- add templates from smeserver-letsencrypt
- use /var/www/html/.well-known/acme-challenge
This commit is contained in:
2025-02-13 01:05:14 -05:00
parent 80e98728e8
commit e631a1dffc
54 changed files with 2327 additions and 20 deletions

View File

@@ -0,0 +1 @@
dehydrated:any:/sbin/e-smith/signal-event smeserver-certificates-update

View File

@@ -0,0 +1 @@
none

View File

@@ -0,0 +1 @@
disabled

View File

@@ -0,0 +1 @@
disabled

View File

@@ -0,0 +1 @@
service

View File

@@ -135,7 +135,9 @@ for DOMAIN in $domainlist
/sbin/e-smith/db $TYPE delprop $DOMAIN letsencryptMYIP
continue
fi
THISDOMIP=$(/usr/bin/q A @$mydns $DOMAIN -f json |jq -r 'first(.Answers[].A | select( . != null )) // null' 2>/dev/null || /usr/bin/q A @$LOCALIP $DOMAIN -f json |jq -r 'first(.Answers[].A | select( . != null )) // null' 2>/dev/null )
# q output has changed
#THISDOMIP=$(/usr/bin/q A @$mydns $DOMAIN -f json |jq -r 'first(.Answers[].A | select( . != null )) // null' 2>/dev/null || /usr/bin/q A @$LOCALIP $DOMAIN -f json |jq -r 'first(.Answers[].A | select( . != null )) // null' 2>/dev/null)
THISDOMIP=$(/usr/bin/q @$mydns A $DOMAIN -f json |jq -r 'first(.[].replies[].answer[].a | select( . != null )) // null' 2>/dev/null || /usr/bin/q @$LOCALIP A $DOMAIN -f json |jq -r 'first(.[].replies[].answer[].a | select( . != null )) // null' 2>/dev/null )
previous=$(/sbin/e-smith/db $TYPE getprop $DOMAIN letsencryptSSLcert||echo 'undefined');
# if it does not resolve, next
if [[ "$THISDOMIP" == "" ]]

View File

@@ -0,0 +1 @@
PERMS=0644

View File

@@ -0,0 +1 @@
PERMS=0755

View File

@@ -0,0 +1,20 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $letsencryptStatus = $configDB->get_prop( 'letsencrypt', 'status' ) || 'disabled';
if ( $letsencryptStatus eq 'enabled' ) {
$OUT .= "#!/bin/sh\n\n";
$OUT .= "32 3 * * 5 root test -s /etc/dehydrated/domains.txt && /usr/bin/dehydrated --cron";
}
else {
$OUT .= "# letsencrypt is disabled\n";
}
}

View File

@@ -0,0 +1,13 @@
#!/bin/bash
########################################################
# This is the main config file for dehydrated #
# #
# This file is looked for in the following locations: #
# $SCRIPTDIR/config (next to this script) #
# /usr/local/etc/dehydrated/config #
# /etc/dehydrated/config #
# ${PWD}/config (in current working-directory) #
# #
# Default values of this config are in comments #
########################################################

View File

@@ -0,0 +1,5 @@
{
$letsencryptStatus = $letsencrypt{'status'} || 'disabled';
$letsencryptAPI = $letsencrypt{'API'} || 'auto';
"";
}

View File

@@ -0,0 +1,6 @@
# Which user should dehydrated run as? This will be implictly enforced when running as root
#DEHYDRATED_USER=
# Which group should dehydrated run as? This will be implictly enforced when running as root
#DEHYDRATED_GROUP=

View File

@@ -0,0 +1,90 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $letsencryptStatus = $configDB->get_prop( 'letsencrypt', 'status' )
|| 'disabled';
# Default to v2 as v1 no longer supported for new certs. At the moment (Oct 2020) v1 still supported for renewing certs.
my $letsencryptAPI = $configDB->get_prop( 'letsencrypt', 'API' )
|| '2';
$OUT .= "#!/bin/bash\n";
if ( $letsencryptStatus eq 'disabled' ) {
$OUT .= "letsencrypt is disabled";
}
else {
# We should only be here if we are not disabled
if ( $letsencryptStatus eq 'test' ) {
# Use staging directory for testing
# Once you are sure you have the settings right then change
# If it's v1 then use v1, if v2 or auto then use v2 staging
if ( $letsencryptAPI eq '1' ) {
$OUT .= "CA=\"https://acme-staging.api.letsencrypt.org/directory\"\n";
}
elsif ( ( $letsencryptAPI eq '2' ) || ( $letsencryptAPI eq 'auto' ) ) {
$OUT .= "CA=\"https://acme-staging-v02.api.letsencrypt.org/directory\"\n";
}
}
elsif ( $letsencryptStatus ne 'test' ) {
# Real server - default settings are in the the main dehydrated file
# Only use this once you are sure things are OK or you will hit a rate limit.
# If it's v1 then use v1, if v2 then v2, if auto accept the defaults in the main file
if ( $letsencryptAPI eq '1' ) {
$OUT .= "CA=\"https://acme-v01.api.letsencrypt.org/directory\"\n";
}
elsif ( $letsencryptAPI eq '2' ) {
$OUT .= "CA=\"https://acme-v02.api.letsencrypt.org/directory\"\n";
}
}
$OUT .= "WELLKNOWN=\"/var/www/html/.well-known/acme-challenge\"\n";
# Hook Script always enabled
$OUT .= "HOOK=\"/usr/bin/hook-script.sh\"\n";
# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
#BASEDIR=$SCRIPTDIR
$OUT .= "BASEDIR=\"/etc/dehydrated\"\n";
# Location of private account key (default: $BASEDIR/private_key.pem)
#PRIVATE_KEY="${BASEDIR}/private_key.pem"
my $letsencryptKeysize = $configDB->get_prop( 'letsencrypt', 'keysize' )
|| '';
if ( $letsencryptKeysize ne '' ) {
# Default keysize for private keys (default: 4096)
$OUT .= "KEYSIZE=\"4096\"\n";
}
my $letsencryptEmail = $configDB->get_prop( 'letsencrypt', 'email' ) || '';
if ( $letsencryptEmail ne '' ) {
# E-mail to use during the registration (default: <unset>)
$OUT .= "CONTACT_EMAIL=$letsencryptEmail\n";
}
# API version - auto | 1 | 2
if ( $letsencryptAPI eq '1' ) {
$OUT .= "API=\"1\"\n";
}
elsif ( $letsencryptAPI eq '2' ) {
$OUT .= "API=\"2\"\n";
}
else {
$OUT .= "API=\"auto\"\n";
}
}
}

View File

@@ -0,0 +1,5 @@
# Resolve names to addresses of IP version only. (curl)
# supported values: 4, 6
# default: <unset>
IP_VERSION=4

View File

@@ -0,0 +1,36 @@
# Path to certificate authority (default: https://acme-v02.api.letsencrypt.org/directory)
{
if ( $letsencryptStatus eq 'test' ) {
# Use staging directory for testing
# Once you are sure you have the settings right then change
# If it's v1 then use v1, if v2 or auto then use v2 staging
if ( $letsencryptAPI eq '1' ) {
$OUT .= "CA=\"https://acme-staging.api.letsencrypt.org/directory\"\n";
}
elsif ( ( $letsencryptAPI eq '2' ) || ( $letsencryptAPI eq 'auto' ) ) {
$OUT .= "CA=\"https://acme-staging-v02.api.letsencrypt.org/directory\"\n";
}
}
elsif ( $letsencryptStatus ne 'test' ) {
# Real server - default settings are in the the main dehydrated file
# Only use this once you are sure things are OK or you will hit a rate limit.
# If it's v1 then use v1, if v2 then v2, if auto accept the defaults in the main file
if ( $letsencryptAPI eq '1' ) {
$OUT .= "CA=\"https://acme-v01.api.letsencrypt.org/directory\"\n";
}
elsif ( $letsencryptAPI eq '2' ) {
$OUT .= "CA=\"https://acme-v02.api.letsencrypt.org/directory\"\n";
}
else {
$OUT .= "#CA=\"https://acme-v02.api.letsencrypt.org/directory\"\n";
}
}
}

View File

@@ -0,0 +1,7 @@
# Path to old certificate authority
# Set this value to your old CA value when upgrading from ACMEv1 to ACMEv2 under a different endpoint.
# If dehydrated detects an account-key for the old CA it will automatically reuse that key
# instead of registering a new one.
# default: https://acme-v01.api.letsencrypt.org/directory
#OLDCA="https://acme-v01.api.letsencrypt.org/directory"

View File

@@ -0,0 +1,11 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $ACCEPT_TERMS = $configDB->get_prop( 'letsencrypt', 'ACCEPT_TERMS' )
|| 'no';
$OUT .= "PARAM_ACCEPT_TERMS=\"yes\"\n" if $ACCEPT_TERMS eq 'yes';
$OUT .= "# letsencrypt property ACCEPT_TERMS not set to yes\n" unless $ACCEPT_TERMS eq 'yes';
}

View File

@@ -0,0 +1,3 @@
# Which challenge should be used? Currently http-01, dns-01 and tls-alpn-01 are supported
#CHALLENGETYPE="http-01"

View File

@@ -0,0 +1,29 @@
# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# default: <unset>
#CONFIG_D=
# Directory for per-domain configuration files.
# If not set, per-domain configurations are sourced from each certificates output directory.
# default: <unset>
#DOMAINS_D=
# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
BASEDIR=$SCRIPTDIR
# File containing the list of domains to request certificates for (default: $BASEDIR/domains.txt)
DOMAINS_TXT="${BASEDIR}/domains.txt"
# Output directory for generated certificates
CERTDIR="${BASEDIR}/certs"
# Output directory for alpn verification certificates
ALPNCERTDIR="${BASEDIR}/alpn-certs"
# Directory for account keys and registration information
ACCOUNTDIR="${BASEDIR}/accounts"
# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/dehydrated)
WELLKNOWN="/var/www/dehydrated"

View File

@@ -0,0 +1,3 @@
# Default keysize for private keys (default: 4096)
KEYSIZE="{$letsencrypt{KeySize}||$modSSL{KeySize}||4096}"

View File

@@ -0,0 +1,3 @@
# SME Server does not support yet elliptic curve (qpsmtpd and perl-IO-SOcket-SSL < 1.95)
KEY_ALGO=rsa

View File

@@ -0,0 +1,3 @@
# Minimum days before expiration to automatically renew certificate (default: 30)
RENEW_DAYS="{$letsencrypt{DAYS} || 30}"

View File

@@ -0,0 +1,8 @@
# Path to openssl config file (default: <unset> - tries to figure out system default)
#OPENSSL_CNF=
# Path to OpenSSL binary (default: "openssl")
OPENSSL="{ return "/usr/bin/openssl11" if ( -f "/usr/bin/openssl11" );
"/usr/bin/openssl"
}"

View File

@@ -0,0 +1,3 @@
# Extra options passed to the curl binary (default: <unset>)
#CURL_OPTS=

View File

@@ -0,0 +1,6 @@
# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
#PRIVATE_KEY_RENEW="yes"
# Create an extra private key for rollover (default: no)
#PRIVATE_KEY_ROLLOVER="no"

View File

@@ -0,0 +1,3 @@
# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
#KEY_ALGO=rsa

View File

@@ -0,0 +1,15 @@
# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: <unset>
HOOK= /usr/bin/hook-script.sh
# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
#HOOK_CHAIN="no"

View File

@@ -0,0 +1,3 @@
# E-mail to use during the registration (default: <unset>)
CONTACT_EMAIL="{$letsencrypt{'email'}||''}"

View File

@@ -0,0 +1,3 @@
# Lockfile location, to prevent concurrent access (default: $BASEDIR/lock)
LOCKFILE="${BASEDIR}/lock"

View File

@@ -0,0 +1,9 @@
# Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no)
#OCSP_MUST_STAPLE="no"
# Fetch OCSP responses (default: no)
#OCSP_FETCH="no"
# OCSP refresh interval (default: 5 days)
#OCSP_DAYS=5

View File

@@ -0,0 +1,3 @@
# Issuer chain cache directory (default: $BASEDIR/chains)
#CHAINCACHE="${BASEDIR}/chains"

View File

@@ -0,0 +1,3 @@
# Automatic cleanup (default: no)
#AUTO_CLEANUP="no"

View File

@@ -0,0 +1,3 @@
# ACME API version (default: auto)
API="{$letsencryptAPI||'auto'}"

View File

@@ -0,0 +1,5 @@
{
my $ACCEPT_TERMS = $letsencrypt{'ACCEPT_TERMS'} || 'no';
return "PARAM_ACCEPT_TERMS=\"yes\"\n" if $ACCEPT_TERMS eq 'yes';
$OUT .= "# letsencrypt property ACCEPT_TERMS not set to yes\n" unless $ACCEPT_TERMS eq 'yes';
}

View File

@@ -0,0 +1,133 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $domainsDB = esmith::ConfigDB->open_ro('domains')
or die("can't connect to domains database");
my $hostsDB = esmith::ConfigDB->open_ro('hosts')
or die("can't connect to hosts database");
# my $dbKey = 'domain';
# my $systemMode = $configDB->get("SystemMode")->value;
# if ( $systemMode ne 'servergateway' ) {
# $OUT .= "# System not in Server Gateway mode\n";
# }
my $letsencryptStatus = $configDB->get_prop( 'letsencrypt', 'status' )
|| 'disabled';
if ( $letsencryptStatus ne 'disabled' ) {
# This should get all the connections in an array
my @domains = $domainsDB->keys;
my @hosts = $hostsDB->keys;
# print "@domains\n";
# Need to check here if we want ALL set
# all, domains, hosts, both, none
my $letsencryptConfig = $configDB->get_prop( 'letsencrypt', 'configure' ) || 'none';
# First get all the domains
# We could do this BUT only once as the array drops $vars
# my $dom = shift @domains;
# Patch from JPP
# Put Primary domain at top
my $DomainName = $configDB->get('DomainName')->value;
my $mainDomainStatus = $domainsDB->get_prop( "$DomainName", 'letsencryptSSLcert' )
|| 'disabled';
$OUT .= "$DomainName " unless $mainDomainStatus eq 'disabled';
foreach my $domain (@domains) {
# If we are all or domains then lets do all regardless
if ( $letsencryptConfig eq 'all' || $letsencryptConfig eq 'domains' ) {
# Check for self
#my $domainStatus =
# $domainsDB->get_prop( "Nameservers", 'HostType' ) || '';
#
#if ( $domainStatus eq 'Localhost' ) {
$OUT .= "$domain ";
#}
}
else {
my $domainEnabled = $domainsDB->get_prop( "$domain", 'letsencryptSSLcert' )
|| 'disabled';
if ( $domainEnabled eq 'enabled' ) {
$OUT .= "$domain " unless $DomainName eq $domain;
}
}
# Now check for hosts
# Buggered if I remember why we check that
# the host has a domain name in domains !
# Must have been a reason
foreach my $fqdn (@hosts) {
# If we are set to all or hosts just do it
if ( $letsencryptConfig eq 'all' || $letsencryptConfig eq 'hosts' ) {
$OUT .= "$fqdn " unless $DomainName eq $fqdn;
}
# Just do selected entries
else {
# Lets get the hostname
my $hostname = $fqdn;
$hostname =~ s/\..*//;
# print "$hostname\n";
# Lets get the domain name
my $domainname = $fqdn;
$domainname =~ s/.*?\.//;
# print "$domainname\n";
# is the domain name from the hosts file
# the same as that in the domains file ?
my $hostEnabled = $hostsDB->get_prop( "$fqdn", 'letsencryptSSLcert' )
|| 'disabled';
if ( $domainname eq $domain && $hostEnabled eq 'enabled' ) {
# Are we self ?
my $type = $hostsDB->get_prop( "$fqdn", 'HostType' );
my $hostOverride = $configDB->get_prop( 'letsencrypt', 'hostOverride' )
|| 'disabled';
# print "Override $hostOverride";
if ( $hostOverride eq 'yes' ) {
$OUT .= "$fqdn " unless $DomainName eq $fqdn;
}
elsif ( $type eq 'Self' ) {
# print "Here: $fqdn $type\n";
$OUT .= "$fqdn " unless $DomainName eq $fqdn;
}
}
}
}
}
}
else {
$OUT .= "# letsencrypt is disabled\n";
}
}

View File

@@ -0,0 +1,12 @@
# dehydrated.timer preset
{
# depending on letsencrypt status
#$status = $letsencrypt{status} || 'disabled';
#$status = ($status eq "enabled") ? "enable" : "disable";
#$OUT .= "$status dehydrated.timer\n";
# or always disabled
$OUT .= "disable dehydrated.timer\n";
}

View File

@@ -0,0 +1,54 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $letsencryptStatus = $configDB->get_prop( 'letsencrypt', 'status' ) || 'disabled';
my $version = $configDB->get_prop( 'sysconfig', 'ReleaseVersion' );
$version = substr( $version, 0, 1 );
if ( $letsencryptStatus ne 'disabled' ) {
if ( $version == 8 ) {
$OUT .= <<'_EOF';
if [ $1 = "deploy_cert" ]; then
KEY=$3
CERT=$4
CHAIN=$6
echo "Set up modSSL db keys"
/sbin/e-smith/db configuration setprop modSSL key $KEY
/sbin/e-smith/db configuration setprop modSSL crt $CERT
/sbin/e-smith/db configuration setprop modSSL CertificateChainFile $CHAIN
echo "Signal events"
/sbin/e-smith/signal-event domain-modify
/sbin/e-smith/signal-event email-update
/sbin/e-smith/signal-event ibay-modify
echo "All complete"
fi
_EOF
}
else {
$OUT .= <<'_EOF';
if [ $1 = "deploy_cert" ]; then
KEY=$3
CERT=$4
CHAIN=$6
echo "Set up modSSL db keys"
/sbin/e-smith/db configuration setprop modSSL key $KEY
/sbin/e-smith/db configuration setprop modSSL crt $CERT
/sbin/e-smith/db configuration setprop modSSL CertificateChainFile $CHAIN
echo "Signal events"
/sbin/e-smith/signal-event ssl-update
echo "All complete"
fi
_EOF
}
}
}

View File

@@ -0,0 +1,76 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $letsencryptStatus = $configDB->get_prop( 'letsencrypt', 'status' ) || 'disabled';
my $hookscript = $configDB->get_prop( 'letsencrypt', 'hookScript' ) || 'disabled';
my $host = $configDB->get_prop( 'letsencrypt', 'host' ) || '';
my $user = $configDB->get_prop( 'letsencrypt', 'user' ) || '';
my $path = $configDB->get_prop( 'letsencrypt', 'path' ) || '';
if ( $letsencryptStatus ne 'disabled' && $hookscript eq 'enabled' && $host ne '' && $user ne '' && $path ne '' ) {
$OUT .= "if [ \$1 = \"deploy_challenge\" ]; then\n";
$OUT .= " CHALLENGE_FILE=\$3\n";
$OUT .= " CHALLENGE_CONTENT=\$4\n";
$OUT .= " HOST=\"$host\" # FQDN or IP of public-facing server\n";
$OUT .= " USER=\"$user\" # username on public-facing server\n";
$OUT .= " REMOTE_PATH=\"$path\"\n";
$OUT .= " if scp \$WELLKNOWN/\$CHALLENGE_FILE \$USER@\$HOST:\$REMOTE_PATH/\$CHALLENGE_FILE; then\n";
$OUT .= " exit 0\n";
$OUT .= " else\n";
$OUT .= " echo \" Failed to deploy challenge !\" \n ";
$OUT .= " exit 1 \n ";
$OUT .= " fi \n ";
$OUT .= "fi \n ";
$OUT .= "\n";
$OUT .= " if [ \$1 = \"clean_challenge\" ]; then\n";
$OUT .= " CHALLENGE_FILE=\$3\n";
$OUT .= " HOST=\"$host\" # FQDN or IP of public-facing server\n";
$OUT .= " USER=\"$user\" # username on public-facing server\n";
$OUT .= " REMOTE_PATH=\"$path\"\n";
$OUT .= " if ssh \$USER\@\$HOST \"rm \$REMOTE_PATH/\$CHALLENGE_FILE\"; then\n";
$OUT .= " exit 0\n";
$OUT .= " else\n";
$OUT .= " echo \" Failed to clean challenge !\" \n ";
$OUT .= " exit 1 \n ";
$OUT .= " fi \n ";
$OUT .= "fi \n ";
}
else {
$OUT .= "# The following all have to be set to enable deploy/clean challenges\n";
$OUT .= "# \n";
if ( $hookscript ne '' ) {
$OUT .= "# hookScript: $hookscript\n";
}
else {
$OUT .= "# hookScript: Not Set\n";
}
if ( $host ne '' ) {
$OUT .= "# host: $host\n";
}
else {
$OUT .= "# host: Not Set\n";
}
if ( $user ne '' ) {
$OUT .= "# user: $user\n";
}
else {
$OUT .= "# user: Not Set\n";
}
if ( $path ne '' ) {
$OUT .= "# path: $path\n";
}
else {
$OUT .= "# path: Not Set\n";
}
}
}

View File

@@ -0,0 +1,17 @@
{
use strict;
use warnings;
use esmith::ConfigDB;
my $configDB = esmith::ConfigDB->open_ro or die("can't open Config DB");
my $letsencryptStatus = $configDB->get_prop( 'letsencrypt', 'status' ) || 'disabled';
if ( $letsencryptStatus ne 'disabled' ) {
$OUT .= "#!/bin/bash\n";
$OUT .= "# deploy_cert hook will set config database entries for the cert files\n";
$OUT .= "# and restart appropriate services\n";
$OUT .= "#\n";
}
}