# RouterOS script name : LetsEncryptReceiveCert # Version : 1.2.2 # Author : Nuc # Creation date : 2023/June/30 # Tested on: RouterOS 7.8+ / multiple MikroTik HW devices and MikroTik CHR # policy : read, write, policy, test # /system scheduler add name=LetsEncryptReceiveCertificate policy=read,write,policy,test interval=1d start-date=Jan/01/2023 start-time=00:00:01 on-event="/system script run LetsEncryptReceiveCert" # You using this script as-is, on your fear and risk! :global renewWhenExpiresInDays "14d"; :global retryRenewFailsCount 2; # You can set IPServiceWWWManualSet to true, and setup port and status of WWW service manually. # :local IPServiceWWWManualSet false/true; :global IPServiceWWWManualSet false; # :local IPServiceWWWStatusDisabledManual yes/no; :global IPServiceWWWStatusDisabledManual yes; :global IPServiceWWWPortManual 80; #Service variables :global DNSName; # C=US, O=Let's Encrypt, CN=R3 :global LEcertIssuer "Let's Encrypt"; :global LEcertNameDefaultName "letsencrypt-autogen"; :global renewCertNecessary false; :global renewCertSuccess false; :global LERenewedCertName; :global renameLECertificates; :global renewLECertificate; :global LECertificateToUse; # doExit function :global doExit do={ # info / warning / error :local messageType "$1"; :local messageText "$2"; /system script environment remove [find name="renewWhenExpiresInDays"]; /system script environment remove [find name="retryRenewFailsCount"]; /system script environment remove [find name="IPServiceWWWManualSet"]; /system script environment remove [find name="IPServiceWWWStatusDisabledManual"]; /system script environment remove [find name="IPServiceWWWPortManual"]; /system script environment remove [find name="renewCertNecessary"]; /system script environment remove [find name="renewCertSuccess"]; /system script environment remove [find name="DNSName"]; /system script environment remove [find name="LEcertIssuer"]; /system script environment remove [find name="LEcertNameDefaultName"]; /system script environment remove [find name="LERenewingResult"]; /system script environment remove [find name="LERenewedCertName"]; /system script environment remove [find name="renameLECertificates"]; /system script environment remove [find name="renewLECertificate"]; /system script environment remove [find name="LECertificateToUse"]; /system script environment remove [find name="doExit"]; :if ( [:len $messageText] > 0 ) do={ :if ( $messageType = "info" ) do={ :log info "doExit:: $messageText"; } :if ( $messageType = "warning" ) do={ :log warning "doExit:: $messageText"; } :if ( $messageType = "error" ) do={ :log error "doExit:: $messageText"; } } else { :log warning "doExit"; } exit; }; # Checking IP->Cloud service :if ( ![/ip cloud get ddns-enabled] ) do={ $doExit [:put (:tostr "error")] [:put (:tostr "IP->Cloud service is Off! Please turn it to On!")] } else { :global DNSName [:tostr [/ip cloud get dns-name]]; :if ( [:len $DNSName] < 0 ) do={ $doExit [:put (:tostr "error")] [:put (:tostr "Something went wrong! Can't determine DDNS name!")] } else { :log warning "Debug::DNSName: $DNSName"; } } :global renameLECertificates do={ :log warning "Debug::renameLECertificates function"; :global DNSName; :global LEcertIssuer; :global LEcertNameDefaultName; :local certNameReturned "null"; #:global certNameReturned; :if ( [/certificate find where name~"$LEcertNameDefaultName" && common-name~"$DNSName" && issuer~"$LEcertIssuer"] ) do={ :foreach LEcerts in=[/certificate find where name~"$LEcertNameDefaultName" && common-name~"$DNSName" && issuer~"$LEcertIssuer"] do={ #:global certNameReturned; :local LECertName [/certificate get $LEcerts name]; :log warning "Debug::renameLECertificates::CertName: $LECertName"; :local LECertCN [/certificate get $LEcerts common-name]; #:local LECertAltNameFull [/certificate get $LEcerts subject-alt-name]; #:local LECertAltName [:pick [:tostr [$LECertAltNameFull]] 4]; #START for 7.10+ :local ROSVersionMinor ""; :for i from=3 to=[:len [/system resource get value-name=version]] do={ :if ( [:tonum [:pick [/system resource get value-name=version] 2 $i]] ) do={ :set ROSVersionMinor ([:tonum [:pick [/system resource get value-name=version] 2 $i]]); } } :local LECertIssueDateYY ""; :local LECertIssueDateMM ""; :local LECertIssueDateDD ""; :if ( $ROSVersionMinor >= 10 ) do={ #:log info "ROS>=10" :set LECertIssueDateYY [:pick [/certificate get $LEcerts invalid-after] 0 4]; :set LECertIssueDateMM [:pick [/certificate get $LEcerts invalid-after] 5 7]; :set LECertIssueDateDD [:pick [/certificate get $LEcerts invalid-after] 8 10]; } else { #:log info "ROS<10" :set LECertIssueDateYY [:pick [:tostr [/certificate get $LEcerts invalid-after]] 7 11]; :local LECertIssueDateMMString [:pick [:tostr [/certificate get $LEcerts invalid-after]] 0 3]; :local monthToNum {jan="01";feb="02";mar="03";apr="04";may="05";jun="06";jul="07";aug="08";sep="09";oct="10";nov="11";dec="12";}; :set LECertIssueDateMM ( $monthToNum -> [:pick $LECertIssueDateMMString 0 3] ); :set LECertIssueDateDD [:pick [:tostr [/certificate get $LEcerts invalid-after]] 4 6]; } #END for 7.10+ #:local LECertIssueDateTT [:pick [:tostr [/certificate get $LEcerts invalid-after]] 6 100]; :local LECertIssueDateTT [:pick [:tostr $LECertName] 30 100]; #:log warning "Debug::renameLECertificates::LECertIssueDateTT: $LECertIssueDateTT"; :log warning "Debug::renameLECertificates::LECertFullName: LECert-$LECertIssueDateYY$LECertIssueDateMM$LECertIssueDateDD$LECertIssueDateTT"; :local LECertIssueDateString "$LECertIssueDateYY$LECertIssueDateMM$LECertIssueDateDD"; #:local certIssueDateString "20230200"; #:local certIssueDateInt ([:tonum $LECertIssueDateString] - 1); :local certIssueDateInt ([:tonum $LECertIssueDateString]); #:log warning "Debug::renameLECertificates::certIssueDateInt: $certIssueDateInt"; :local certIssueDateReady "$certIssueDateInt$LECertIssueDateTT"; #:log warning "Debug::renameLECertificates::certIssueDateReady: $certIssueDateReady"; :local LECertNameReady "$LECertCN-$certIssueDateReady"; :log warning "Debug::renameLECertificates::LECertNameReady: $LECertNameReady"; /certificate set name=$LECertNameReady $LECertName; :set $certNameReturned $LECertNameReady; }; }; :log warning "Debug::renameLECertificates::certNameReturned: $certNameReturned"; :return $certNameReturned; #/system script environment remove [find name="certNameReturned"]; }; # **************************************************************************************** # Renewing LetsEncrypt certificate :global renewLECertificate do={ :log warning "Debug::renewLECertificate function"; :global DNSName; :global retryRenewFailsCount; :global LEcertIssuer; :global LEcertNameDefaultName; :global renewCertNecessary; :global renewCertSuccess; :global LERenewedCertName; :global LERenewingResult; :global renameLECertificates; :global doExit; # Renewing LetsEncrypt certificate # Firewall filter rules enable/create # IPv4 rules :local ruleExistsIPv4Firewall false; :local ruleExistsIPv4RAW false; :if ( [:len [/ip cloud get public-address]] > 0 ) do={ if ( [/ip firewall filter find where comment="LetsEncryptAccept"] ) do={ :set ruleExistsIPv4Firewall true; /ip firewall filter enable [find where comment="LetsEncryptAccept"]; } else { :if ( [/ip firewall filter find] ) do={ # some rules exists /ip firewall filter add action=accept chain=input comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp place-before=0; } else { # no rules find /ip firewall filter add action=accept chain=input comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp; } } if ( [/ip firewall raw find where comment="LetsEncryptAccept"] ) do={ :set ruleExistsIPv4RAW true; /ip firewall raw enable [find where comment="LetsEncryptAccept"]; } else { :if ( [/ip firewall raw find] ) do={ # some rules exists /ip firewall raw add action=accept chain=prerouting comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp place-before=0; } else { # no rules find /ip firewall raw add action=accept chain=prerouting comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp; } } } #IPv6 rules :local ruleExistsIPv6Firewall false; :local ruleExistsIPv6RAW false; :if ( [:len [/ip cloud get public-address-ipv6]] > 0 ) do={ if ( [/ipv6 firewall filter find where comment="LetsEncryptAccept"] ) do={ :set ruleExistsIPv6Firewall true; /ipv6 firewall filter enable [find where comment="LetsEncryptAccept"]; } else { :if ( [/ipv6 firewall filter find] ) do={ # some rules exists /ipv6 firewall filter add action=accept chain=input comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp place-before=0; } else { # no rules find /ipv6 firewall filter add action=accept chain=input comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp; } } if ( [/ipv6 firewall raw find where comment="LetsEncryptAccept"] ) do={ :set ruleExistsIPv6RAW true; /ipv6 firewall raw enable [find where comment="LetsEncryptAccept"]; } else { :if ( [/ipv6 firewall raw find] ) do={ # some rules exists /ipv6 firewall raw add action=accept chain=prerouting comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp place-before=0; } else { # no rules find /ipv6 firewall raw add action=accept chain=prerouting comment="LetsEncryptAcceptTemporary" dst-port=80 protocol=tcp; } } } # Getting current state port/status of IP-Service WWW :local IPServiceWWWPort [/ip service get [/ip service find where name="www"] port]; :log warning "Debug::renewLECertificate::IPServiceWWWPort: $IPServiceWWWPort"; :local IPServiceWWWStatusBool [/ip service get [/ip service find where name=www] disabled]; :log warning "Debug::renewLECertificate::IPServiceWWWStatusBool: $IPServiceWWWStatusBool"; :local IPServiceWWWStatusDisabled; :if ( $IPServiceWWWStatusBool = true ) do={ # rule is Disabled :set $IPServiceWWWStatusDisabled "yes"; } else { # rule is Enabled :set $IPServiceWWWStatusDisabled "no"; } :log warning "Debug::renewLECertificate::IPServiceWWWStatusDisabled: $IPServiceWWWStatusDisabled"; /ip service set [/ip service find where name="www"] port=80; /ip service set [/ip service find where name="www"] disabled=no; :local cycleRun true; :local retryRenewCounter 0; :set LERenewingResult "error"; :while ($cycleRun) do={ :log warning "Debug::renewLECertificate::Start while count: $retryRenewCounter"; :set LERenewingResult [:tostr [/certificate enable-ssl-certificate dns-name="$DNSName" as-value]]; :delay 30s; if ( $LERenewingResult~"success" ) do={ :log warning "Debug::renewLECertificate::LERenewingResult success: $LERenewingResult"; :set cycleRun false; :set renewCertSuccess true; } else { :log warning "Debug::renewLECertificate::LERenewingResult error: $LERenewingResult"; :set retryRenewCounter ($retryRenewCounter + 1); # Debug #:set renewCertSuccess true; } :if ( $retryRenewCounter >= $retryRenewFailsCount ) do={ :set cycleRun false; } :log warning "Debug::renewLECertificate::retryRenewCounter: $retryRenewCounter"; :delay 3s; } :log warning "Debug::renewLECertificate::LERenewingResult: $LERenewingResult"; :if ( $ruleExistsIPv4Firewall = true ) do={ /ip firewall filter disable [find where comment="LetsEncryptAccept"]; } else { /ip firewall filter remove [find where comment="LetsEncryptAcceptTemporary"]; } :if ( $ruleExistsIPv4RAW = true ) do={ /ip firewall raw disable [find where comment="LetsEncryptAccept"]; } else { /ip firewall raw remove [find where comment="LetsEncryptAcceptTemporary"]; } :if ( $ruleExistsIPv6Firewall = true ) do={ /ipv6 firewall filter disable [find where comment="LetsEncryptAccept"]; } else { /ipv6 firewall filter remove [find where comment="LetsEncryptAcceptTemporary"]; } :if ( $ruleExistsIPv6RAW = true ) do={ /ipv6 firewall raw disable [find where comment="LetsEncryptAccept"]; } else { /ipv6 firewall raw remove [find where comment="LetsEncryptAcceptTemporary"]; } :if ( $IPServiceWWWManualSet = true ) do={ /ip service set [/ip service find where name="www"] port=$IPServiceWWWPortManual; /ip service set [/ip service find where name="www"] disabled=$IPServiceWWWStatusDisabledManual; } else { /ip service set [/ip service find where name="www"] port=$IPServiceWWWPort; /ip service set [/ip service find where name="www"] disabled=$IPServiceWWWStatusDisabled; } if ( $renewCertSuccess = true ) do={ :if ( [/certificate find where name~"$LEcertNameDefaultName" && common-name~"$DNSName" && issuer~"$LEcertIssuer"] ) do={ :log warning "Certificate just was renew : $LERenewingResult"; :local LERenewedCertRenemad [$renameLECertificates]; :if ( $LERenewedCertRenemad = "null" ) do={ :log warning "Debug::renewLECertificate::certCurrentName: NULL"; $doExit [:put (:tostr "error")] [:put (:tostr "Something went wrong! $LEcertNameDefaultName certificate not found....")]; } else { :log warning "Debug::renewLECertificate::LERenewedCertRenemad: $LERenewedCertRenemad"; :set LERenewedCertName "$LERenewedCertRenemad"; :log warning "Debug::renewLECertificate::LERenewedCertName: $LERenewedCertName"; } } else { $doExit [:put (:tostr "error")] [:put (:tostr "Something went wrong! $LEcertNameDefaultName certificate not found....")]; } } else { $doExit [:put (:tostr "error")] [:put (:tostr "Something went wrong when renewing LetsEncrypt certificate!")]; } }; # **************************************************************************************** # If we find LetsEncrypt certificates with the default name, function renameLECertificates will rename them to the new format #$renameLECertificates; :set LECertificateToUse [$renameLECertificates]; :if ( $LECertificateToUse = "null" ) do={ :log warning "Debug::LECertificateToUse: We didn't find any previously LetsEncrypt certificates with a default name!"; } else { :log warning "Debug::LECertificateToUse: $LECertificateToUse"; } # Finding LetsEncrypt certificate and check their expiration :local LECertNameToRenew; :if ( [/certificate find where name~"$DNSName" && common-name~"$DNSName" && issuer~"$LEcertIssuer"] ) do={ :foreach releasedLECerts in=[/certificate find where name~"$DNSName" && common-name~"$DNSName" && issuer~"$LEcertIssuer"] do={ :local LECertName [/certificate get $releasedLECerts name]; :local LECertInvalidAfter [/certificate get $releasedLECerts invalid-after]; :local LECertExpiresAfter [/certificate get $releasedLECerts expires-after]; :log warning "Debug::CertName: $LECertName - $LECertInvalidAfter - $LECertExpiresAfter"; # If certificates more than one, here be latest :set LECertNameToRenew $LECertName; } } else { :log warning "Debug::Certificates not found!"; :set $renewCertNecessary true; } :if ( [:len $LECertNameToRenew]>0 ) do={ :log warning "Debug::LECertNameToRenew: $LECertNameToRenew"; # Use this LetsEncrypt certificate for services # If enable, every start script will be re setup certificate in a services in a lines below #:set LECertificateToUse $LECertNameToRenew; :local certExpired [/certificate get $LECertNameToRenew expired]; #:log warning "Debug::certExpired: $certExpired"; :local certExpiresAfter [/certificate get $LECertNameToRenew expires-after]; :log warning "Debug::certExpiresAfter: $certExpiresAfter"; :if ( $certExpired = true ) do={ # Certificate expired :log warning "Debug:: Certificate expired! We need to renew $LECertNameToRenew!"; :set $renewCertNecessary true; } else { :if ( $certExpiresAfter <= $renewWhenExpiresInDays ) do={ :log warning "Debug:: We need to renew $LECertNameToRenew!"; :set $renewCertNecessary true; } } } :log warning "Debug::renewCertNecessary: $renewCertNecessary"; :if ( $renewCertNecessary = true ) do={ #:if ( $renewCertNecessary = false ) do={ $renewLECertificate; } else { :log warning "Debug::Not needed renewing LetsEncrypt certificate!"; } :if ( $renewCertSuccess = true ) do={ # Sample how to use received LetsEncrypt certificate /ip service set [/ip service find where name="www-ssl"] certificate=$LERenewedCertName; /ip service set [/ip service find where name="api-ssl"] certificate=$LERenewedCertName; /interface sstp-server server set certificate=$LERenewedCertName; /interface ovpn-server server set certificate=$LERenewedCertName; /ip ipsec identity set [/ip ipsec identity find where peer=IKE2VPN-Peer] certificate=$LERenewedCertName; } else { # It's for first time start script :if ( $LECertificateToUse != "null" ) do={ :log warning "Debug::Use this LetsEncrypt certificate to services: $LECertificateToUse"; /ip service set [/ip service find where name="www-ssl"] certificate=$LECertificateToUse; /ip service set [/ip service find where name="api-ssl"] certificate=$LECertificateToUse; #/interface sstp-server server set certificate=$LECertificateToUse; #/interface ovpn-server server set certificate=$LECertificateToUse; #/ip ipsec identity set [/ip ipsec identity find where peer=IKE2VPN-Peer] certificate=$LECertificateToUse; } } $doExit [:put (:tostr "warning")] [:put (:tostr "Script end, doExit successfully!")];