{"id":466,"date":"2023-04-03T20:13:06","date_gmt":"2023-04-03T18:13:06","guid":{"rendered":"https:\/\/jbsoft.nl\/site\/?p=466"},"modified":"2023-04-03T20:39:09","modified_gmt":"2023-04-03T18:39:09","slug":"home-assistant-haproxy-letsencrypttransip","status":"publish","type":"post","link":"https:\/\/jbsoft.nl\/site\/home-assistant-haproxy-letsencrypttransip\/","title":{"rendered":"Home Assistant + `haproxy` +`LetsEncrypt`+TransIP"},"content":{"rendered":"\n<p>This post is about my (positive) experience with <code>haproxy<\/code> as reverse proxy for Home Assistant. Remote access is need if youw want to access Home Assistant from outside of your home network.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p id=\"block-672143c0-9b3b-450f-8df7-2edc759e0312\">As prerequisite we will also need to forward port <code>443<\/code>(https) in our router\/firewall to system used and make sure we have a valid DNS A\/AAAA or CNAME record set up pointing to the Public IP address that is used to forward.<\/p>\n<\/blockquote>\n\n\n\n<p>As I wanted to have <code>LetsEncrypt<\/code> certificates being enrolled using a <a rel=\"noreferrer noopener\" href=\"https:\/\/letsencrypt.org\/docs\/challenge-types\/#http-01-challenge\" target=\"_blank\">DNS Challenge<\/a>. For this I have used a Raspberry Pi 3b board with Rasbian (Debian) installed. Futher <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.docker.com\/desktop\/install\/debian\/\" target=\"_blank\">I installed<\/a> <code>docker<\/code>, and <code>haproxy<\/code>.<\/p>\n\n\n\n<p>Installing the <code>haproxy<\/code> package is as simple as:<\/p>\n\n\n\n<p><code>sudo apt update<\/code> and <code>sudo apt install haproxy<\/code><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>As prerequisite we will also need to forward port <code>443<\/code>(https) in our router\/firewall to system used and make sure we have a valid DNS A\/AAAA or CNAME record set up pointing to the Public IP address that is used to forward.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><code>LetsEncrypt<\/code> with DNS-01 challenge and TransIP API<\/h2>\n\n\n\n<p>My Internet domain are managed by <a rel=\"noreferrer noopener\" href=\"https:\/\/transip.nl\" target=\"_blank\">TransIP<\/a>, and DNS access has <a rel=\"noreferrer noopener\" href=\"https:\/\/api.transip.nl\/rest\/docs.html#domains\" target=\"_blank\">API support<\/a>. In the portal you can create an API key and whitelist the IP-adresses that you want to use for DNS accesses. We use this API to enroll <code>LetsEncrypt<\/code> certificates. Big advantage is that we can enroll wild card certificates or certificates with internal names we do not want to expose on the Internet, and we do not need to open port 80!<\/p>\n\n\n\n<p>For the enrollment of certificates with <code>LetsEncrypt<\/code> via the TransIP API I used the docker image (rbongers\/certbot-dns-transip) of <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/roy-bongers\" data-type=\"URL\" data-id=\"https:\/\/github.com\/roy-bongers\" target=\"_blank\">Roy Bongers<\/a> that has all functionality in it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Setting up <code>LetsEncrypt<\/code><\/h3>\n\n\n\n<p>First create the folders <code>\/etc\/letsencrypt<\/code>, <code>\/var\/log\/letsencrypt<\/code> and <code>\/etc\/transip<\/code>.<\/p>\n\n\n\n<p>Create as <code>root<\/code> file <code>config.php<\/code> in <code>\/etc\/transip<\/code>. Use the link below for the template:<\/p>\n\n\n\n<p><a href=\"https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-config-php-L1-L17\">https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-config-php-L1-L17<\/a><\/p>\n\n\n\n<p>You need to paste in the obtained TransIP API key and your TransIP username.<\/p>\n\n\n\n<p>Make sure you secure your key file with <code>sudo chmod 400 \/etc\/transip\/config\/php<\/code> so that is read-only for root.<\/p>\n\n\n\n<p>To setup the certificate for the first time we create a bash script (I placed it in <code>\/usr\/local\/sbin<\/code>):<\/p>\n\n\n\n<p><a href=\"https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-certinit-bash-L1-L10\">https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-certinit-bash-L1-L10<\/a><\/p>\n\n\n\n<p>Replace the `&#8211;cert-name` and the wild card domain (<code>-d certname.example.com<\/code>), and replace the domain with any domain name you own. You can use the <code>-d<\/code> parameter multiple times. For each domain a dns-challenge will be created.<\/p>\n\n\n\n<p>Make sure the script is executable <code>chmod +x certinit.bash<\/code>.<\/p>\n\n\n\n<p>Now run <code>certinit.bash<\/code> . It will download the image and start a docker for the set-up. This is interactive, you will be asked some questions like your email adress.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root@docker01:\/usr\/local\/sbin# certinit.bash\nUnable to find image 'rbongers\/certbot-dns-transip:latest' locally\nlatest: Pulling from rbongers\/certbot-dns-transip\n330ad28688ae: Pull complete\n882df4fa64e9: Pull complete\n07e271639575: Pull complete\n2d60c5e17079: Pull complete\nf54b294a6f71: Pull complete\n6f27ea6ab430: Pull complete\n0c8c5a3cd6a8: Pull complete\n6436ec8cd157: Pull complete\n350482e0cef8: Pull complete\nfcb8169b6442: Pull complete\nba9658959877: Pull complete\nb9d5ffb589b1: Pull complete\na368f8fc57ed: Pull complete\nDigest: sha256:faec7bc102edf00237041fbce8030249fb55f300da76b637660384c353043bff\nStatus: Downloaded newer image for rbongers\/certbot-dns-transip:latest\nSaving debug log to \/var\/log\/letsencrypt\/letsencrypt.log\nPlugins selected: Authenticator manual, Installer None\nEnter email address (used for urgent renewal and security notices)\n (Enter 'c' to cancel): <strong>user@example.com<\/strong> (your email adres)\n\n\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\nPlease read the Terms of Service at\nhttps:\/\/letsencrypt.org\/documents\/LE-SA-v1.3-September-21-2022.pdf. You must\nagree in order to register with the ACME server. Do you agree?\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n(Y)es\/(N)o: <strong>Y<\/strong>\n\n\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\nWould you be willing, once your first certificate is successfully issued, to\nshare your email address with the Electronic Frontier Foundation, a founding\npartner of the Let's Encrypt project and the non-profit organization that\ndevelops Certbot? We'd like to send you email about our work encrypting the web,\nEFF news, campaigns, and ways to support digital freedom.\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n(Y)es\/(N)o: <strong>N<\/strong>\n\nAccount registered.\nRequesting a certificate for <strong>xxxxxx <\/strong>and <strong>yyyyy<\/strong>\nPerforming the following challenges:\ndns-01 challenge for <strong>xxxxx<\/strong>\ndns-01 challenge for <strong>yyyyy<\/strong>\nRunning manual-auth-hook command: \/opt\/certbot-dns-transip\/auth-hook\nOutput from manual-auth-hook command auth-hook:\n&#91;2023-03-31 08:32:03.596791] INFO: Creating TXT record for _acme-challenge with challenge 'FfGM5VPKWKkwR-6ggUhlpoZlQ5-vPGK-JIy25tekb_E' &#91;] &#91;]\n&#91;2023-03-31 08:32:06.782752] INFO: Waiting until nameservers (ns0.transip.net, ns1.transip.nl, ns2.transip.eu) are up-to-date &#91;] &#91;]\n&#91;2023-03-31 08:32:08.625219] INFO: All nameservers are updated! &#91;] &#91;]\n\nRunning manual-auth-hook command: \/opt\/certbot-dns-transip\/auth-hook\nOutput from manual-auth-hook command auth-hook:\n&#91;2023-03-31 08:32:09.659195] INFO: Creating TXT record for _acme-challenge with challenge 'lP_hQVRODhVCiglLiNSZvNky9voGChnTyo407N_xRU4' &#91;] &#91;]\n&#91;2023-03-31 08:32:12.628115] INFO: Waiting until nameservers (ns0.transip.net, ns1.transip.nl, ns2.transip.eu) are up-to-date &#91;] &#91;]\n&#91;2023-03-31 08:32:13.659641] INFO: All nameservers are updated! &#91;] &#91;]\n\nWaiting for verification...\nCleaning up challenges\nRunning manual-cleanup-hook command: \/opt\/certbot-dns-transip\/cleanup-hook\nOutput from manual-cleanup-hook command cleanup-hook:\n&#91;2023-03-31 08:32:16.459605] INFO: Cleaning up record _acme-challenge with value 'FfGM5VPKWKkwR-6ggUhlpoZlQ5-vPGK-JIy25tekb_E' &#91;] &#91;]\n\nRunning manual-cleanup-hook command: \/opt\/certbot-dns-transip\/cleanup-hook\nOutput from manual-cleanup-hook command cleanup-hook:\n&#91;2023-03-31 08:32:20.031682] INFO: Cleaning up record _acme-challenge with value 'lP_hQVRODhVCiglLiNSZvNky9voGChnTyo407N_xRU4' &#91;] &#91;]\n\n\nIMPORTANT NOTES:\n - Congratulations! Your certificate and chain have been saved at:\n   \/etc\/letsencrypt\/live\/<strong>xxxxxx<\/strong>\/fullchain.pem\n   Your key file has been saved at:\n   \/etc\/letsencrypt\/live\/<strong>xxxxxx<\/strong>\/privkey.pem\n   Your certificate will expire on 202<strong>x-mm-ss<\/strong>. To obtain a new or\n   tweaked version of this certificate in the future, simply run\n   certbot again. To non-interactively renew *all* of your\n   certificates, run \"certbot renew\"\n - If you like Certbot, please consider supporting our work by:\n\n   Donating to ISRG \/ Let's Encrypt:   https:\/\/letsencrypt.org\/donate\n   Donating to EFF:                    https:\/\/eff.org\/donate-le\n<\/code><\/pre>\n\n\n\n<p>Your certificate will be created <code>\/etc\/letsencrypt\/live\/{certname}<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Set up auto enrollment<\/h3>\n\n\n\n<p>We want to auto renew the certificate and update <code>haproxy<\/code> to use the new certificate. The <code>haproxy<\/code> certificate will placed at <code>\/etc\/haproxy\/cert.pem<\/code>. To enroll I made a bash script <code>certrenew.bash<\/code>:<\/p>\n\n\n\n<p><a href=\"https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-certrenew-bash-L1-L68\">https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-certrenew-bash-L1-L68<\/a><\/p>\n\n\n\n<p>Make sure you adjust the <code>basecert<\/code> parameter in the script. and place it as <code>certrenew<\/code> in <code>\/usr\/local\/bin\/sbin<\/code> and make it executable with <code>sudo chmod + x \/etc\/local\/sbin\/certrenew<\/code>.<\/p>\n\n\n\n<p>We need to run <code>certrenew<\/code> a first time to make sure the <code>haproxy<\/code> cert is created.<\/p>\n\n\n\n<p>To make sure it runs every day somewhere between 11pm and midnight we create a cronfile (`\/etc\/cron.d\/certrenew`) for it (see link).<\/p>\n\n\n\n<p><a href=\"https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-certrenew-cron-L1-L9\">https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-certrenew-cron-L1-L9<\/a>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Restart cron <code>sudo systemctl restart cron<\/code><\/p>\n<\/blockquote>\n\n\n\n<p>When the script executes, it will check if the certificate will be expired within 30 days, in that case a new certificate is requested. If the process was successful it will update the certificate and restart <code>haproxy<\/code> with the new certificate.<\/p>\n\n\n\n<p>Now we are all set, make sure you check the logs to see the whole setup is working correctly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set up <code>haproxy<\/code><\/h2>\n\n\n\n<p>As a last step now can set up <code>haproxy<\/code> (<code>\/etc\/haproxy\/haproxy.cfg<\/code>) using the new certificate. <\/p>\n\n\n\n<p>You can use <a rel=\"noreferrer noopener\" href=\"https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-haproxy-cfg-L1-L61\" target=\"_blank\">https:\/\/gist.github.com\/jbouwh\/3b6042ed4ca1189e1f37d0f8ff7274e5#file-haproxy-cfg-L1-L61<\/a> as a template for your configuration. You need to change the DNS names and internal IP-address of your backend. In my case I used a Raspberry PI with a SD card and switched off logging to ensure the SD card lasts longer. If you have an SSD attached you can change the logging .<\/p>\n\n\n\n<p>Make sure you also install <code>\/etc\/haproxy\/dhparam.pem<\/code> (see the comment at the last line of the config file on how to obtain it).<\/p>\n\n\n\n<p>In the example config I have enabled the stats page at <code>https:\/\/{your_domain_name}\/stats<\/code>. You can use this page to see the stats. <code>acl network_allowed_src src<\/code> is used to secure that the page is only accessible from internal IP adresses. Make sure you fill in the correct IP-ranges that should have access.<\/p>\n\n\n\n<p>If you are ready you can test the config file with:<\/p>\n\n\n\n<p><span class=\"pl-c\"><code>haproxy -f \/etc\/haproxy\/haproxy.cfg -c<\/code><\/span><\/p>\n\n\n\n<p>If that is okay, you are all set. Restart <code>haproxy<\/code> to load the new config using: <code>sudo systemctl restart haproxy<\/code>.<\/p>\n\n\n\n<p>Now you can access Home Assistant from out side. Make sure <a href=\"https:\/\/www.home-assistant.io\/docs\/authentication\/multi-factor-auth\/\" data-type=\"URL\" data-id=\"https:\/\/www.home-assistant.io\/docs\/authentication\/multi-factor-auth\/\" target=\"_blank\" rel=\"noreferrer noopener\">to set up two-factor-authentication <\/a>to secure the access to your network.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post is about my (positive) experience with haproxy as reverse proxy for Home Assistant. Remote access is need if youw want to access Home Assistant from outside of your home network. As prerequisite we will also need to forward port 443(https) in our router\/firewall to system used and make sure we have a valid &hellip; <a href=\"https:\/\/jbsoft.nl\/site\/home-assistant-haproxy-letsencrypttransip\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Home Assistant + `haproxy` +`LetsEncrypt`+TransIP&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,5],"tags":[],"class_list":["post-466","post","type-post","status-publish","format-standard","hentry","category-docker","category-home-assistant"],"_links":{"self":[{"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/posts\/466","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/comments?post=466"}],"version-history":[{"count":2,"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/posts\/466\/revisions"}],"predecessor-version":[{"id":468,"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/posts\/466\/revisions\/468"}],"wp:attachment":[{"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/media?parent=466"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/categories?post=466"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jbsoft.nl\/site\/wp-json\/wp\/v2\/tags?post=466"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}