diff options
| author | Simon Rettberg | 2025-08-12 14:52:40 +0200 |
|---|---|---|
| committer | Simon Rettberg | 2025-08-12 14:52:40 +0200 |
| commit | 32ec0505afae781a44466621ebbd8e83cf68fdb8 (patch) | |
| tree | b4f3db53319de6112bafaf99dd5999858e88e0e2 | |
| parent | [locationinfo] Update HisInOne title cleanup regex (diff) | |
| download | slx-admin-32ec0505afae781a44466621ebbd8e83cf68fdb8.tar.gz slx-admin-32ec0505afae781a44466621ebbd8e83cf68fdb8.tar.xz slx-admin-32ec0505afae781a44466621ebbd8e83cf68fdb8.zip | |
[webinterface] Add support for HARICA and custom URLs
7 files changed, 80 insertions, 8 deletions
diff --git a/modules-available/webinterface/inc/acme.inc.php b/modules-available/webinterface/inc/acme.inc.php index 3f5e76a0..bc26b7b2 100644 --- a/modules-available/webinterface/inc/acme.inc.php +++ b/modules-available/webinterface/inc/acme.inc.php @@ -9,15 +9,19 @@ class Acme const PROP_HMAC_KEY = 'acme.hmac-key'; const PROP_DOMAINS = 'acme.domains'; const PROP_MAIL = 'acme.mail'; + const PROP_CUSTOM_ACME_URL = 'acme.server-url'; const VALID_PROVIDERS = [ 'letsencrypt' => "Let's Encrypt", 'zerossl' => 'ZeroSSL.com', 'buypass' => 'BuyPass.com', - 'geant/sectigo' => 'GEANT via Sectigo', + //'geant/sectigo' => 'GEANT via Sectigo', + 'harica' => 'HARICA', + 'custom' => '...', ]; const PROVIDER_ALIASES = [ 'geant/sectigo' => 'https://acme.sectigo.com/v2/GEANTOV', + 'harica' => 'https://acme.harica.gr/acme/directory', ]; public static function getLastError(): ?string @@ -30,6 +34,11 @@ class Acme return Property::get(self::PROP_PROVIDER, null); } + public static function getServerUrl(): ?string + { + return Property::get(self::PROP_CUSTOM_ACME_URL, null); + } + public static function getKeyId(): ?string { return Property::get(self::PROP_KEY_ID, null); @@ -58,9 +67,27 @@ class Acme return explode(' ', Property::get(self::PROP_DOMAINS)); } - public static function setConfig(string $provider, string $mail, ?string $keyId = null, ?string $hmacKey = null): bool + /** + * Sets the configuration to the specified provider with optional server URL and authentication keys. + * + * @param string $provider The provider identifier, either 'custom' or a key in the valid providers list. + * @param string $mail The email address associated with the provider. + * @param string|null $serverUrl The custom server URL for the provider, required for the 'custom' provider and must use HTTPS. + * @param string|null $keyId The optional key ID used for authentication. + * @param string|null $hmacKey The optional HMAC key for authentication. + * + * @return bool Returns true if the configuration is successfully set, false otherwise. + */ + public static function setConfig(string $provider, string $mail, ?string $serverUrl = null, + ?string $keyId = null, ?string $hmacKey = null): bool { - if (!isset(self::VALID_PROVIDERS[$provider])) { + if ($provider === 'custom') { + if (substr($serverUrl, 0, 6) !== 'https:') { + Message::addError('webinterface.acme-invalid-url', $serverUrl); + return false; + } + Property::set(self::PROP_CUSTOM_ACME_URL, $serverUrl); // Only update if custom is selected + } elseif (!isset(self::VALID_PROVIDERS[$provider])) { Message::addError('webinterface.acme-invalid-provider', $provider); return false; } @@ -116,9 +143,18 @@ class Acme } } + /** + * Issues a new certificate using the configured ACME provider and other relevant details. + * + * @param bool $wipeAll Indicates whether all existing certificates and accounts should be wiped before issuing a new one. + * @return ?string The task ID of the certificate issuance process, or null if an error occurred. + */ public static function issueNewCertificate(bool $wipeAll = false): ?string { $provider = self::getProvider(); + if ($provider === 'custom') { + $provider = Property::get(self::PROP_CUSTOM_ACME_URL, null); + } if ($provider === null) { Message::addError('webinterface.acme-no-provider'); return null; @@ -148,6 +184,12 @@ class Acme return $task['id'] ?? null; } + /** + * Renews certificates based on available domains. + * This expects a valid configuration and existing account. + * + * @return ?string ID of the submitted task for the renewal process or null if no domains are available + */ public static function renew(): ?string { error_log("Renew called"); diff --git a/modules-available/webinterface/lang/de/messages.json b/modules-available/webinterface/lang/de/messages.json index f1d0d6af..f87f3d3a 100644 --- a/modules-available/webinterface/lang/de/messages.json +++ b/modules-available/webinterface/lang/de/messages.json @@ -1,5 +1,6 @@ { "acme-invalid-provider": "Ung\u00fcltiger ACME-Anbieter: {{0}}", + "acme-invalid-url": "Ung\u00fcltige URL: {{0}}", "acme-no-domains": "Keine Domains angegeben", "acme-no-mail": "Keine administrative Mailadresse angegeben", "acme-no-provider": "Kein ACME-Anbieter ausgew\u00e4hlt", diff --git a/modules-available/webinterface/lang/de/template-tags.json b/modules-available/webinterface/lang/de/template-tags.json index ed7e9f00..ae174e91 100644 --- a/modules-available/webinterface/lang/de/template-tags.json +++ b/modules-available/webinterface/lang/de/template-tags.json @@ -1,12 +1,14 @@ { - "lang_acmeCreateNewHint": "Hier k\u00f6nnen Sie ein Zertifikat via ACMEv2 erzeugen lassen. Daf\u00fcr ist es erforderlich, dass der Satellitenserver auf Port 80 erreichbar ist, genauer gesagt der Pfad \"\/.well-known\/acme-challenge\". Eine Ausnahme bietet hier GEANT\/Sectigo, welches durch die Verwendung eines Accounts an bestimmte Domains gebunden werden kann, und dann keine HTTP-Verifizierung erfordert.", + "lang_acmeCreateNewHint": "Hier k\u00f6nnen Sie ein Zertifikat via ACMEv2 erzeugen lassen. Daf\u00fcr ist es ggf. erforderlich, dass der Satellitenserver auf Port 80 erreichbar ist, genauer gesagt der Pfad \"\/.well-known\/acme-challenge\". Bei Verwendung einiger Anbieter f\u00e4llt diese Anforderung ggf. weg, falls mit Zugangsdaten gearbeitet wird.", "lang_acmeDomains": "Anzufordernde Domains (eine pro Zeile)", "lang_acmeHmacKey": "HMAC-Key (optional)", "lang_acmeKeyId": "Key ID (optional)", - "lang_acmeKidKeyHint": "Sofern der Anbieter die Verwendung eines Kontos (\"external account binding\") erfordert (GEANT), geben Sie hier die Daten in Form der Key ID und des HMAC-Keys ein.", + "lang_acmeKidKeyHint": "Sofern der Anbieter die Verwendung eines Kontos (\"external account binding\") erfordert (Harica\/GEANT), geben Sie hier die Daten in Form der Key ID und des HMAC-Keys ein.", "lang_acmeMail": "Die E-Mail-Adresse des Zust\u00e4ndigen", "lang_acmeProvider": "Zu verwendender Anbieter", "lang_acmeSelected": "Das aktuelle Zertifikat wurde via ACME erstellt.", + "lang_acmeServerUrl": "ACME Server", + "lang_acmeServerUrlHint": "Die URL zum ACME-Endpoint des zu verwendenden ACME Service Providers.", "lang_acmeWipeAll": "Alle hinterlegten Daten l\u00f6schen und Account etc. von neuem anfordern", "lang_acmeWipeAllHint": "W\u00e4hlen Sie diese Option aus, wenn das Zertifikat nicht verl\u00e4ndert werden kann, oder es Probleme beim \u00c4ndern und Speichern der Daten gibt. Beachten Sie, dass einige Anbieter ein Ratelimit haben, Sie also nicht zu oft\/schnell hintereinander ein neues Zertifikat anfordern sollten.", "lang_apiSelected": "Das aktuelle Zertifikat wurde mittels API-Zugriff eingespielt.", diff --git a/modules-available/webinterface/lang/en/messages.json b/modules-available/webinterface/lang/en/messages.json index f938552a..b4083ec3 100644 --- a/modules-available/webinterface/lang/en/messages.json +++ b/modules-available/webinterface/lang/en/messages.json @@ -1,5 +1,6 @@ { "acme-invalid-provider": "Invalid ACME provider: {{0}}", + "acme-invalid-url": "Invalid URL: {{0}}", "acme-no-domains": "No domains specified", "acme-no-mail": "No technical mail contact specified", "acme-no-provider": "No ACME provider selected", diff --git a/modules-available/webinterface/lang/en/template-tags.json b/modules-available/webinterface/lang/en/template-tags.json index b3afb43f..d282c582 100644 --- a/modules-available/webinterface/lang/en/template-tags.json +++ b/modules-available/webinterface/lang/en/template-tags.json @@ -1,12 +1,14 @@ { - "lang_acmeCreateNewHint": "Here you can create a certificate using an ACMEv2 provider. This requires making this server accessible on port 80, more specifically the path \"\/.well-known\/acme-challenge\" needs to be accessible via HTTP. An exeption is GEANT\/Sectigo, which uses accounts that are verified for certain domains and don't require any verification via HTTP.", + "lang_acmeCreateNewHint": "Here you can create a certificate using an ACMEv2 provider. This usually requires making this server accessible on port 80, more specifically the path \"\/.well-known\/acme-challenge\" needs to be accessible via HTTP. When using a provider that requires access credentials, this requirement might not apply.", "lang_acmeDomains": "Domains to request (one per line)", "lang_acmeHmacKey": "HMAC-KEY (options)", "lang_acmeKeyId": "Key ID (optional)", - "lang_acmeKidKeyHint": "If the provider requires an account (\"external account binding\"), i.e. GEANT, please specify it here.", + "lang_acmeKidKeyHint": "If the provider requires an account (\"external account binding\"), i.e. Harica\/GEANT, please specify it here.", "lang_acmeMail": "Technical contact e-mail address", "lang_acmeProvider": "Provider to use", "lang_acmeSelected": "Current certificate was generated via ACME.", + "lang_acmeServerUrl": "ACME server", + "lang_acmeServerUrlHint": "URL of ACME endpoint of SSL prodiver to use.", "lang_acmeWipeAll": "Wipe all existing data and request everything anew", "lang_acmeWipeAllHint": "Select this option if you experience trouble renewing an existing certificate, or if a previous registration attempt left stale data. Please be aware that rate limits apply with some providers, so you shouldn't issue too many requests over a short period of time.", "lang_apiSelected": "Current certificate was supplied via API access.", diff --git a/modules-available/webinterface/page.inc.php b/modules-available/webinterface/page.inc.php index fb982616..880a67b8 100644 --- a/modules-available/webinterface/page.inc.php +++ b/modules-available/webinterface/page.inc.php @@ -153,6 +153,7 @@ class Page_WebInterface extends Page $data['acmeMail'] = Acme::getMail(); $data['acmeDomains'] = $domains; if (User::hasPermission("edit.https")) { + $data['acmeServerUrl'] = Acme::getServerUrl(); $data['acmeKeyId'] = Acme::getKeyId(); $data['acmeHmacKey'] = Acme::getHmacKey(); $data['httpsApiKey'] = WebInterface::getApiKey(); @@ -246,6 +247,7 @@ class Page_WebInterface extends Page $wipeAll = Request::post('acme-wipe-all', false, 'bool'); // Get params $provider = Request::post('acme-provider', Request::REQUIRED, 'string'); + $serverUrl = Request::post('acme-server-url', null, 'string'); $mail = Request::post('acme-mail', Request::REQUIRED, 'string'); $domains = Request::post('acme-domains', Request::REQUIRED, 'string'); $kid = Request::post('acme-kid', null, 'string'); @@ -265,6 +267,7 @@ class Page_WebInterface extends Page // First, try to revive existing config/certs if parameters didn't change if (!$wipeAll && $provider === Acme::getProvider() + && ($provider !== 'custom' || $serverUrl === Acme::getServerUrl()) && $mail === Acme::getMail() && $kid === Acme::getKeyId() && $hmac === Acme::getHmacKey() @@ -274,7 +277,7 @@ class Page_WebInterface extends Page return null; // Nothing to do, old setup works return Acme::renew(); // Hope for the best, otherwise user needs to check "force reissue" } - if (!Acme::setConfig($provider, $mail, $kid, $hmac)) + if (!Acme::setConfig($provider, $mail, $serverUrl, $kid, $hmac)) return null; // Will generate error messages in this case Acme::setDomains($domains); return Acme::issueNewCertificate($wipeAll); diff --git a/modules-available/webinterface/templates/https.html b/modules-available/webinterface/templates/https.html index a93b8fb8..0580965a 100644 --- a/modules-available/webinterface/templates/https.html +++ b/modules-available/webinterface/templates/https.html @@ -179,6 +179,27 @@ MIIFfTCCA... {{/acmeProviders}} </select> </div> + <div class="form-group collapse" id="acme-server-url-group"> + <label for="acme-server-url">{{lang_acmeServerUrl}}</label> + <input class="form-control" name="acme-server-url" id="acme-server-url" value="{{acmeServerUrl}}"> + <i>{{lang_acmeServerUrlHint}}</i> + </div> + <script> + document.addEventListener('DOMContentLoaded', function () { + var $ap = $('#acme-provider'); + var $serverUrlGroup = $('#acme-server-url-group'); + var cf = function () { + var provider = $ap.val(); + if (provider === 'custom') { + $serverUrlGroup.show(); + } else { + $serverUrlGroup.hide(); + } + }; + $ap.change(cf); + cf(); + }); + </script> <div class="form-group"> <label for="acme-mail">{{lang_acmeMail}}</label> <input class="form-control" name="acme-mail" id="acme-mail" value="{{acmeMail}}"> |
