Hi all, I installed ISPconfig-3.2.11p2 on my LOCAL LAN. To be able to get a Let's Encrypt certificate I have to use the script .acme.sh with DNS challenges, Code: # Delete the original .acme.sh folder to have no original parameters, rm -rf /root/.acme.sh # Install a new .acme.sh curl https://get.acme.sh | sh -s [email protected] # Display the version /root/.acme.sh/acme.sh -v # Display the email address cat /root/.acme.sh/account.conf | grep ACCOUNT_EMAIL # To make sure the CA is Let's Encrypt /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt # Export the Globel API key from Cloudflare.com export CF_Key="Cloudflare-Global-API-key" # Export the mail address used to connect to Cloudflare.co, export CF_Email="Email-Address-For-Cloudflare" # Display the exported variables env | grep -i CF_ # - Using TEST certificates until I am sure that all is working correctly # and to make sure to not reach the 5/7 limit of Let's Encrypt. # - I am using the DNS challenges with Cloudflare. # - All the DNS record are set at Cloudflare with Proxy disabled. # - The FQDN of the ISPconfig server is: debian.toto.org. # - Requesting a TEST certificate. /root/.acme.sh/acme.sh \ --issue \ --dns dns_cf \ -d debian.toto.org \ -d toto.org \ -d mail.toto.org \ -d smtp.toto.org \ -d imap.toto.org \ -d wap.toto.org \ -d www.toto.org \ --keylength 4096 \ --cert-file /usr/local/ispconfig/interface/ssl/ispserver.crt \ --ca-file /usr/local/ispconfig/interface/ssl/ispserver.pem \ --key-file /usr/local/ispconfig/interface/ssl/ispserver.key \ --reloadcmd "/usr/sbin/service pure-ftpd-mysql restart; \ /usr/sbin/service postfix restart; \ /usr/sbin/service dovecot restart; \ /usr/sbin/service mysql restart; \ /usr/sbin/service apache2 restart" \ --log '/var/log/ispconfig/acme.log' \ --test \ --force This is working as I am able to connect to the ISPconfig control panel and the certificate displayed is this TEST one from Let's Encrypt. Question: Should I put the reload commands in a bash script in the /root/.acme.sh/deploy folder to make sure the renewal of the certificate will deploy the certifiate files in the right place? My next step will be to get a Let's Encrypt certificate for the web site... Any suggestion or comment appreciated, Michel-André
for one: do not do this on the ispconfig host, use a seperate host. also, you could use -d *.toto.org for a wildcard cert instead of putting so many subdomains in it. Having one cert for many domains enlarges a security risk on the other hand. you could ask chatgpt to write a reloadcmd which would update websites ssl cert using ispconfig api and it would 99% work but don't blame ispconfig or me if not in fact, this is what chatgpt got me for a simple bash variant, might need some testing and looking at the api but it should be adaptable. jq and curl might need to be installed as dependency Code: #!/bin/bash API_URL="https://your-ispconfig-api-url:8080/remote/json.php" USERNAME="your-api-username" PASSWORD="your-api-password" DOMAIN=$1 if [ -z "$DOMAIN" ]; then echo "Usage: $0 <domain>" exit 1 fi CERT_FILE_PATH="/path/to/certificates/${DOMAIN}.crt" KEY_FILE_PATH="/path/to/private-keys/${DOMAIN}.key" CA_FILE_PATH="/path/to/ca-bundles/${DOMAIN}.ca-bundle.crt" # Function to log in and get session ID get_session_id() { local response=$(curl -s -X POST -d "{ \"username\": \"$USERNAME\", \"password\": \"$PASSWORD\", \"method\": \"session_login\" }" $API_URL) echo $(echo $response | jq -r '.response') } # Function to get the domain ID get_domain_id() { local session_id=$1 local response=$(curl -s -X POST -d "{ \"session_id\": \"$session_id\", \"method\": \"sites_web_domain_get_all\", \"params\": [{}] }" $API_URL) echo $(echo $response | jq -r --arg DOMAIN "$DOMAIN" '.response[] | select(.domain == $DOMAIN) | .domain_id') } # Function to update SSL certificates update_ssl_certificates() { local session_id=$1 local domain_id=$2 local cert_content=$(cat $CERT_FILE_PATH) local key_content=$(cat $KEY_FILE_PATH) local ca_content=$(cat $CA_FILE_PATH) local response=$(curl -s -X POST -d "{ \"session_id\": \"$session_id\", \"method\": \"sites_web_domain_update\", \"params\": [{ \"domain_id\": \"$domain_id\", \"ssl\": \"y\", \"ssl_cert\": \"$cert_content\", \"ssl_key\": \"$key_content\", \"ssl_bundle\": \"$ca_content\" }] }" $API_URL) echo $response } # Main script execution SESSION_ID=$(get_session_id) if [ -z "$SESSION_ID" ]; then echo "Failed to login and get session ID" exit 1 fi DOMAIN_ID=$(get_domain_id $SESSION_ID) if [ -z "$DOMAIN_ID" ]; then echo "Failed to get domain ID for $DOMAIN" exit 1 fi UPDATE_RESPONSE=$(update_ssl_certificates $SESSION_ID $DOMAIN_ID) echo "Update Response: $UPDATE_RESPONSE" # Log out to clean up session curl -s -X POST -d "{ \"session_id\": \"$SESSION_ID\", \"method\": \"session_logout\" }" $API_URL echo "SSL certificates updated successfully for $DOMAIN"
Hi ztk.me, Thank you for your reply. Very interesting script. I am on a Proxmox VE virtual machine so there is no limit to return to a snapshot. I installed jq. I created a remote user. I ajusted the .acme.sh script to use only *.toto.org as you suggested. I created a reloadcmd and ajusted it for *.toto.org etc. After a few trials with the reloadcmd I checked all the functions for the remote user. There is something wrong with the "session_id". I added echo "THE SESSION ID is => "$session_id in a few places but it is always empty. I added an "echo" for $USERNAME, $PASSWORD, $DOMAIN, $CERT_FILE_PATH, $KEY_FILE_PATH, $CA_FILE_PATH, $0, $1, and all looked OK. Code: [root@debian ~]# ./mar-reloadcmd.sh toto.org DISPLAY DOMAIN => toto.org DISPLAY USERNAME => deploy DISPLAY PASSWORD => deploy-password DISPLAY CERT_FILE_PATH => /root/.acme.sh/toto.org/toto.org.cer DISPLAY KEY_FILE_PATH => /root/.acme.sh/toto.org/toto.org.key DISPLAY CA_FILE_PATH => /root/.acme.sh/toto.org/fullchain.cer DISPLAY response => DISPLAY username => DISPLAY password => DISPLAY method => DISPLAY API_URL => https://toto.org:8080/remote/json.php AFTER => # Function to log in and get session ID AFTER => # Function to get the domain ID THIS IS THE SESSION ID => AFTER => # Function to update SSL certificates Next line is: ***** DOMAIN_ID=$(get_domain_id $SESSION_ID) ***** jq: error (at <stdin>:1): Cannot iterate over boolean (false) Failed to get domain ID for toto.org [root@debian ~]# The error is from those lines. Code: if [ -z "$DOMAIN_ID" ]; then echo "Failed to get domain ID for $DOMAIN" exit 1 fi Because of this function returning nothing: Code: # Function to log in and get session ID get_session_id() { local response=$(curl -s -X POST -d "{ \"username\": \"$USERNAME\", \"password\": \"$PASSWORD\", \"method\": \"session_login\" }" $API_URL) echo $(echo $response | jq -r '.response') } I tried with *.toto.org, debian.toto.org, and toto.org. I alway have the same error. Any suggestion or comment appreciated, Michel-André
Please be aware that you can not set the central ISPConfig SSL cert using remote API, so the script that @ztk.me posted will not do what you did in post #1. Your approach from first post should be generally ok for that cert. The only thing that I would change is to create a bash script for reloading the services instead of putting all commands in the --reloadcmd option of acme.sh and that it might be useful to create a wilcard cert, like @ztk.me mentioned. Such a remote API script could only be used to set an external SSL cert statically for a website, and that script looks a bit crude to me anyway as one would e.g. not use sites_web_domain_get_all to get the ID for a domain, even if it might work, just not really effective on larger systems. Normally one would use sites_web_domain_get function and pass an array 'domain' => 'domain.tld' as primary_id parameter. The problem with ChatGPT and ISPConfig is that most answers from ChatGPT regarding ISPConfig are incorrect, at least what I found out testing it a few months ago, so it would not surprise me if it would get a remote API script right on its own.
Hi Till, Thank you so much for your support. I will ajust the acme.sh script to reflect you suggestion and use *.toto.org as ztk.me recommended. I will post the results when everything will be workiing properly. I now have confidence that my documentation project for using ISPconfig as a home server has real good prospects for success, Michel-André
exactly, chatgpt can help getting an idea sort of structured, but one should always question the output. Actually it did not try the domains_get_all before, I just remembered a forum thread where this was an issue that one could not add a domain name as argument, so I made it correct itself Also yes, this will not secure the ispconfig or server certificate. One could use those set certificates for other places,. too w/o having a maybe in the future conflicting ispconfig setup? This was the first code, which I thought could not work Code: #!/bin/bash API_URL="https://your-ispconfig-api-url:8080/remote/json.php" USERNAME="your-api-username" PASSWORD="your-api-password" DOMAIN=$1 if [ -z "$DOMAIN" ]; then echo "Usage: $0 <domain>" exit 1 fi CERT_FILE_PATH="/path/to/certificates/${DOMAIN}.crt" KEY_FILE_PATH="/path/to/private-keys/${DOMAIN}.key" CA_FILE_PATH="/path/to/ca-bundles/${DOMAIN}.ca-bundle.crt" # Function to log in and get session ID get_session_id() { local response=$(curl -s -X POST -d "{ \"username\": \"$USERNAME\", \"password\": \"$PASSWORD\", \"method\": \"session_login\" }" $API_URL) echo $(echo $response | jq -r '.response') } # Function to get domain ID get_domain_id() { local session_id=$1 local response=$(curl -s -X POST -d "{ \"session_id\": \"$session_id\", \"method\": \"sites_web_domain_get\", \"params\": [{\"primary_id\": \"$DOMAIN\"}] }" $API_URL) echo $(echo $response | jq -r '.response[0].domain_id') } # Function to update SSL certificates update_ssl_certificates() { local session_id=$1 local domain_id=$2 local cert_content=$(cat $CERT_FILE_PATH) local key_content=$(cat $KEY_FILE_PATH) local ca_content=$(cat $CA_FILE_PATH) local response=$(curl -s -X POST -d "{ \"session_id\": \"$session_id\", \"method\": \"sites_web_domain_update\", \"params\": [{ \"domain_id\": \"$domain_id\", \"ssl\": \"y\", \"ssl_cert\": \"$cert_content\", \"ssl_key\": \"$key_content\", \"ssl_bundle\": \"$ca_content\" }] }" $API_URL) echo $response } # Main script execution SESSION_ID=$(get_session_id) if [ -z "$SESSION_ID" ]; then echo "Failed to login and get session ID" exit 1 fi DOMAIN_ID=$(get_domain_id $SESSION_ID) if [ -z "$DOMAIN_ID" ]; then echo "Failed to get domain ID" exit 1 fi UPDATE_RESPONSE=$(update_ssl_certificates $SESSION_ID $DOMAIN_ID) echo "Update Response: $UPDATE_RESPONSE" # Log out to clean up session curl -s -X POST -d "{ \"session_id\": \"$SESSION_ID\", \"method\": \"session_logout\" }" $API_URL echo "SSL certificates updated successfully for $DOMAIN"
Agreed. Plus you can also use some of code from the ISPConfig installer which adds/uses LE hook scripts instead, to achieve the same objective. The ISPConfig LE hook scripts are customizable and would only need to copy them from /conf to /conf-custom for such customization. With that, you don't need other scripts.
Hi all, As wriiten above: Code: **** FOR ISPconfig 3.2.11p2 ON A LOCAL LAN **** 2024-06-13 22h00 Corrected some typos in comments. #### CREATE DNS RECORDS AT YOUR DNS REGISTRAR #### AT THE DEVICE CONNECTED TO THE INTERNET # CREATE REDIRECTIONS TO YOUR LOCAL ISPconfig SERVER #### IN ISPconfig # System | Server config | Web tab | Settings # Skip Lets Encrypt Check: => Checked. # Sites | Your domain | Domain tab # Let's Encrypt: => Not Checked. # Sites | Your domain | SSL tab # Domaine SSL: => *.your-domain.tld # DNS # Create your-domain.tld ZONE Code: ** AT THE ISPconfig SERVER CONSOLE ** #### Delete /root/.acme.sh to delete all previous parameters #### Install .acme.sh #### REQUEST 2 CERTIFICATES: #### = ONE FOR ISPconfig #### - ONE FOR THE WEB DOMAIN # Set default CA to Let's Encrypt /root/.acme.sh/acme.sh --set-default-ca --server letsencrypt # Export Global API from Cloudflare export CF_Key="abcdefghijklmnopqrestuvwxyz0123456789" # Export login email used to connect to Cloudflare export CF_Email="[email protected]" # Verify exported variables env | grep -i CF_ #### CERTIFICATE FOR ISPCONFIG ## Request a TEST certificate # FQDN domain has to be the first one # - EXAMPLE: debian.toto.org # The reloadcmd: "/root/reload.sh FQDN" # - EXAMPLE: "/root/reload.sh debian.toto.org" ## Request a PRODUCTION certificate by REMOVING line: "--test \" #### CERTIFICATE FOR WEBSITE ## Request a TEST certificate # Website domain has to be the first one # - EXAMPLE: toto.org # The reloadcmd: "/root/reload.sh Domain ClientNumber WebNumber" # - EXAMPLE: "/root/reload.sh toto.org client1 web1" ## Request a PRODUCTION certificate by REMOVING line: "--test \" # /root/.acme.sh/acme.sh \ --issue \ --dns dns_cf \ -d debian.toto.org \ -d toto.org \ -d mail.toto.org \ -d smtp.toto.org \ -d imap.toto.org \ -d wap.toto.org \ -d www.toto.org \ --keylength 4096 \ --reloadcmd "/root/reload.sh debian.toto.org" \ --log '/var/log/ispconfig/acme.log' \ --test \ --force NOTE: --renew-hook <command> Command to be run after each successfully renewed certificate. This parameter does nothing in a request except putting the BASE64 name of the command in the conf file. Might be interesting, as a line in the certificate requests, to add for ISPconfig: --renew-hook "/root/reload.sh debian.toto.org" for Website: --renew-hook "/root/reload.sh toto.org client1 web1" Below is the "reload.sh" I am using for the "reloadcmd". It should work for any FQDN and any website domain. All is working correctly and no problem with a Thunderbird account. I am not a programmer, but the script is working. Code: #!/bin/bash #### Routine for managing files from a Let's Encrypt certificate # Author: Michel-André Robillard # Website: https://www.micronator.org/affaires # Date: 2024-06-12 @ 22h17 # © 2024 RF-232 Any reproduction prohibited. # I should put the © like BSD, it will be better. #### USAGE for ISPconfig certificate # /root/reload.sh <FQDN> # # EXAMPLE: # /root/reload.sh debian.toto.org # If the certificate is for ISPconfig. if [ "$1" = "$(hostname -f)" ]; then HostName=$(hostname -f); # Folder of Let's Encrypt files. RepertoireLE=/root/.acme.sh/$HostName ; # Directory of ISPconfig certificate files for the manager. RepertoireSSL="/usr/local/ispconfig/interface/ssl" ; # We save the contents of the ISPconfig files directory # for the manager: /usr/local/ispconfig/interface/ssl # If the backup directory does not exist, we create it. DirDeSauvegarde="/tmp/Sauvegarde"; if [ ! -d "$DirDeSauvegarde" ]; then mkdir $DirDeSauvegarde; fi # We save the contents of the /usr/local/ispconfig/interface/ssl # directory in /tmp/Backup. cp $RepertoireSSL/* $DirDeSauvegarde; # In the SSL Directory, we delete all the content. rm -rf $RepertoireSSL/*; # In the SSL Directory, we bring back the Diffie-Hellman exchange # key file "dhparam4096.pem". cp $DirDeSauvegarde/dhparam4096.pem $RepertoireSSL; # We copy the new Let's Encrypt files into the SSL Directory. # CERTIFICATE => .acme.sh: .cer => ssl: ispserver.crt. # PRIVATE KEY => .acme.sh: .key => ssl: ispserver.key. #INTERMEDIATE CERTIFICATE => .acme.sh: .key + .fullchain.cer => ispserver.pem. cp $RepertoireLE/$HostName.cer $RepertoireSSL/ispserver.crt; cp $RepertoireLE/$HostName.key $RepertoireSSL/ispserver.key; cat $RepertoireLE/$HostName.key $RepertoireLE/fullchain.cer > $RepertoireSSL/ispserver.pem; # We protect the PRIVATE KEY /usr/bin/chmod 600 $RepertoireSSL/ispserver.key # If the dhparam4096.pem file has been recovered, # we can delete the backup directory in /tmp. if [ -f $DirDeSauvegarde/dhparam4096.pem ]; then rm -rf $DirDeSauvegarde; else exit 1; fi # Warning message printf \\n"The command "reload.sh" may take a few seconds."\\n; # We launch the command to restart the server services. if [ $(dpkg-query -W -f='${Status}' pure-ftpd-mysql 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service pure-ftpd-mysql restart; fi if [ $(dpkg-query -W -f='${Status}' monit 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service monit restart; fi if [ $(dpkg-query -W -f='${Status}' postfix 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service postfix restart; fi if [ $(dpkg-query -W -f='${Status}' dovecot-imapd 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service dovecot restart; fi if [ $(dpkg-query -W -f='${Status}' mysql 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service mysql restart; fi if [ $(dpkg-query -W -f='${Status}' mariadb 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service mysql restart; fi if [ $(dpkg-query -W -f='${Status}' nginx 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service nginx restart; fi if [ $(dpkg-query -W -f='${Status}' apache2 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service apache2 restart; fi # All is done printf \\n"The new Let\'s Encrypt certificate for ISPconfig manager \nof the server \"$HostName\" was installed successfully."\\n\\n; exit 0; fi; ############################################################################### ############################################################################### #### Routine for managing website certificate files. # USAGE => /root/reload.sh NomDuSiteWeb NumeroDuClient NumeroDuWeb # # EXAMPLE: # /root/reload.sh /root/reload.sh toto.org client1 web1 # Is this a certificate from a website if yes, # we check the existence of 3 arguments otherwise, # Exit the script indicating an error in its execution. if [ $# -eq 3 ]; then test=OK else echo -e \\n"The number of arguments is not correct" \\n; echo -e "USAGE: /root/test.sh NomDuSiteWeb NumeroDuClient NumeroDuWeb" \\n; # Exit the script indicating an error in its execution. exit 1; fi # Checking if the 2nd argument starts with "client". TEXTE=$2; PREFIXE=client; if [[ $TEXTE =~ ^"$PREFIXE" ]]; then test=OK else echo -e "The customer number does not begin with: \"client\", but by" \"$TEXTE\" \\n; echo -e "USAGE: /root/test.sh NomDuSiteWeb NumeroDuClient NumeroDuWeb" \\n; # Exit the script indicating an error in its execution. exit 1; fi if [ [$2] ]; then test=3 else echo -e "USAGE: /root/test.sh NomDuSiteWeb NumeroDuClient NumeroDuWeb" \\n; # Exit the script indicating an error in its execution. exit 1; fi # Initialization of variables. HostName=$1; # The Let's Encrypt files directory. RepertoireLE=/root/.acme.sh/$HostName ; # Customer number. NoClient=$2; # Website number. NoWeb=$3; # Client Directory. RepertoireClient="/var/www/clients/$NoClient/$NoWeb/ssl" ; # We go to the website client certificate file directory. cd $RepertoireClient; #Content of the directory. ContenuDuRepClient=*.*; # We create a backup directory and save all the client certificate files in it. mkdir -p /tmp/Sauvegarde; cp $ContenuDuRepClient /tmp/Sauvegarde; # We delete the contents of the website's certificate directory. rm -rf $ContenuDuRepClient; ## Description of website certificate files. # toto.org.key => toto.org.key # toto.org.csr => toto.org.csr # fullchain.cer + ca.cer => toto.org.crt cp $RepertoireLE/$HostName.key $RepertoireClient/'*.'$HostName.key cp $RepertoireLE/$HostName.csr $RepertoireClient/'*.'$HostName.csr cat $RepertoireLE/fullchain.cer $RepertoireLE/ca.cer > $RepertoireClient/'*.'$HostName.crt # Warning message printf \\n"The command \"reload.sh\" may take a few seconds."\\n\\n; ## Command "reload" if [ $(dpkg-query -W -f='${Status}' pure-ftpd-mysql 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service pure-ftpd-mysql restart; fi if [ $(dpkg-query -W -f='${Status}' monit 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service monit restart; fi if [ $(dpkg-query -W -f='${Status}' postfix 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service postfix restart; fi if [ $(dpkg-query -W -f='${Status}' dovecot-imapd 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service dovecot restart; fi if [ $(dpkg-query -W -f='${Status}' mysql 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service mysql restart; fi if [ $(dpkg-query -W -f='${Status}' mariadb 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service mysql restart; fi if [ $(dpkg-query -W -f='${Status}' nginx 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service nginx restart; fi if [ $(dpkg-query -W -f='${Status}' apache2 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service apache2 restart; fi # All is done printf "The new Let\'s Encrypt certificate for the site \"$NoWeb\" of \"$NoClient\" \nof the domain \"$HostName\" was installed successfully."\\n\\n; exit 0; ############################################################################### ############################################################################### Any suggestion or comment appreciated, Michel-André