<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Homelab on mhemeryck</title><link>https://mhemeryck.xyz/tags/homelab/</link><description>Recent content in Homelab on mhemeryck</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 16 Dec 2021 00:00:00 +0000</lastBuildDate><atom:link href="https://mhemeryck.xyz/tags/homelab/index.xml" rel="self" type="application/rss+xml"/><item><title>unifi terraform</title><link>https://mhemeryck.xyz/posts/2021-12-16-unifi_terraform/</link><pubDate>Thu, 16 Dec 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-12-16-unifi_terraform/</guid><description>&lt;p&gt;Last week, I did write about &lt;a href="https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/"&gt;resetting my unifi cloud key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main reason I did gain a renewed interest in the cloud key was because I wanted to add some more fixed IPs to my home network.
One of the nice things about the unifi controller software is that it provides a single dashboard interface to manage everything network-related.&lt;/p&gt;
&lt;p&gt;On the downside, the interface is overall quite limited.
While it does offer DHCP (the fixed IPs) in this fashion, it does not offer built-in DNS (e.g. for naming dedicated hosts on the home network).&lt;/p&gt;</description><content:encoded>&lt;p&gt;Last week, I did write about &lt;a href="https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/"&gt;resetting my unifi cloud key&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main reason I did gain a renewed interest in the cloud key was because I wanted to add some more fixed IPs to my home network.
One of the nice things about the unifi controller software is that it provides a single dashboard interface to manage everything network-related.&lt;/p&gt;
&lt;p&gt;On the downside, the interface is overall quite limited.
While it does offer DHCP (the fixed IPs) in this fashion, it does not offer built-in DNS (e.g. for naming dedicated hosts on the home network).&lt;/p&gt;
&lt;p&gt;Additionally, I also don&amp;rsquo;t really like these point-and-click interfaces which are hard to put under version control except for a full snapshot.
Coming from a software development background and with the current infrastructure-as-code movement, I wanted to look into an alternative.&lt;/p&gt;
&lt;h2 id="infrastructure-as-code-and-hashicorp-terraform"&gt;infrastructure-as-code and HashiCorp terraform&lt;/h2&gt;
&lt;p&gt;Nowadays, if you want to run some kind of software service, all the major cloud providers do provide web APIs to manage the (virtual) infrastructure for you.
Managing infrastructure at this point becomes another &lt;strong&gt;software problem&lt;/strong&gt; for interacting with these APIs, thus the term infrastructure-as-code.&lt;/p&gt;
&lt;p&gt;One platform that has become increasingly popular in that regard has been HashiCorp&amp;rsquo;s &lt;strong&gt;&lt;a href="https://www.terraform.io/"&gt;terraform&lt;/a&gt;&lt;/strong&gt;.
It provides an abstraction layer for all of these different cloud provider and offers an ecosystem for easily adding more modules.&lt;/p&gt;
&lt;p&gt;Additionally, the format is &lt;strong&gt;declarative&lt;/strong&gt;, meaning that the tool will manage the life cycle of the components you describe.
As an example, adding a resource means just declaring it in the configuration file and running it through the &lt;code&gt;terraform&lt;/code&gt; CLI tool.
At that point, the resource becomes managed by terraform.
Destroying the resource is done by removing the entry from the configuration file and once again applying.
Essentially, the configuration file will always reflect the state of the resources being managed and vice versa.&lt;/p&gt;
&lt;h2 id="unifi-terraform"&gt;unifi-terraform&lt;/h2&gt;
&lt;p&gt;What does any of that have to do with my tiny homelab setup?
Enter &lt;strong&gt;&lt;a href="https://registry.terraform.io/providers/paultyng/unifi/latest/docs"&gt;unifi-terraform&lt;/a&gt;&lt;/strong&gt;, which is a community provided terraform module that interacts with the API of the unifi cloud key.
This way, I could manage all the required fixed IPs.&lt;/p&gt;
&lt;p&gt;As I mentioned in my previous post, I did have some trouble getting started though, so let&amp;rsquo;s walk a bit through the steps I had taken.&lt;/p&gt;
&lt;h3 id="terraform-configuration-file"&gt;terraform configuration file&lt;/h3&gt;
&lt;p&gt;The first step is creating a &lt;code&gt;main.tf&lt;/code&gt; file containing the actual configuration.
I&amp;rsquo;m not going to elaborate too much on this here, see the &lt;a href="https://www.terraform.io/docs"&gt;terraform docs&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;Generally, my configuration file looked like this (redacted).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform {
required_providers {
unifi = {
source = &amp;#34;paultyng/unifi&amp;#34;
version = &amp;#34;0.34.0&amp;#34;
}
}
}
provider &amp;#34;unifi&amp;#34; {
username = var.username # These are in a variables file
password = var.password
api_url = var.api_url
}
resource &amp;#34;unifi_site&amp;#34; &amp;#34;sitename&amp;#34; {
description = &amp;#34;Default&amp;#34;
}
resource &amp;#34;unifi_user_group&amp;#34; &amp;#34;default&amp;#34; {
name = &amp;#34;Default&amp;#34;
}
# the network / DHCP config
resource &amp;#34;unifi_network&amp;#34; &amp;#34;lan&amp;#34; {
name = &amp;#34;LAN&amp;#34;
purpose = &amp;#34;corporate&amp;#34;
dhcp_dns = [
&amp;#34;192.168.1.21&amp;#34;,
&amp;#34;1.1.1.1&amp;#34;,
&amp;#34;8.8.8.8&amp;#34;,
&amp;#34;9.9.9.9&amp;#34;,
]
dhcp_enabled = true
dhcp_start = &amp;#34;192.168.1.20&amp;#34;
dhcp_stop = &amp;#34;192.168.1.254&amp;#34;
domain_name = &amp;#34;lan&amp;#34;
igmp_snooping = true
subnet = &amp;#34;192.168.1.0/24&amp;#34;
}
# WLAN
resource &amp;#34;unifi_wlan&amp;#34; &amp;#34;wlan&amp;#34; {
name = &amp;#34;wlan&amp;#34;
security = &amp;#34;wpapsk&amp;#34;
passphrase = var.passphrase_wifi_wlan
network_id = unifi_network.lan.id
user_group_id = unifi_user_group.default.id
wlan_band = &amp;#34;5g&amp;#34;
multicast_enhance = true # chromecast
# default group ID
ap_group_ids = [
var.ap_group_id
]
# enable WPA2/WPA3 support
wpa3_support = true
wpa3_transition = true
pmf_mode = &amp;#34;optional&amp;#34;
}
...
# Devices
resource &amp;#34;unifi_device&amp;#34; &amp;#34;my-router&amp;#34; {
name = &amp;#34;my-router&amp;#34;
}
...
# Users: network clients
resource &amp;#34;unifi_user&amp;#34; &amp;#34;my_client&amp;#34; {
name = &amp;#34;my-client&amp;#34;
mac = &amp;#34;d9:6e:3d:12:8f:53&amp;#34;
fixed_ip = &amp;#34;192.168.1.10&amp;#34;
network_id = unifi_network.lan.id
}
...
resource &amp;#34;unifi_user&amp;#34; &amp;#34;floating_client&amp;#34; {
name = &amp;#34;floating&amp;#34;
mac = &amp;#34;bc:e4:26:db:df:e4&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At the top of this file the &lt;code&gt;required_provider&lt;/code&gt; is mentioned, i.e. the module that contains the client code to talk to the unifi cloud key API.
The &lt;code&gt;provider&lt;/code&gt; block contains the data used for the provider to do the actual connection (essentially username / password and a URL to connect to).&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a couple of interesting resources in there:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unifi_site&lt;/code&gt;: just the physical location &amp;ndash; I guess this makes more sense if you have multiple locations to manage&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_user_group&lt;/code&gt;: this is relevant if you want to separate out specific access for e.g. WiFi clients.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_network&lt;/code&gt;: main (wired) network parameters, including e.g. the DHCP settings&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_wlan&lt;/code&gt;: wireless network configuration. Helpful if you want push in passwords&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_device&lt;/code&gt;: the unifi hardware such as switches, routers and wireless access points. I actually don&amp;rsquo;t have anything in particular to manage here, but I guess if you want to have specific port profiles for a switch, this would be the way to link to it&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unifi_user&lt;/code&gt;: the clients on the network. It&amp;rsquo;s at this point that I can actually fix the IPs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have a look at &lt;a href="https://registry.terraform.io/providers/paultyng/unifi/latest/docs"&gt;unifi-terraform&lt;/a&gt; for more resources that can be managed.&lt;/p&gt;
&lt;h3 id="terraform-import"&gt;terraform import&lt;/h3&gt;
&lt;p&gt;At this point, you could in theory start creating all of the resources by applying it (with &lt;code&gt;terraform apply&lt;/code&gt;).
In reality, these resources are likely already recognized and managed by the cloud key itself.
The way around that is by &lt;strong&gt;importing&lt;/strong&gt; the resources via &lt;code&gt;terraform import {resource_name} {resource_id}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;{resource_name}&lt;/code&gt; is just the name as you did provide it in the main configuration file, e.g. &lt;code&gt;unifi_user.my_client&lt;/code&gt;
The &lt;code&gt;{resource_id}&lt;/code&gt; is an ID that&amp;rsquo;s used in the API of the cloud key.
This was for me the point for which it all broke down since I couldn&amp;rsquo;t find any good documentation of this API.&lt;/p&gt;
&lt;h3 id="cloud-key-api"&gt;cloud key API&lt;/h3&gt;
&lt;p&gt;Fortunately, I did find some pointer into the cloud key API in a &lt;a href="https://thenewstack.io/how-to-manage-a-home-network-with-infrastructure-as-code/"&gt;blog post from the original module author&lt;/a&gt;.
Another real help to me has been this &lt;a href="https://github.com/Art-of-WiFi/UniFi-API-client/blob/fbfd6a824628d2d45f7b5dadb211cb1191335156/src/Client.php"&gt;unifi PHP client code&lt;/a&gt; which also lists most of the endpoints.&lt;/p&gt;
&lt;p&gt;I did distill all my findings into this &lt;a href="https://mhemeryck.xyz/2021-12-16/cloudkey.postman_collection.json"&gt;cloud key postman collection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s walk through some of the API calls, built using &lt;a href="https://httpie.io/"&gt;&lt;code&gt;httpie&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="login-post-hostapilogin"&gt;login: &lt;code&gt;POST {host}/api/login&lt;/code&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;printf &lt;span style="color:#e6db74"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;username&amp;#34;: &amp;#34;foo&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;password&amp;#34;: &amp;#34;bar&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;}&amp;#39;&lt;/span&gt;| http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; POST &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/login&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content-Type:&lt;span style="color:#e6db74"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=abc; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTTP/1.1 &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Allow-Credentials: true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection: keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Length: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Type: application/json;charset&lt;span style="color:#f92672"&gt;=&lt;/span&gt;UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Date: Thu, &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; Dec &lt;span style="color:#ae81ff"&gt;2021&lt;/span&gt; 20:55:24 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive: timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Set-Cookie: unifises&lt;span style="color:#f92672"&gt;=&lt;/span&gt;19uFvBLta4optBuzwV4QyVip5TbdR0t6; Path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/; Secure; HttpOnly
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Set-Cookie: csrf_token&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Z1dK47sp8NsjfpdbgHNyVNgYewNYIodd; Path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/; Secure
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vary: Origin
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[]&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This call will perform the login on the login endpoint and set a cookie for subsequent calls.
Note that all other calls will need this cookie and that it will expire.&lt;/p&gt;
&lt;h4 id="self-get-hostapissiteself"&gt;self: &lt;code&gt;GET {host}/api/s/{site}/self&lt;/code&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; GET &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/s/default/self&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=cNHfNcA1zQoMW9wzFvCmGwEEo22irJ6D; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTTP/1.1 &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Allow-Credentials: true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection: keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Encoding: gzip
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Content-Type: application/json;charset&lt;span style="color:#f92672"&gt;=&lt;/span&gt;UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Date: Thu, &lt;span style="color:#ae81ff"&gt;16&lt;/span&gt; Dec &lt;span style="color:#ae81ff"&gt;2021&lt;/span&gt; 20:58:28 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive: timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer-Encoding: chunked
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vary: accept-encoding,origin,accept-encoding
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;site_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;{site_id}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;site_name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This just lists some generic information about the site (location), e.g. the &lt;code&gt;site_id&lt;/code&gt; to be imported.&lt;/p&gt;
&lt;h4 id="devices-get-hostapissitestatdevice"&gt;devices: &lt;code&gt;GET {host}/api/s/{site}/stat/device&lt;/code&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; GET &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/s/default/stat/device&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=cNHfNcA1zQoMW9wzFvCmGwEEo22irJ6D; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;5ea4395234309a00041ffa6e&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;adopted&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;anon_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;d5416fde-c2ad-4558-9aa0-4b4024c441de&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;antenna_table&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;: 4,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Combined&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;wifi0_gain&amp;#34;&lt;/span&gt;: 3,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;wifi1_gain&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This endpoint is interesting to get the IDs of the devices to import.&lt;/p&gt;
&lt;h4 id="users-get-hostapissiterestuser"&gt;users: &lt;code&gt;GET {host}/api/s/{site}/rest/user&lt;/code&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;http --follow --timeout &lt;span style="color:#ae81ff"&gt;3600&lt;/span&gt; GET &lt;span style="color:#e6db74"&gt;&amp;#39;https://cloudkey.lan:8443/api/s/default/rest/user&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cookie:&lt;span style="color:#e6db74"&gt;&amp;#39;csrf_token=cNHfNcA1zQoMW9wzFvCmGwEEo22irJ6D; unifises=dfcwZvHcpBwc5LTlOzgxYSlsutpC6F2b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;meta&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;rc&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;9dfeee213c227000f47aade2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mac&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;48:98:e3:f6:21:a5&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;site_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;5dffdda64c2370010eb91fa0&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was the most important endpoint for my purposes: it lists the resource ID under &lt;code&gt;_id&lt;/code&gt; and makes it possible to import it.&lt;/p&gt;
&lt;p&gt;Practically, this means&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform import unifi_user.my_client 9dfeee213c227000f47aade2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, the client should be managed by terraform.&lt;/p&gt;
&lt;p&gt;Refer to the &lt;a href="https://mhemeryck.xyz/2021-12-16/cloudkey.postman_collection.json"&gt;cloud key postman collection&lt;/a&gt; for more resources.
Note that I only really looked into the read-only endpoints (the &lt;code&gt;GET&lt;/code&gt; operations) since I would have terraform do the actual operations (&lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I was really pleased to see that after some crawling of the cloud key API I was able to get the unifi resources managed with terraform.
At this point, I can &lt;strong&gt;manage my unifi configuration under source control&lt;/strong&gt;, most notably the DHCP fixed IPs and some firewall rules.
This only scratches the surface of what is now possible at this point though.&lt;/p&gt;
&lt;p&gt;Since terraform also has modules for various &lt;strong&gt;DNS providers&lt;/strong&gt; such as &lt;a href="https://registry.terraform.io/providers/pan-net/powerdns/latest/docs"&gt;powerdns&lt;/a&gt;, &lt;a href="https://github.com/shelmangroup/terraform-provider-coredns"&gt;coredns&lt;/a&gt; and even &lt;a href="https://registry.terraform.io/providers/ryanwholey/pihole/latest/docs"&gt;pihole&lt;/a&gt; I could start thinking of managing the fixed IPs and the host names from a single spot.
The same local network DNS could then be used by my local kubernetes to register service names into.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s supposedly infrastructure-as-code, I could also start thinking of integrating everything in &lt;strong&gt;CI/CD&lt;/strong&gt; flow, where the terraform code is first validated and then automatically applied.
This in turn would mean I&amp;rsquo;d need to find a way to either self-host a source control / CI/CD platform (thinking of &lt;a href="https://about.gitlab.com/"&gt;gitlab&lt;/a&gt;) or find some way to safely tunnel out to a cloud-based platform with (thinking github actions + &lt;a href="https://www.wireguard.com/"&gt;wireguard&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Another change I&amp;rsquo;d like to make, is to use a &lt;strong&gt;&lt;a href="https://www.terraform.io/language/state/remote-state-data"&gt;remote state back-end&lt;/a&gt;&lt;/strong&gt; instead of the local file storage.&lt;/p&gt;
&lt;p&gt;&amp;hellip; but that&amp;rsquo;s for another time!&lt;/p&gt;</content:encoded></item><item><title>cloud key reset</title><link>https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/</link><pubDate>Wed, 08 Dec 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-12-08-cloudkey_reset/</guid><description>&lt;p&gt;After my &lt;a href="https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/"&gt;last password issues post&lt;/a&gt;, I still had some places for which I didn&amp;rsquo;t have an easy approach to reset the login.
For my local network, I still use a unifi cloud key gen 1, to keep an overview of the network.&lt;/p&gt;
&lt;h2 id="cloud-key"&gt;Cloud key&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://unifi-protect.ui.com/cloud-key-gen2"&gt;ubiquiti unifi cloud key&lt;/a&gt;&lt;/strong&gt; is a small client device on the local network, specifically running the ubiquiti unifi controller software.
The controller software is there to e.g. provision new (ubiquiti) hardware, to configure the network (switch ports, VLANs, firewalls, &amp;hellip;) but also to allow remote management from a web interface or phone app.&lt;/p&gt;</description><content:encoded>&lt;p&gt;After my &lt;a href="https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/"&gt;last password issues post&lt;/a&gt;, I still had some places for which I didn&amp;rsquo;t have an easy approach to reset the login.
For my local network, I still use a unifi cloud key gen 1, to keep an overview of the network.&lt;/p&gt;
&lt;h2 id="cloud-key"&gt;Cloud key&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;&lt;a href="https://unifi-protect.ui.com/cloud-key-gen2"&gt;ubiquiti unifi cloud key&lt;/a&gt;&lt;/strong&gt; is a small client device on the local network, specifically running the ubiquiti unifi controller software.
The controller software is there to e.g. provision new (ubiquiti) hardware, to configure the network (switch ports, VLANs, firewalls, &amp;hellip;) but also to allow remote management from a web interface or phone app.&lt;/p&gt;
&lt;p&gt;Note that you actually &lt;strong&gt;don&amp;rsquo;t need dedicated hardware&lt;/strong&gt; to run the controller software.
You can just as well run the software from your own local computer or instead use the &lt;a href="https://hub.docker.com/r/linuxserver/unifi-controller"&gt;dockerized controller software&lt;/a&gt;.
Having it on a dedicated device though means that it is always available, doing logging and monitoring in the background and providing an entry point for remote access.
Additionally, the cloud key does indeed mainly run the controller software, but it also has onboard firmware to set manage the controller software for you.&lt;/p&gt;
&lt;p&gt;On the upside, I think the &lt;em&gt;unifi experience&lt;/em&gt; is overall quite nice and graphically slick.
Despite being considered a &lt;em&gt;prosumer&lt;/em&gt; kind of brand, some features I would consider as basic are missing though (built-in DHCP / DNS, VPN termination, &amp;hellip;)&lt;/p&gt;
&lt;p&gt;In the end, I still like the unifi products since they do have somewhat more enhanced features yet mostly &lt;strong&gt;&amp;ldquo;just work&amp;rdquo;&lt;/strong&gt;.
There was a time when I was running my own &lt;a href="https://openwrt.org/"&gt;openWrt&lt;/a&gt;-enabled router, but the reality just is that after the initial setup, this is just something I barely feel the need to touch (and maintain).&lt;/p&gt;
&lt;h2 id="factory-reset"&gt;Factory reset&lt;/h2&gt;
&lt;p&gt;As mentioned above, the cloud key hardware runs both the main unifi controller software as well as its own firmware to manage said software.
The login for the controller software is done via my &lt;a href="https://ui.com/"&gt;ui.com&lt;/a&gt; account.
Retrieving that one was as simple as resetting this account.&lt;/p&gt;
&lt;p&gt;The controller &lt;strong&gt;firmware management&lt;/strong&gt; however is also password-protected and being a good citizen, I obviously changed it from its default of &lt;code&gt;ubnt&lt;/code&gt; / &lt;code&gt;ubnt&lt;/code&gt;.
Even though I still had access to the controller software, I figured it&amp;rsquo;d be better to have full access to the firmware management as well.
The rationale is that the times when you often need this is in case of some breaking issue &amp;ndash; in which event you want to have immediate access.&lt;/p&gt;
&lt;p&gt;The cloud key has an &lt;strong&gt;SD-card&lt;/strong&gt;, so I figured that it would run from there (pretty much how a raspberry pi does).
After checking the SD card contents though, they only seemed to contain the regularly scheduled backups.&lt;/p&gt;
&lt;p&gt;Checking the &lt;a href="https://dl.ubnt.com/guides/UniFi/UniFi_Cloud_Key_UC-CK_QSG.pdf"&gt;cloud key gen1 pdf docs&lt;/a&gt; showed me that there is simple &lt;strong&gt;factory reset button&lt;/strong&gt;.
After grabbing a backup snapshot of the controller software, I did decide on just triggering the factory reset.&lt;/p&gt;
&lt;p&gt;Sure enough, the firmware management did now work with the default credentials (prompting me to change it straight after that).&lt;/p&gt;
&lt;h2 id="lets-encrypt-certificates-setup"&gt;Let&amp;rsquo;s Encrypt certificates setup&lt;/h2&gt;
&lt;p&gt;I want to be able to also access the cloud key on a regular URL even if it&amp;rsquo;s only from my local network.
Also, by default the cloud key controller software will use an HTTPS version (which is good), but it obviously has no built-in way to provide a valid certificate (not so good).&lt;/p&gt;
&lt;p&gt;A side-effect of this factory reset was that I had now lost this setup in where the cloud key itself regularly pulls in an updates Let&amp;rsquo;s Encrypt SSL certificate.
In the past, I had followed this nicely detailed blog post on &lt;a href="https://www.jamesridgway.co.uk/auto-renewing-ssl-certificate-unifi-cloud-key-lets-encrypt-cloudflare-dns-validation/"&gt;cloud key SSL certificates with Let&amp;rsquo;s Encrypt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To summarize the steps for myself for future reference:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fixed IP for the cloud key: this actually easy to do from the unifi controller software (running on the cloud key)&lt;/li&gt;
&lt;li&gt;DNS entry for this fixed IP: I use &lt;a href="https://pi-hole.net/"&gt;pihole&lt;/a&gt;&amp;rsquo;s built-in &lt;a href="https://thekelleys.org.uk/dnsmasq/doc.html"&gt;dnsmasq&lt;/a&gt; for this. This means that all devices on my local network will be able to resolve the host for the cloud key&lt;/li&gt;
&lt;li&gt;Set up a job on the cloud key to regularly pull in updated SSL certificates&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="acmesh-lets-encrypt-cloud-key-bot-setup"&gt;&lt;code&gt;acme.sh&lt;/code&gt; Let&amp;rsquo;s Encrypt cloud key bot setup&lt;/h3&gt;
&lt;p&gt;The way &lt;strong&gt;Let&amp;rsquo;s Encrypt works&lt;/strong&gt; is that you can request a certificate for your own domain name.
Afterwards, the Let&amp;rsquo;s Encrypt server will verify with you whether or not you actually own the domain for which you did request the SSL certificate.
This can be done in a multitude of ways, but the most common ones are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;HTTP-based: on the service you expose, you host some kind of secret that was given to you before by Let&amp;rsquo;s Encrypt&lt;/li&gt;
&lt;li&gt;DNS-based: on the DNS provider on which you host the domain, you provide an &lt;code&gt;txt&lt;/code&gt; kind of record which Let&amp;rsquo;s Encrypt, again containing the secret Let&amp;rsquo;s Encrypt had provided before&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For my home network case, I am obliged to use the DNS-based approach, since my own service isn&amp;rsquo;t exposed on the wide internet for Let&amp;rsquo;s Encrypt to call back to.
It&amp;rsquo;s a nice approach to be able to get valid SSL certificates for a machine on a local network.
Additionally, a lot of tooling is readily available to automate all details for you, such as &lt;a href="https://certbot.eff.org/"&gt;certbot&lt;/a&gt; or &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Essentially, these steps are completely taken from the aforementioned post.
Since I wouldn&amp;rsquo;t like to lose these and since I have some minor differences due to using digital ocean as DNS provider, I detail them here again for myself.&lt;/p&gt;
&lt;p&gt;Installing the bot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://get.acme.sh | sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Installing a hook script to run on certificate renewal in &lt;code&gt;/root/.acme.sh/cloudkey-renew-hook.sh&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Renew-hook for ACME / Let&amp;#39;s encrypt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;** Configuring new Let&amp;#39;s Encrypt certs&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /etc/ssl/private
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rm -f /etc/ssl/private/cert.tar /etc/ssl/private/unifi.keystore.jks /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/private/fullchain.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl pkcs12 -export -in /etc/ssl/private/cloudkey.crt -inkey /etc/ssl/private/cloudkey.key -out /etc/ssl/private/cloudkey.p12 -name unifi -password pass:aircontrolenterprise
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore /usr/lib/unifi/data/keystore -srckeystore /etc/ssl/private/cloudkey.p12 -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -alias unifi
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rm -f /etc/ssl/private/cloudkey.p12
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tar -cvf cert.tar *
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chown root:ssl-cert /etc/ssl/private/*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod &lt;span style="color:#ae81ff"&gt;640&lt;/span&gt; /etc/ssl/private/*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;** Testing Nginx and restarting&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/sbin/nginx -t
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/etc/init.d/nginx restart ; /etc/init.d/unifi restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will modify the retrieved certificates in such a way that they can be used by the unifi controller software.&lt;/p&gt;
&lt;p&gt;Export the proper API keys to the environment.
In my case, I am using the &lt;a href="https://github.com/acmesh-official/acme.sh/wiki/dnsapi#20-use-digitalocean-api-native"&gt;digitalocean DNS provider&lt;/a&gt;.
This changes the line in the &lt;code&gt;.bashrc&lt;/code&gt; file to adapt to something like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export DO_API_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&amp;lt;key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first certificate retrieval line changes to&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;acme.sh --force --issue --dns dns_dgon -d &amp;lt;YOUR_DOMAIN&amp;gt; --pre-hook &lt;span style="color:#e6db74"&gt;&amp;#34;touch /etc/ssl/private/cert.tar; tar -zcvf /root/.acme.sh/CloudKeySSL_`date +%Y-%m-%d_%H.%M.%S`.tgz /etc/ssl/private/*&amp;#34;&lt;/span&gt; --fullchainpath /etc/ssl/private/cloudkey.crt --keypath /etc/ssl/private/cloudkey.key --reloadcmd &lt;span style="color:#e6db74"&gt;&amp;#34;sh /root/.acme.sh/cloudkey-renew-hook.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The crontab entry becomes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; * * * /root/.acme.sh/acme.sh --renew --apache --renew-hook /root/.acme.sh/cloudkey-renew-hook.sh -d &amp;lt;YOUR_DOMAIN&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, everything should be back up and running and the cloud key should be accessible from the custom domain &lt;em&gt;with&lt;/em&gt; a valid certificate!&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;This post serves mainly as a &lt;strong&gt;note-to-self&lt;/strong&gt; after needing to go through some steps to manually recover my cloud key setup.&lt;/p&gt;
&lt;p&gt;At this point, the cloud key is just back running as before.
Going through this process again made me realize some points of improvement.&lt;/p&gt;
&lt;p&gt;Firstly, in the past I did &lt;strong&gt;run the controller software as part of my home &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt; cluster&lt;/strong&gt;, but I ended up not doing that since it cost me a lot of resources and I basically did not want to manage that.
However, I still have some plans to upgrade the cluster setup itself at which point it might come interesting again to run it there.
The added value would be that I could manage the certificate renewal using &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; instead of via a cron job.
The downside of the cron job on the cloud key is that it gets wiped for firmware upgrades.&lt;/p&gt;
&lt;p&gt;Secondly, I am also not completely happy with the &lt;strong&gt;manual host name&lt;/strong&gt; management.
I first need to configure a static IP manually in the controller software and then also manually configure pihole / dnsmasq.
It seems there is a &lt;a href="https://registry.terraform.io/providers/paultyng/unifi/latest/docs"&gt;unifi terraform&lt;/a&gt; provider that would be able to handle the IP management &amp;ndash; but I haven&amp;rsquo;t been able yet to get that working with the cloud key (perhaps it does not work for the gen 1?).
DNS configuration can be done via API using e.g. &lt;a href="https://www.powerdns.com/"&gt;powerdns&lt;/a&gt; or &lt;a href="https://coredns.io/"&gt;coredns&lt;/a&gt;, which is another route to explore.
Services running on the kubernetes cluster could then also register their host names using &lt;a href="https://github.com/kubernetes-sigs/external-dns"&gt;external-dns&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thirdly, it would also be nice that any of the host names I set for my local network are actually accessible in a similar manner over a &lt;strong&gt;&lt;a href="https://www.wireguard.com/"&gt;wireguard&lt;/a&gt; VPN&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A final small change would be for me to &lt;strong&gt;change DNS provider&lt;/strong&gt; from digital ocean to &lt;a href="https://www.hetzner.com/cloud"&gt;hetzner cloud&lt;/a&gt; since I have everything else there.&lt;/p&gt;
&lt;p&gt;Still a lot of home lab ideas, so little time, let&amp;rsquo;s see what&amp;rsquo;s next!&lt;/p&gt;</content:encoded></item></channel></rss>