├── LICENSE ├── README.md ├── deploy.sh └── ubios-cert ├── ubios-cert.env └── ubios-cert.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 Alexander Wolf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Valid and free TLS / SSL certificates for UniFi Consoles V4.1.13 and V3.2.x 2 | 3 | Last and final update: March 14, 2025. 4 | 5 | OK folks, sorry to disappoint but I've had it, as with the introduction of new hardware (Dream Router etc.) the foundation has been broken - again. I'm not a professional user (or programmer) and neither have time or the need to help a commercial company fixing their security. 6 | 7 | Thanks to all those who helped making this work! As of March 14, 2025, this is archived. I can only point you in the direction of Glenn R.'s [UniFi Easy Encrypt](https://glennr.nl/s/unifi-lets-encrypt) script. 8 | 9 | ## \### NO USER SERVICEABLE PARTS BEYOND THIS POINT \### 10 | 11 | *Public Service Announcement:* In its best tradition, UI has established some new, of course undocumented, black magic around valid SSL certificates. This project here tries to cope with this new twist. Web frontend and Hotspot are covered now, and WiFiMan will be toasted as soon as you use custom SSL certificates. This has been officially acknowledged by UI. Right now, RADIUS ~~will still not work~~ can be fixed manually as described [here](https://github.com/alxwolf/ubios-cert/wiki/RADIUS-certificates). 12 | 13 | With 4.1.x, UI has *again* changed the way they handle certificates, but still not implemented in a proper way. 4.1 is supported on UniFi OS - Dream Machines 4.1.13 14 | 15 | I suggest you give Glenn R.'s [monster scripts](https://glennr.nl/s/unifi-lets-encrypt) a try - if you can. He's on the [UI community forums](https://community.ui.com/questions/UniFi-Installation-Scripts-or-UniFi-Easy-Update-Script-or-UniFi-Lets-Encrypt-or-UniFi-Easy-Encrypt-/ccbc7530-dd61-40a7-82ec-22b17f027776) and obviously knows extremely well what needs to be done, but is not willing to address the request for certificates issued with DNS-Challenge. Coincidently, he seems to work for UI and there is a UI team member called *UI-Glenn*. 16 | 17 | If you're able to convince him or UI to provide proper, out of the box support for securing communication with UI devices beyond having a self-signed "unifi.local" certificate - I will archive this project the next minute. In the meantime... 18 | 19 | ## What it does 20 | 21 | Spare you and your users from certificate errors when browsing to your UniFi Console's (Dream Machine Base / Pro / SE / R) administrative web frontend, Hotspot Portal ~~and RADIUS server~~. 22 | 23 | **TL;DR** jump to [Installation](#installation) 24 | 25 | It will install Neilpang's [`acme.sh`](https://github.com/acmesh-official/acme.sh), is extremely light as it runs on bare metal and survives (until further notice...) reboots and firmware upgrades (at least for minor revisions). 26 | 27 | With that, it will 28 | 29 | * issue TLS (aka SSL) certificates for a domain (with Subject Alternate Names or wildcards) you own, using ([Let's Encrypt](https://letsencrypt.org) (LE), and other [supported certification authorities](https://github.com/acmesh-official/acme.sh#supported-ca), 30 | * use the DNS-01 challenge, so you don't have be present on the Internet with open ports 80 and 443, 31 | * renew your certificate automatically every 60 days. 32 | 33 | ## Discontinued support for firmwares < v4.x 34 | 35 | This branch serves the most current firmware(s). 36 | 37 | If you're still running a V1.x (why would you...), please have a look at branch [v1.x](https://github.com/alxwolf/ubios-cert/blob/V1.x/README.md) - which is no longer supported (at least not by me due to lack of hardware). 38 | 39 | If you're on V2.x to anything before V3.2.7, check branch [V2-to-V3.1](https://github.com/alxwolf/ubios-cert/tree/V2-to-V3.1). 40 | 41 | ## Currently supported DNS API providers 42 | 43 | Over 150, check [acme.sh DNS API](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) Wiki for details. 44 | 45 | ## But why? 46 | 47 | In most private installations, the UniFi console will live behind a router / firewall provided by an ISP, and we don't want to open HTTP(S) ports 80 and 443 to the interested public. 48 | 49 | ## What you need 50 | 51 | * A UniFi Console with firmware V4.x, 52 | * a registered domain where you have API access for running the DNS-01 API challenge, 53 | * the awareness you might break something. 54 | 55 | ## Installation 56 | 57 | ### Download the package 58 | 59 | * `ssh` into your UDM 60 | * Download the archive to your home directory and unzip it 61 | 62 | ```sh 63 | cd 64 | curl -L https://github.com/alxwolf/ubios-cert/archive/main.zip > ubios-cert.zip 65 | unzip ubios-cert.zip 66 | cd ubios-cert-main 67 | chmod +x deploy.sh 68 | ``` 69 | 70 | * [Make your adjustments](#make-your-adjustments) to `ubios-cert.env` 71 | 72 | ```sh 73 | vi ubios-cert/ubios-cert.env 74 | ``` 75 | 76 | * Deploy the files to their proper place 77 | 78 | ```sh 79 | ./deploy.sh 80 | ``` 81 | 82 | * Navigate to the deployment folder and issue your certificate for the first time 83 | 84 | ```sh 85 | cd /data/ubios-cert 86 | ./ubios-cert.sh initial 87 | ``` 88 | 89 | ### Make your adjustments 90 | 91 | Adjust file [`ubios-cert.env`](./ubios-cert/ubios-cert.env) to your needs. 92 | 93 | First, define your certificate names and CA by adjusting 94 | 95 | ```sh 96 | ####################################### 97 | # Configure certificates and provider # 98 | ####################################### 99 | 100 | # The FQDN of your UniFi Console (comma separated fqdns and wildcards are supported) 101 | CERT_HOSTS='domain.com,*.domain.com' 102 | 103 | # Email address for registration 104 | CA_REGISTRATION_EMAIL='user@domain.com' 105 | 106 | # Default CA: https://github.com/alxwolf/ubios-cert/wiki/acme.sh:-choosing-the-default-CA 107 | DEFAULT_CA="letsencrypt" 108 | ``` 109 | 110 | Second, 111 | 112 | ```sh 113 | ################################################# 114 | # Select services to provide the certificate to # 115 | ################################################# 116 | 117 | # Enable updating Hotspot Portal certificate 118 | # this will break WiFiMan 100% as of v3.2.7 119 | # provide options 'yes' or 'no' in lowercase 120 | ENABLE_HOTSPOT='yes' 121 | 122 | # Enable updating Radius support 123 | # provide options 'yes' or 'no' in lowercase 124 | ENABLE_RADIUS='yes' 125 | 126 | ``` 127 | 128 | Third, select your DNS API provider by adjusting the variable `DNS_API_PROVIDER="dns_xxx"`. 129 | 130 | `dns_xxx` must be replaced with the `--dns` parameter from your provider's [acme.sh DNS API](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) Wiki entry. 131 | 132 | So for CloudFlare this would say 133 | 134 | ```sh 135 | export DNS_API_PROVIDER="dns_cf" 136 | ``` 137 | 138 | Some APIs may require additional manual preparation, please check the [Wiki](https://github.com/alxwolf/ubios-cert/wiki). 139 | 140 | Advanced: you can pass additional command line options to `acme.sh` by editing environment variable `ACMESH_CMD_PARAMS`. 141 | 142 | ## First Run 143 | 144 | Consider making a backup copy of your [current certificate and key](https://github.com/alxwolf/ubios-cert/wiki/Certificate-locations-on-UDM(P)) before moving on. 145 | 146 | ```sh 147 | mkdir /data/ubios-cert/certbackup 148 | cd /data/ubios-cert/certbackup 149 | cp /data/unifi-core/config/unifi-core.key ./unifi-core.key_orig 150 | cp /data/unifi-core/config/unifi-core.crt ./unifi-core.crt_orig 151 | cp /data/udapi-config/raddb/certs/server.pem ./raddb-server.pem 152 | cp /data/udapi-config/raddb/certs/server-key.pem ./raddb-server-key.pem 153 | ``` 154 | 155 | Calling the script with `sh /data/ubios-cert/ubios-cert.sh initial` will 156 | 157 | * setup up the trigger for persistence over reboot / firmware upgrades 158 | * establish a cron job to take care about your certificate renewals 159 | * register an account with your email 160 | * issue a certificate (with SANs, if you like) 161 | * deploy the certificate to your network controller (and captive portal, if you selected that) 162 | * restart the unifi-os 163 | 164 | ## Certificate Renewal 165 | 166 | Should be fully automated, done via a daily `cron` job. You can trigger a manual renewal by running `sh /data/ubios-cert/ubios-cert.sh renew`, which may be useful for debugging. If `acme.sh` fails, check if you hit the [rate limits](https://letsencrypt.org/docs/rate-limits/). 167 | 168 | The certificate can be force-renewed by running `sh /data/ubios-cert/ubios-cert.sh force-renew`. 169 | 170 | ## Behaviour after firmware upgrade / reboot 171 | 172 | Survived reboots and firmware updates, including release change from V3 to V4. 173 | 174 | ## De-installation and de-registration 175 | 176 | `ssh` into your UDM. Calling the script with `sh /data/ubios-cert/ubios-cert.sh cleanup` will 177 | 178 | * Remove the cron file from `/etc/cron.d` 179 | * Remove the (most recently issued) domains from the Let's Encrypt account 180 | * De-activate the Let's Encrypt account 181 | 182 | Then, you can delete the script directory. As always, be careful with `rm`. 183 | 184 | ```sh 185 | cd /data/ 186 | ./ubios-cert/ubios-cert.sh cleanup 187 | rm -irf ./ubios-cert 188 | ``` 189 | 190 | ## Selecting the default CA 191 | 192 | `acme.sh` can access different CAs. [You can select which CA you want it to use](https://github.com/alxwolf/ubios-cert/wiki/acme.sh:-choosing-the-default-CA). The keywords are listed [here](https://github.com/acmesh-official/acme.sh/wiki/Server). Adjust the value in `ubios-cert.env` first and then call the script with `ubios-cert.sh set-default-ca`. This CA will **from now on** be applied to newly issued certificates. 193 | 194 | ## Debugging 195 | 196 | * Increase the log level in `ubios-cert.sh` by setting `LOGLEVEL="--log-level 2"` 197 | * Run `tail -f ${DATA_DIR}/ubios-cert/acme.sh/acme.sh.log`in separate terminal while running `sh ubios-cert.sh initial`, `sh ubios-cert.sh renew` or `sh ubios-cert.sh force-renew` manually 198 | 199 | ## Inspired by - Sources and Credits 200 | 201 | A huge "Thank You" goes to 202 | 203 | * [Neilpang's acme.sh](https://github.com/acmesh-official/acme.sh): the probably most convenient and most supported interface for Let's Encrypt, ZeoSSL, Buypass and SSL.com. 204 | * [llaforest](https://github.com/llaforest): for implementing the native / bare metal version of `acme.sh` 205 | * [docholliday-sc001](https://github.com/docholliday-sc001): for coming up with a fix for RADIUS certificates 206 | * [kchristensen's udm-le for UDM](https://github.com/kchristensen/udm-le): his work provides the base for both structure of implementation and content. 207 | * [Glenn R.'s Easy Encrypt script](https://glennr.nl/s/unifi-lets-encrypt) 208 | 209 | ## Known bugs and unknowns 210 | 211 | * For sure some. And a lack of tests, checks and cleanup. RADIUS certificates are still broken. 212 | 213 | ## UniFi OS and Network Controller Versions 214 | 215 | Confirmed to work on UniFi OS Version 4.1.13 and Network Version 9.0.114 216 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | SCRIPT_DIR=$(dirname ${0}) 4 | 5 | # Get the firmware version 6 | export FIRMWARE_VER=$(ubnt-device-info firmware || true) 7 | # Get the Harware Model 8 | export MODEL="$(ubnt-device-info model || true)" 9 | 10 | deploy_acmesh() { 11 | echo "acme.sh will be deployed inside ubios-cert to persist firmware updates" 12 | ACME_URL=$(curl -s https://api.github.com/repos/acmesh-official/acme.sh/releases/latest | grep tarball_url | awk '{ print $2 }' | sed 's/,$//' | sed 's/"//g') 13 | echo "Fetching latest ACME from ${ACME_URL}" 14 | curl -L "${ACME_URL}" > acmesh.tar.gz 15 | echo "Extracting ACME ${SCRIPT_DIR}/ubios-cert/acme.sh" 16 | mkdir -p "${SCRIPT_DIR}/ubios-cert/acme.sh" 17 | tar -xvf acmesh.tar.gz --directory="${SCRIPT_DIR}/ubios-cert/acme.sh" --strip-components=1 18 | } 19 | 20 | if [ $(echo ${FIRMWARE_VER} | sed 's#\..*$##g') -gt 1 ] 21 | then 22 | export DATA_DIR="/data" 23 | else 24 | echo "Unsupported firmware: ${FIRMWARE_VER}" 25 | exit 1 26 | fi 27 | 28 | if [ $(echo ${FIRMWARE_VER} | sed 's#\..1$##g') = "4.1" ] 29 | then 30 | echo "Unsupported firmware: ${FIRMWARE_VER}" 31 | exit 1 32 | fi 33 | 34 | case "${MODEL}" in 35 | "UniFi Dream Machine Pro Max"|"UniFi Dream Machine Pro"|"UniFi Dream Machine"|"UniFi Dream Router"|"UniFi Dream Machine SE"|"UniFi Express"|"UniFi Dream Wall") 36 | echo "${MODEL} running firmware ${FIRMWARE_VER} detected, installing ubios-cert in ${DATA_DIR}..." 37 | ;; 38 | *) 39 | echo "Unsupported model: ${MODEL}" 40 | exit 1 41 | ;; 42 | esac 43 | echo 44 | 45 | deploy_acmesh 46 | chmod +x ${SCRIPT_DIR}/ubios-cert/ubios-cert.sh 47 | mv "${SCRIPT_DIR}/ubios-cert/" "${DATA_DIR}/ubios-cert/" 48 | rm -rf ${SCRIPT_DIR}/../ubios-cert-main ~/ubios-cert.zip 49 | echo "Deployed with success in ${DATA_DIR}/ubios-cert" 50 | cd ${DATA_DIR}/ubios-cert 51 | -------------------------------------------------------------------------------- /ubios-cert/ubios-cert.env: -------------------------------------------------------------------------------- 1 | # 2 | # Required configuration 3 | # 4 | 5 | ####################################### 6 | # Configure certificates and provider # 7 | ####################################### 8 | 9 | # The FQDN of your UniFi Console (comma separated fqdns and wildcards are supported) 10 | CERT_HOSTS='domain.com,*.domain.com' 11 | 12 | # Email address for registration 13 | CA_REGISTRATION_EMAIL='user@domain.com' 14 | 15 | # Default CA: https://github.com/alxwolf/ubios-cert/wiki/acme.sh:-choosing-the-default-CA 16 | DEFAULT_CA="letsencrypt" 17 | 18 | ################################################# 19 | # Select services to provide the certificate to # 20 | ################################################# 21 | 22 | # Enable updating Hotspot Portal certificate 23 | # this will break WiFiMan 100% as of v3.2.7 24 | # provide options 'yes' or 'no' in lowercase 25 | ENABLE_HOTSPOT='no' 26 | 27 | # Enable updating Radius support 28 | # provide options 'yes' or 'no' in lowercase 29 | ENABLE_RADIUS='no' 30 | 31 | ############################################## 32 | # Select and configure your DNS API provider # 33 | ############################################## 34 | 35 | # GoDaddy 36 | export DNS_API_PROVIDER="dns_gd" 37 | export GD_Key="" 38 | export GD_Secret="" 39 | 40 | ############################################ 41 | # Provide additional parameters to acme.sh # 42 | ############################################ 43 | 44 | ACMESH_CMD_PARAMS="" 45 | 46 | # The following can be used to provide additional parameters for acme.sh, 47 | # e.g. a static dnssleep (https://github.com/acmesh-official/acme.sh/wiki/dnssleep) 48 | # instead of a dynamic checking via google or cloudflare dns. 49 | # ACMESH_CMD_PARAMS="--dnssleep 600" 50 | 51 | # Or to perform notifications via various channels as described 52 | # here: https://github.com/acmesh-official/acme.sh/wiki/notify, 53 | # e.g. for GChat: 54 | # export SAVED_GCHAT_WEBHOOK_URL='paste your webbook url here' 55 | # ACMESH_CMD_PARAMS="--set-notify --notify-hook gchat" 56 | 57 | ################################################ 58 | # more DNS API provider configuration examples # 59 | ################################################ 60 | 61 | # See README.md file for more details 62 | # uncomment the lines you'd like to use for DNS_API_PROVIDER and DNS_API_ENV 63 | # 64 | # A full list of DNS APIs provided by acme.sh can be found here: 65 | # https://github.com/acmesh-official/acme.sh/tree/master/dnsapi and 66 | # https://github.com/acmesh-official/acme.sh/tree/master/dnsapi2 67 | # 68 | 69 | # all-inkl.com: caution - only accepts plain text passwords 70 | # export DNS_API_PROVIDER="dns_kas" 71 | # export KAS_Login="" 72 | # export KAS_Authdata="" 73 | # export KAS_Authtype="plain" 74 | 75 | # Cloudflare 76 | # export DNS_API_PROVIDER="dns_cf" 77 | # export CF_Token= 78 | # export CF_Account_ID= 79 | 80 | # OVH 81 | # export DNS_API_PROVIDER="dns_ovh" 82 | # export OVH_AK="" 83 | # export OVH_AS="" 84 | # export OVH_CK="" 85 | 86 | #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!# 87 | # Change stuff below at your own risk # 88 | #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!# 89 | 90 | # Changing below requires changing line 11 of ubios-cert.sh 91 | UBIOS_CERT_ROOT='/data/ubios-cert' 92 | 93 | # This is where the deploy script has extracted acme.sh 94 | ACMESH_ROOT="${UBIOS_CERT_ROOT}"/acme.sh 95 | 96 | # These should only change if Unifi-OS core changes require it 97 | # Confirmed to work with Firmwares: 98 | # 4.0.20 on October 12, 2024 99 | 100 | # path to SSL certifcate location override YAML 101 | UNIFI_CORE_SSL_CONFIG="/data/unifi-core/config/overrides/custom_ssl.yaml" 102 | 103 | # Path to Application TLS certificate 104 | UNIFIOS_CERT_PATH='/data/unifi-core/config' 105 | 106 | # Path to RADIUS server certificate 107 | UBIOS_RADIUS_CERT_PATH='/data/udapi-config/raddb/certs' 108 | -------------------------------------------------------------------------------- /ubios-cert/ubios-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # based on the fine work of kchristensen/udm-le 5 | # https://github.com/kchristensen/udm-le 6 | # and 7 | # Glenn Rietveld https://GlennR.nl 8 | # 9 | 10 | # Foreground colors 11 | # 0;30 Black 1;30 Dark Gray 12 | # 0;34 Blue 1;34 Light Blue 13 | # 0;32 Green 1;32 Light Green 14 | # 0;36 Cyan 1;36 Light Cyan 15 | # 0;31 Red 1;31 Light Red 16 | # 0;35 Purple 1;35 Light Purple 17 | # 0;33 Brown 1;33 Yellow 18 | # 0;37 Light Gray 1;37 White 19 | 20 | RESET='\e[0m' 21 | YELLOW='\e[1;33m' 22 | GRAY='\e[0;37m' 23 | BLUE='\e[0;34m' 24 | RED='\e[1;31m' 25 | GREEN='\e[1;32m' 26 | 27 | unifi_core_device=$(grep -io "welcome.*" /etc/motd | sed -e 's/Welcome //g' -e 's/to //g' -e 's/the //g' -e 's/!//g'); 28 | 29 | set -e 30 | 31 | # Load environment variables 32 | . /data/ubios-cert/ubios-cert.env 33 | 34 | # Setup variables for later for those who want to tinker around 35 | LOGFILE="--log ${ACMESH_ROOT}/acme.sh.log" 36 | LOGLEVEL='--log-level 1' # default is 1, can be increased to 2 37 | LOG="${LOGFILE} ${LOGLEVEL}" 38 | 39 | # identify device firmware version: <2 is legacy (podman), 2+ is current (baremetal) 40 | 41 | IS_UNIFI_4='false' 42 | FIRMWARE_VER=$(ubnt-device-info firmware) 43 | 44 | # ugly bailout if FW >4.0 45 | if [ $(echo ${FIRMWARE_VER} | sed 's#\..1$##g') = "4.1" ] 46 | then 47 | echo "Unsupported firmware: ${FIRMWARE_VER}" 48 | exit 1 49 | fi 50 | 51 | if [ $(ubnt-device-info firmware | sed 's#\..*$##g' || true) -gt 1 ] 52 | then 53 | IS_UNIFI_4='true' 54 | echo -e "${BLUE}#${RESET} Supported firmware: ${FIRMWARE_VER} on ${unifi_core_device}. Moving on." 55 | else 56 | echo -e "${RED}#${RESET} Unsupported firmware: ${FIRMWARE_VER} on ${unifi_core_device}. Exiting." 57 | exit 1 58 | fi 59 | 60 | deploy_webfrontend() { 61 | echo -e "${BLUE}#${RESET} Checking for new certificate to be deployed to web frontend." 62 | # check if cert younger than 5 minutes 63 | if [ "$(find -L "${ACMESH_ROOT}" -type f -name fullchain.cer -mmin -5)" ]; then 64 | # beginning with 3.2.7, no need to copy the cert and key, but point in the right direction via a YAML file 65 | if [[ ! -f "${UNIFI_CORE_SSL_CONFIG}" ]]; then 66 | mkdir -p "$(dirname "${UNIFI_CORE_SSL_CONFIG}")" 67 | tee "${UNIFI_CORE_SSL_CONFIG}" &>/dev/null << SSL 68 | # File created by ubios-cert (certificates for Unifi Dream Machines). 69 | ssl: 70 | crt: '${ACMESH_ROOT}/${CERT_NAME}/fullchain.cer' 71 | key: '${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.key' 72 | SSL 73 | fi 74 | # funny enough, this still seems to be required by UniFi Protect to be able to boot up 75 | cp -f ${ACMESH_ROOT}/${CERT_NAME}/fullchain.cer ${UNIFIOS_CERT_PATH}/unifi-core.crt 76 | cp -f ${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.key ${UNIFIOS_CERT_PATH}/unifi-core.key 77 | echo -e "${GREEN}#${RESET} Certifcate deployed to UniFi OS, service not yet restarted." 78 | webfrontend_restart 79 | else 80 | echo -e "${BLUE}#${RESET} No new certificate was found." 81 | fi 82 | } 83 | 84 | deploy_hotspot_portal() { 85 | echo -e "${BLUE}#${RESET} Checking if Hotspot Portal certificate needs update." 86 | # Import the certificate for the hotspot portal 87 | if [ "${ENABLE_HOTSPOT}" = 'yes' ] && [ "$(find -L ${ACMESH_ROOT} -type f -name fullchain.cer -mmin -5)" ]; then 88 | echo -e "${BLUE}#${RESET} New certificate was generated, time to deploy it to Hotspot Portal" 89 | 90 | # mangle cert and key into P12 format 91 | if openssl pkcs12 -export -inkey ${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.key -in ${ACMESH_ROOT}/${CERT_NAME}/fullchain.cer -out ${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.p12 -name unifi -password pass:aircontrolenterprise; then 92 | echo -e "${GREEN}#${RESET} Created ${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.p12" 93 | else 94 | echo -e "${RED}#${RESET} Could not create ${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.p12" 95 | fi 96 | 97 | # remove the existing key called 'unifi' 98 | keytool -delete -alias unifi -keystore /usr/lib/unifi/data/keystore -deststorepass aircontrolenterprise 99 | 100 | # finally, import the p12 formatted cert+key of server only into keystore 101 | keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore /usr/lib/unifi/data/keystore -srckeystore "${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.p12" -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -alias unifi -noprompt 102 | 103 | # new since V3.2.7 - some flags must be set if ECDSA certificate 104 | if openssl pkcs12 -in "${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.p12" -password pass:aircontrolenterprise -nokeys ${openssl_legacy_flag} | openssl x509 -text -noout | grep -i signature | grep -iq ecdsa &> /dev/null; then 105 | echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" &>> /usr/lib/unifi/data/system.properties 106 | echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" &>> /usr/lib/unifi/data/system.properties 107 | fi 108 | 109 | # fix rights of keystore if broken 110 | chown -R unifi:unifi /usr/lib/unifi/data/keystore &> /dev/null 111 | 112 | # show what we've done 113 | echo -e "${BLUE}#${RESET} Here's the new keystore contents:" 114 | keytool -list -v -storepass aircontrolenterprise -keystore /usr/lib/unifi/data/keystore | grep "Owner" 115 | 116 | # and finally restart the UniFi Network Application 117 | networkapp_restart 118 | else 119 | echo -e "${BLUE}#${RESET} No new cert or update excluded in config file." 120 | fi 121 | } 122 | 123 | deploy_radius() { 124 | echo -e "${BLUE}#${RESET} Checking if RADIUS server certificate needs update." 125 | # Import the certificate for the RADIUS server 126 | if [ "$ENABLE_RADIUS" == "yes" ] && [ "$(find -L ${ACMESH_ROOT} -type f -name fullchain.cer -mmin -5)" ]; then 127 | echo -e "${BLUE}#${RESET} New certificate was generated, time to deploy to RADIUS server" 128 | # copy key 129 | cp -f ${ACMESH_ROOT}/${CERT_NAME}/${CERT_NAME}.key ${UBIOS_RADIUS_CERT_PATH}/server-key.pem 130 | # copy certificate with full chain 131 | cp -f ${ACMESH_ROOT}/${CERT_NAME}/fullchain.cer ${UBIOS_RADIUS_CERT_PATH}/server.pem 132 | chmod 600 ${UBIOS_RADIUS_CERT_PATH}/server.pem ${UBIOS_RADIUS_CERT_PATH}/server-key.pem 133 | echo -e "${BLUE}#${RESET} New RADIUS certificate and key deployed." 134 | echo -e "${RED}#${RESET} Most probably, it won't work." 135 | fi 136 | radius_restart 137 | } 138 | 139 | webfrontend_restart () { 140 | echo -e "${BLUE}#${RESET} Restarting web frontend (unifi-core)." 141 | if systemctl restart unifi-core; then 142 | echo -e "${GREEN}#${RESET} Restarted UniFi OS on ${unifi_core_device}." 143 | else 144 | echo -e "${RED}#${RESET} Failed to restart UniFi OS on ${unifi_core_device}." 145 | fi 146 | sleep 2; 147 | } 148 | 149 | networkapp_restart() { 150 | echo -e "${BLUE}#${RESET} Restarting UniFi Network Application." 151 | if systemctl restart unifi; then 152 | echo -e "${GREEN}#${RESET} Restarted UniFi Network Application." 153 | else 154 | echo -e "${RED}#${RESET} Failed to restart UniFi Network Application." 155 | fi 156 | } 157 | 158 | radius_restart() { 159 | echo -e "${BLUE}#${RESET} Restarting UniFi RADIUS server." 160 | if systemctl restart udapi-server; then 161 | echo -e "${GREEN}#${RESET} Restarted UniFi RADIUS server." 162 | else 163 | echo -e "${RED}#${RESET} Failed to restart UniFi RADIUS server." 164 | fi 165 | } 166 | 167 | remove_old_log() { 168 | # Trash the previous logfile 169 | if [ -f "${ACMESH_ROOT}/acme.sh.log" ]; then 170 | rm ${ACMESH_ROOT}/acme.sh.log 171 | echo -e "${BLUE}#${RESET} Removed old logfile" 172 | fi 173 | } 174 | 175 | remove_cert() { 176 | echo "Executing: ${ACME_CMD} --remove ${DOMAINS}" 177 | remove_old_log 178 | ${ACME_CMD} --remove ${DOMAINS} 179 | echo -e "${GREEN}#${RESET} Removed certificates from acme.sh renewal. The certificate files can now manually be removed." 180 | } 181 | 182 | deploy() { 183 | echo -e "${BLUE}#${RESET} Deploying certificates and restarting UniFi OS" 184 | deploy_webfrontend 185 | deploy_hotspot_portal 186 | deploy_radius 187 | } 188 | 189 | ###################### 190 | # 191 | # "main" starts here 192 | # 193 | ###################### 194 | 195 | # Check openSSL version, if version 3.x.x, use -legacy for pkcs12 - with UDM V4.x, we're still on OpenSSL v1.1 196 | openssl_version="$(openssl version | awk '{print $2}' | sed -e 's/[a-zA-Z]//g')" 197 | first_digit_openssl="$(echo "${openssl_version}" | cut -d'.' -f1)" 198 | if [[ "${first_digit_openssl}" -ge "3" ]]; then openssl_legacy_flag="-legacy"; fi 199 | 200 | # Check for and if it not exists create acme.sh directory so the container can write to it - owner "nobody" 201 | if [ ! -d "${ACMESH_ROOT}" ]; then 202 | mkdir "${ACMESH_ROOT}" 203 | chmod 700 "${ACMESH_ROOT}" 204 | echo -e "${BLUE}#${RESET} Created directory 'acme.sh'" 205 | fi 206 | 207 | # Check for correct permissions and adjust if necessary 208 | if [ "$(stat -c '%u:%g' "${ACMESH_ROOT}")" != "65534:65534" ]; then 209 | chown 65534:65534 "${ACMESH_ROOT}" 210 | echo -e "${BLUE}#${RESET} Adjusted permissions for 'acme.sh'" 211 | fi 212 | 213 | # Support multiple certificate SANs 214 | # Subject Alternative Name (SAN) 215 | for DOMAIN in $(echo $CERT_HOSTS | tr "," "\n"); do 216 | # Store the certificate under 'first entry' of CERT_HOSTS list 217 | if [ -z "$CERT_NAME" ]; then 218 | CERT_NAME=$DOMAIN 219 | fi 220 | DOMAINS="${DOMAINS} -d ${DOMAIN}" 221 | done 222 | 223 | ACME_HOME="--config-home ${ACMESH_ROOT} --cert-home ${ACMESH_ROOT} --home ${ACMESH_ROOT}" 224 | ACME_CMD="${ACMESH_ROOT}/acme.sh ${ACMESH_CMD_PARAMS} ${ACME_HOME}" 225 | 226 | # confirm if 'account.conf' exists and can only be accessed by owner (nobody / nogroup) 227 | if [ -f "${ACMESH_ROOT}/account.conf" ]; then 228 | if [ "$(stat -c '%a' "${ACMESH_ROOT}/account.conf")" != "600" ]; then 229 | chmod 600 ${ACMESH_ROOT}/account.conf 230 | fi 231 | fi 232 | 233 | # Setup nightly cron job 234 | CRON_FILE='/etc/cron.d/ubios-cert' 235 | if [ ! -f "${CRON_FILE}" ]; then 236 | # V2.x and later requires username 237 | echo "0 3 * * * root ${UBIOS_CERT_ROOT}/ubios-cert.sh renew" >${CRON_FILE} 238 | 239 | chmod 644 ${CRON_FILE} 240 | 241 | if [ -f /etc/init.d/cron ]; then 242 | /etc/init.d/cron reload ${CRON_FILE} 243 | else 244 | echo -e "${RED}#${RESET} Could not find cron service at /etc/init.d/cron" >&2 245 | exit 1 246 | fi 247 | echo -e "${GREEN}#${RESET} Restored cron file" 248 | fi 249 | 250 | case $1 in 251 | initial) 252 | remove_old_log 253 | echo -e" ${BLUE}#${RESET} Setting default CA to ${DEFAULT_CA}" 254 | ${ACME_CMD} --set-default-ca --server ${DEFAULT_CA} 255 | echo -e "${BLUE}#${RESET} Attempting initial certificate generation" 256 | ${ACME_CMD} --register-account --email ${CA_REGISTRATION_EMAIL} 257 | ${ACME_CMD} --issue ${DOMAINS} --dns ${DNS_API_PROVIDER} --keylength 2048 ${LOG} 258 | deploy 259 | ;; 260 | renew) 261 | remove_old_log 262 | echo -e "${BLUE}#${RESET} Attempting acme.sh upgrade" 263 | ${ACME_CMD} --upgrade 264 | echo -e "${BLUE}#${RESET} Attempting certificate renewal" 265 | ${ACME_CMD} --renew ${DOMAINS} --dns ${DNS_API_PROVIDER} --keylength 2048 ${LOG} 266 | deploy 267 | ;; 268 | force-renew) 269 | remove_old_log 270 | echo "Forcing certificate renewal" 271 | ${ACME_CMD} --renew ${DOMAINS} --force --dns ${DNS_API_PROVIDER} --keylength 2048 ${LOG} 272 | deploy 273 | ;; 274 | deploy) 275 | deploy 276 | ;; 277 | deploy-webfrontend) 278 | deploy_webfrontend 279 | ;; 280 | deploy-hotspot-portal) 281 | deploy_hotspot_portal 282 | ;; 283 | deploy-radius) 284 | deploy_radius 285 | ;; 286 | set-default-ca) 287 | remove_old_log 288 | echo -e "${BLUE}#${RESET} Setting default CA to ${DEFAULT_CA}" 289 | ${ACME_CMD} --set-default-ca --server ${DEFAULT_CA} 290 | ;; 291 | cleanup) 292 | if [ -f "${CRON_FILE}" ]; then 293 | rm "${CRON_FILE}" 294 | echo "Removed cron file" 295 | fi 296 | 297 | if [ -f "${ACMESH_ROOT}/account.conf" ]; then 298 | remove_old_log 299 | remove_cert 300 | echo -e "${BLUE}#${RESET} Executing: ${ACME_CMD} --deactivate-account" 301 | ${ACME_CMD} --deactivate-account 302 | echo -e "${BLUE}#${RESET} Deactivated LE account" 303 | fi 304 | ;; 305 | esac 306 | --------------------------------------------------------------------------------