<?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>Note-to-Self on mhemeryck</title><link>https://mhemeryck.xyz/tags/note-to-self/</link><description>Recent content in Note-to-Self 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/note-to-self/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><item><title>Password troubles</title><link>https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/</link><pubDate>Wed, 01 Dec 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-12-01-password_fsckup/</guid><description>&lt;p&gt;Last Saturday, my new phone arrived in the mail.
As a techie, I was naturally delighted to get my new treat up and running.
Both my old and my new phone are Android phones and I was surprised to see how (shockingly) easy it was to migrate all my stuff.
Not all settings were migrated directly though; mostly passwords would need to be set up again.&lt;/p&gt;
&lt;p&gt;Since I did use gnu pass + GPG for my password management as described in my &lt;a href="https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/"&gt;previous password management post&lt;/a&gt; earlier this year, I figured this would be quite easy to do!&lt;/p&gt;</description><content:encoded>&lt;p&gt;Last Saturday, my new phone arrived in the mail.
As a techie, I was naturally delighted to get my new treat up and running.
Both my old and my new phone are Android phones and I was surprised to see how (shockingly) easy it was to migrate all my stuff.
Not all settings were migrated directly though; mostly passwords would need to be set up again.&lt;/p&gt;
&lt;p&gt;Since I did use gnu pass + GPG for my password management as described in my &lt;a href="https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/"&gt;previous password management post&lt;/a&gt; earlier this year, I figured this would be quite easy to do!&lt;/p&gt;
&lt;p&gt;Unfortunately, the whole endeavor ended with me losing my current GPG sub keys and (again) &lt;em&gt;all&lt;/em&gt; my passwords.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;But first, let&amp;rsquo;s explain the setup again.&lt;/p&gt;
&lt;h3 id="gpg"&gt;GPG&lt;/h3&gt;
&lt;p&gt;I have a main GPG public / private key pair, valid indefinitely, stored offline.
From these, I generate 3 sub key pairs valid for the coming year:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;encryption key: encrypt / decrypt stuff, notably passwords&lt;/li&gt;
&lt;li&gt;signing key&lt;/li&gt;
&lt;li&gt;authentication key: to be used for SSH.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="pass"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; is essentially a wrapper around GPG.
It manages a folder in your home folder containing an encrypted file using a previously defined GPG encryption key.
Typically, that&amp;rsquo;s the GPG encryption sub key mentioned earlier.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pass&lt;/code&gt; also wraps around &lt;code&gt;git&lt;/code&gt;, meaning you can have remote (encrypted) versioned backup of your password storage using &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="chrome-integration-browserpass"&gt;Chrome integration: browserpass&lt;/h3&gt;
&lt;p&gt;Chrome has an extension called &lt;a href="https://github.com/browserpass/browserpass-extension"&gt;browserpass&lt;/a&gt; which essentially allows you to fill in passwords by calling &lt;code&gt;pass&lt;/code&gt;.
Apart from the passwords, it&amp;rsquo;s also possible to store the usernames (or any extra data, really) as part of the secret in &lt;code&gt;pass&lt;/code&gt;, which browserpass will fill in for you.
I&amp;rsquo;m using the extension for chrome, but I suppose there are similar integrations for other browsers as well.&lt;/p&gt;
&lt;h3 id="android-integration-openkeychain--password-store"&gt;Android integration: openkeychain + password store&lt;/h3&gt;
&lt;p&gt;My phone setup is quite similar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.openkeychain.org/"&gt;openkeychain&lt;/a&gt; for the keys (&lt;code&gt;gpg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.google.com/store/apps/details?id=dev.msfjarvis.aps"&gt;password store&lt;/a&gt; for the passwords (&lt;code&gt;pass&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since &lt;code&gt;pass&lt;/code&gt; has the possibility to sync the full password store based on &lt;code&gt;git&lt;/code&gt;, it&amp;rsquo;s actually quite easy to keep my laptop and phone in sync.&lt;/p&gt;
&lt;h3 id="yubikey"&gt;yubikey&lt;/h3&gt;
&lt;p&gt;The yubikey provides a suite of different cryptographic operations (e.g. &lt;a href="https://www.yubico.com/authentication-standards/fido-u2f/"&gt;U2F&lt;/a&gt;), but in this case, I use to also &lt;em&gt;physically&lt;/em&gt; contain aforementioned GPG private sub keys.&lt;/p&gt;
&lt;p&gt;For example, in the case of password management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;encrypt a new password: insert a new password with pass &lt;code&gt;pass insert &amp;lt;passname&amp;gt;&lt;/code&gt;. &lt;code&gt;pass&lt;/code&gt; will use the public key to encrypt the password&lt;/li&gt;
&lt;li&gt;decrypt the password: you&amp;rsquo;d need the private key to decrypt. Since the private key is never on my laptop, I need to insert my yubikey to decrypt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For SSH authentication, I use something similar where the GPG agent provides the SSH process an SSH private key, based on the GPG authentication sub key, which lives on the yubikey.&lt;/p&gt;
&lt;p&gt;Having the GPG sub keys not physically on the laptop is safer but means you can&amp;rsquo;t afford losing the yubikey.
To mitigate this risk of losing the yubikey (and the GPG sub keys on them), I would save an offline backup of the sub keys prior to copying them to the yubikey.&lt;/p&gt;
&lt;p&gt;So, at this point, when there&amp;rsquo;s a backup, nothing can go wrong anymore, right?&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;Well, no.&lt;/p&gt;
&lt;p&gt;In my &lt;a href="https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/"&gt;previous password management post&lt;/a&gt;, the issue was that I did drop my old GPG decryption key before decrypting the passwords.&lt;/p&gt;
&lt;p&gt;In this case, multiple things went wrong.&lt;/p&gt;
&lt;p&gt;First of all the &lt;a href="https://www.openkeychain.org/"&gt;openkeychain&lt;/a&gt; app on my &lt;strong&gt;new phone&lt;/strong&gt; did not directly recognize my yubikey.
After trying a second time, it indicated &lt;em&gt;there were no GPG keys on the yubikey&lt;/em&gt;.
Checking on my laptop indeed revealed the yubikey had no more GPG keys.
Even more, at this point, it looked like the yubikey had been pretty much wiped to factory state.&lt;/p&gt;
&lt;p&gt;Next step is checking out the offline backup.
I did find the sub keys again that I had saved for this very purpose.
Re-importing them did however not result in being able to decrypt again.&lt;/p&gt;
&lt;p&gt;To verify what had happened, I did start from a live OS boot (i.e. &lt;a href="https://tails.boum.org/index.en.html"&gt;tails&lt;/a&gt;) and tried a clean setup with all my private keys.&lt;/p&gt;
&lt;p&gt;What it showed me was something like this (redacted):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;~ gpg -K
/$HOME/.gnupg/pubring.kbx
----------------------------------
sec rsa4096 2016-06-24 [SC]
1B477ADA04B47A5CE61AEDE01AA36833BC86F0F1
uid [ultimate] Martijn ... &amp;lt;user@mail.com&amp;gt;
ssb rsa4096 2016-06-24 [E]
ssb&amp;gt; rsa4096 2021-03-09 [E] [expires: ...]
ssb&amp;gt; rsa4096 2021-03-09 [S] [expires: ...]
ssb&amp;gt; rsa4096 2021-03-09 [A] [expires: ...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The bracket &lt;code&gt;&amp;gt;&lt;/code&gt; in front of &lt;code&gt;ssb&lt;/code&gt; means that it&amp;rsquo;s a &lt;em&gt;stub&lt;/em&gt; of a sub key.
A stub is what is left over on the key ring after the private key has been moved to the key card (the yubikey).
Apparently, instead of making a backup of the key, I had made a back-up of the stub &amp;hellip;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;key issue is that the &lt;code&gt;gpg&lt;/code&gt; &lt;code&gt;keytocard&lt;/code&gt; operation is a destructive one&lt;/strong&gt;.
If you move the private key to the card, only the stub is left, never to be retrieved again.
To make a proper back-up of the private key (sub key or full key), do it before you move it over to the key card!&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Since this was the second time in a relatively short time I had principally lost all my logins, I really felt like giving up altogether on this solution for password management.&lt;/p&gt;
&lt;p&gt;However, the whole &lt;code&gt;gpg&lt;/code&gt; + &lt;code&gt;pass&lt;/code&gt; + &lt;code&gt;git&lt;/code&gt; + yubikey combo still is quite appealing to me.
It fits in the true &lt;a href="https://en.wikipedia.org/wiki/Unix_philosophy"&gt;Unix philosophy&lt;/a&gt; of &amp;ldquo;having each program do one thing well&amp;rdquo;.
I also like the fact I&amp;rsquo;m myself in complete control of the data instead of relying on a remote cloud provider, specifically if it is about sensitive data.&lt;/p&gt;
&lt;p&gt;Ultimately, after a couple of days of re-requesting logins, I&amp;rsquo;m back to before, this time with a proper backup (fingers crossed!).&lt;/p&gt;</content:encoded></item><item><title>Bootstrapping a pi4</title><link>https://mhemeryck.xyz/posts/2021-02-21-bootstrapping/</link><pubDate>Sun, 21 Feb 2021 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2021-02-21-bootstrapping/</guid><description>&lt;p&gt;I&amp;rsquo;ve recently bought myself a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-400/"&gt;raspberry pi 400&lt;/a&gt;, a small form factor keyboard with a &lt;em&gt;raspberry pi 4 4GB RAM&lt;/em&gt; built into it.
In hindsight, there are probably very few scenarios in which such a computer makes actually makes sense to me, but it still looks quite nice.
I first took it for a spin using the raspberry pi OS that was on the SD card that came along with the set, but while I can acknowledge that it works quite OK out of the box, it just looks so ugly.
Henceforth, I did decide on installing &lt;a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4"&gt;arch for ARM&lt;/a&gt; on it.
Since I did notice that I need to rethink every time again what are the best tools to install on a fresh install, this is a write-up of the most important tools I would always install.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve recently bought myself a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-400/"&gt;raspberry pi 400&lt;/a&gt;, a small form factor keyboard with a &lt;em&gt;raspberry pi 4 4GB RAM&lt;/em&gt; built into it.
In hindsight, there are probably very few scenarios in which such a computer makes actually makes sense to me, but it still looks quite nice.
I first took it for a spin using the raspberry pi OS that was on the SD card that came along with the set, but while I can acknowledge that it works quite OK out of the box, it just looks so ugly.
Henceforth, I did decide on installing &lt;a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4"&gt;arch for ARM&lt;/a&gt; on it.
Since I did notice that I need to rethink every time again what are the best tools to install on a fresh install, this is a write-up of the most important tools I would always install.&lt;/p&gt;
&lt;p&gt;The key 2 tools are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.passwordstore.org/"&gt;pass&lt;/a&gt; password manager&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yadm.io/"&gt;yadm&lt;/a&gt; yet-another-dotfile-manager&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rationale behind it is the following: I use &lt;code&gt;pass&lt;/code&gt; to be able to get easy access to all my passwords and logins, &lt;code&gt;yadm&lt;/code&gt; manages all the other dotfiles for me.&lt;/p&gt;
&lt;h2 id="arch"&gt;Arch&lt;/h2&gt;
&lt;p&gt;For a &amp;ldquo;regular&amp;rdquo; arch install, see the &lt;a href="https://wiki.archlinux.org/index.php/installation_guide"&gt;arch installation guide&lt;/a&gt;.
In this case, the installation was a bit atypical since it was for an ARM platform, see &lt;a href="https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4"&gt;arch for ARM&lt;/a&gt; for this (I did use the ARMv7 installation guide).&lt;/p&gt;
&lt;p&gt;Other noteworthy steps in the installation &amp;hellip;&lt;/p&gt;
&lt;h2 id="arch-linux-arm-noisy-boot"&gt;arch linux arm: noisy boot&lt;/h2&gt;
&lt;p&gt;Terminal would show all of the kernel audit messages; add &lt;code&gt;audit=0&lt;/code&gt; to command line options &lt;code&gt;/boot/cmdline.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Remove noisy welcome message&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm /etc/motd
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="helpers"&gt;Helpers&lt;/h2&gt;
&lt;p&gt;Some helpers to install with the subsequent steps:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -Syu --noconfirm zsh sudo which vim git openssh libfido2
pacman -S --noconfirm --needed base-devel
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="add-a-user"&gt;Add a user&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;touch /etc/skel/.zshrc
groupadd sudo
useradd -m $USER -g $GROUP -G sudo -s /usr/bin/zsh
echo &amp;quot;%sudo ALL=(ALL) NOPASSWD: ALL&amp;quot; &amp;gt; /etc/sudoers.d/$USER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update password&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;passwd $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="install-pikaur"&gt;Install &lt;code&gt;pikaur&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git clone https://aur.archlinux.org/pikaur.git
cd pikaur
makepkg -fsri
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="some-more-helpers"&gt;Some more helpers&lt;/h2&gt;
&lt;p&gt;Some more helpers I&amp;rsquo;d always like to keep around&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S --noconfirm ctags oh-my-zsh-git ripgrep fzf bat tmux
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="git-default-config"&gt;git default config&lt;/h2&gt;
&lt;p&gt;Since we&amp;rsquo;ll be using &lt;code&gt;git&lt;/code&gt;, you&amp;rsquo;ll need to define some initial config&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global user.email &amp;lt;email&amp;gt;
git config --global user.name &amp;quot;&amp;lt;name&amp;gt;&amp;quot;
git config --global init.defaultBranch master
git config --global pull.rebase true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="gpg"&gt;&lt;code&gt;gpg&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Import stubs from yubikey&lt;/p&gt;
&lt;p&gt;Install and enable smart card reader:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S ccid
systemctl enable pcscd
systemctl start pcscd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fetch keys from card&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-card
&amp;gt; fetch
&amp;gt; quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Get the key id&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;KEYID=$(gpg --with-colons --fingerprint | awk -F: '$1 == &amp;quot;fpr&amp;quot; {print $10;}' | head -n1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update the trust of the key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-key $KEYID
&amp;gt; trust
&amp;gt; 5
&amp;gt; quit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable ssh-agent using gpg-agent (temporarily; the final &lt;code&gt;zshrc&lt;/code&gt; file will contain the proper setup)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export SSH_AUTH_SOCK=&amp;quot;$(gpgconf --list-dirs agent-ssh-socket)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tell the gpg-agent to prompt on the current TTY:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg-connect-agent updatestartuptty /bye
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="pass"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Install pass&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Init pass&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass init $KEYID
pass git remote add origin git@gitlab.com:&amp;lt;repo&amp;gt;
pass git pull
pass git reset --hard origin/master
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="yadm"&gt;&lt;code&gt;yadm&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;pikaur -S yadm
yadm clone git@gitlab.com:&amp;lt;repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, I have all the minimum tools I would use for my daily work.&lt;/p&gt;
&lt;h2 id="ui"&gt;UI&lt;/h2&gt;
&lt;p&gt;Even though I would spend most of my life on the command line, I also a graphical environment to work in.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to delve to deeply into that, since I mostly prefer vanilla &lt;a href="https://www.gnome.org"&gt;gnome3&lt;/a&gt; for that, combined with &lt;a href="https://wiki.gnome.org/Projects/GDM"&gt;&lt;code&gt;gdm&lt;/code&gt;&lt;/a&gt; for a login manager.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;My initial goal was to find a way of completely automating my initial installs, but in the end I figured this wasn&amp;rsquo;t what I really needed, because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;distro-specific: I don&amp;rsquo;t always run on the same distro, so not everything is portable&lt;/li&gt;
&lt;li&gt;other requirements: for unattended installs (servers), I don&amp;rsquo;t want / need to copy all my secrets on there (albeit encrypted or not)&lt;/li&gt;
&lt;li&gt;outdated: for my daily driver install, I have no need to often keep on reprovisioning my install. By the time I would have a need to reprovision, a lot of the setup has probably changed, so it makes more sense to (vaguely) document some of the &lt;em&gt;basic&lt;/em&gt; steps instead of a fully automated machine image.&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Updating GPG subkeys</title><link>https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/</link><pubDate>Tue, 09 Jul 2019 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2019-07-09-updating_gpg_subkeys/</guid><description>&lt;p&gt;This is just a list of things to consider when updating my GPG subkeys (encryption, signing, authentication) for the particular setup I use on a daily basis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;one master key without any expiry date to generate and revoke subkeys&lt;/li&gt;
&lt;li&gt;I use &lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; as my password manager.
&lt;ol&gt;
&lt;li&gt;the basic idea is that it&amp;rsquo;s a command-line password manager that uses gpg under the hood for encrypting / decrypting the passwords.&lt;/li&gt;
&lt;li&gt;the passwords are encrypted with an encryption subkey&lt;/li&gt;
&lt;li&gt;pass allows managing its history with git, linking the storage on a remote gitlab / github server.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;the subkeys are only stored on my yubikey&lt;/li&gt;
&lt;li&gt;the subkeys are always valid for a period of one year&lt;/li&gt;
&lt;li&gt;the yubikey I use also has NFC, which makes it possible to use it on my android phone as well&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I recently needed to go through this process and did forget to re-encrypt &amp;hellip;&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is just a list of things to consider when updating my GPG subkeys (encryption, signing, authentication) for the particular setup I use on a daily basis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;one master key without any expiry date to generate and revoke subkeys&lt;/li&gt;
&lt;li&gt;I use &lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; as my password manager.
&lt;ol&gt;
&lt;li&gt;the basic idea is that it&amp;rsquo;s a command-line password manager that uses gpg under the hood for encrypting / decrypting the passwords.&lt;/li&gt;
&lt;li&gt;the passwords are encrypted with an encryption subkey&lt;/li&gt;
&lt;li&gt;pass allows managing its history with git, linking the storage on a remote gitlab / github server.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;the subkeys are only stored on my yubikey&lt;/li&gt;
&lt;li&gt;the subkeys are always valid for a period of one year&lt;/li&gt;
&lt;li&gt;the yubikey I use also has NFC, which makes it possible to use it on my android phone as well&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I recently needed to go through this process and did forget to re-encrypt &amp;hellip;&lt;/p&gt;
&lt;p&gt;The main steps are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;generate new subkeys&lt;/li&gt;
&lt;li&gt;pass: re-encrypt with &lt;em&gt;both&lt;/em&gt; sets of keys available&lt;/li&gt;
&lt;li&gt;move the new keys to the card&lt;/li&gt;
&lt;li&gt;backup the master key again and remove all keys from the local machine&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="generate-the-new-subkeys"&gt;Generate the new subkeys&lt;/h2&gt;
&lt;p&gt;First, put the master key back on the local machine, such that we can generate new subkeys.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re really paranoid, you should consider something like &lt;a href="https://tails.boum.org/"&gt;tails&lt;/a&gt; to have a safe live OS for doing all these operations.&lt;/p&gt;
&lt;p&gt;Add the key again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --import priv.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;priv.asc&lt;/code&gt; obviously is the backup of your master key.&lt;/p&gt;
&lt;p&gt;Start generating new subkeys for that master key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-key &amp;lt;foo@bar.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will now get a prompt for doing operations on the master key. Go for &lt;code&gt;addkey&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg&amp;gt;addkey
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: in case you didn&amp;rsquo;t have your master key, this will not work.&lt;/p&gt;
&lt;p&gt;You can now add your signing and encryption keys; some parameters I&amp;rsquo;d like to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4096b size&lt;/li&gt;
&lt;li&gt;1y expiration date&lt;/li&gt;
&lt;li&gt;RSA for both encryption / signing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;don&amp;rsquo;t forget to call &lt;code&gt;save&lt;/code&gt; in the end.&lt;/p&gt;
&lt;p&gt;You can now list all the keys:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -K
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="re-encrypt-all-passwords"&gt;Re-encrypt all passwords&lt;/h2&gt;
&lt;p&gt;This is really important (I messed up big time here): before you remove any older keys, make sure that they are re-encrypted with the new subkey!&lt;/p&gt;
&lt;p&gt;List the fingerprint of the new subkey you&amp;rsquo;d like to use for re-encrypting:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --fingerprint --fingerprint &amp;lt;foo@bar.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Re-encrypt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass init &amp;lt;fingerprint subkey&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, already push them out&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pass git push
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="push-the-new-subkeys-to-the-card"&gt;Push the new subkeys to the card&lt;/h2&gt;
&lt;p&gt;Again edit the key&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --edit-key &amp;lt;foo@bar.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Select the subkey&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;key &amp;lt;N&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where N is the count in the list of keys (visually not that obvious)&lt;/p&gt;
&lt;p&gt;Add the key to the card&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;keytocard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;select the slot to save to (encryption for encryption, signing for signing &amp;hellip;)&lt;/p&gt;
&lt;p&gt;and save&lt;/p&gt;
&lt;h2 id="remove-the-keys-from-the-local-computer-again"&gt;Remove the keys from the local computer again&lt;/h2&gt;
&lt;p&gt;First, make a backup of everything again; see &lt;a href="http://www.racoonlab.com/2013/02/how-to-remove-the-private-master-key-from-your-laptop/"&gt;gpg backup tutorial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Export your private key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -a --export-secret-keys &amp;lt;foo@bar.com&amp;gt; &amp;gt; priv.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Export public key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -a --export &amp;lt;foo@bar.com&amp;gt; &amp;gt; pub.asc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, already push it out to a key server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --send-keys &amp;lt;fingerprint&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, delete everything (best to unplug the key card):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --delete-secret-key &amp;lt;fingerprint&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plug the key card back in and sync&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --card-status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the keys are available albeit from the key card:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg -K
&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>MQTT over TLS</title><link>https://mhemeryck.xyz/posts/2018-11-12-mqtt_over_tls/</link><pubDate>Mon, 12 Nov 2018 00:00:00 +0000</pubDate><guid>https://mhemeryck.xyz/posts/2018-11-12-mqtt_over_tls/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MQTT"&gt;MQTT&lt;/a&gt; is a popular lightweight protocol for use in home automation.
If features a central broker where each of the clients can communicate with in a pub / sub fashion.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;ve been toying around with some home automation setups, I was curious how to secure it with TLS.&lt;/p&gt;
&lt;h2 id="mosquitto"&gt;Mosquitto&lt;/h2&gt;
&lt;p&gt;The most commonly used broker implementation I have come across is &lt;a href="https://mosquitto.org/"&gt;mosquitto&lt;/a&gt; and is quite straightforward to setup on most *nix flavors.
In my setup, I wanted to run it on Raspberry Pi, which I could leave running at my local home network.
Since it runs raspbian, installing it was as simple as:&lt;/p&gt;</description><content:encoded>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MQTT"&gt;MQTT&lt;/a&gt; is a popular lightweight protocol for use in home automation.
If features a central broker where each of the clients can communicate with in a pub / sub fashion.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;ve been toying around with some home automation setups, I was curious how to secure it with TLS.&lt;/p&gt;
&lt;h2 id="mosquitto"&gt;Mosquitto&lt;/h2&gt;
&lt;p&gt;The most commonly used broker implementation I have come across is &lt;a href="https://mosquitto.org/"&gt;mosquitto&lt;/a&gt; and is quite straightforward to setup on most *nix flavors.
In my setup, I wanted to run it on Raspberry Pi, which I could leave running at my local home network.
Since it runs raspbian, installing it was as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ sudo apt-get install mosquitto
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tls-setup-keys-and-certificates"&gt;TLS setup: keys and certificates&lt;/h2&gt;
&lt;p&gt;Doing the TLS setup requires a number of key and certificate files.
The official &lt;a href="https://mosquitto.org/man/mosquitto-tls-7.html"&gt;mosquitto-tls man page&lt;/a&gt; fortunately lists all of them and how to generate them.&lt;/p&gt;
&lt;p&gt;Note that for the final setup, not all of these files are required; these are the files we &lt;em&gt;do&lt;/em&gt; need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ca.crt&lt;/code&gt;: Certificate Authority certificate: the &amp;ldquo;central&amp;rdquo; authority certifying ownership of the public key&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.key&lt;/code&gt;: server private key, linked to the certificate.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.crt&lt;/code&gt;: certificate from the server (MQTT broker), signing the server key pair, by the CA.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="file-layout-on-the-broker"&gt;File layout on the broker&lt;/h2&gt;
&lt;p&gt;On my setup, most of these folders were already available after install, so I figured to actually use them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ tree /etc/mosquitto
/etc/mosquitto
├── ca_certificates
│   ├── ca.crt
│   └── README
├── certs
│   ├── README
│   ├── server.crt
│   └── server.key
├── conf.d
│   ├── README
│   └── tls.conf
└── mosquitto.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="mosquittoconf"&gt;&lt;code&gt;mosquitto.conf&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This is pretty the same config that was also provided by the built-in; just to show:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ cat /etc/mosquitto/mosquitto.conf
# Place your local configuration in /etc/mosquitto/conf.d/
#
# A full description of the configuration file is at
# /usr/share/doc/mosquitto/examples/mosquitto.conf.example
pid_file /var/run/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
# This include assures that our own custom config gets loaded
include_dir /etc/mosquitto/conf.d
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="tlsconf"&gt;&lt;code&gt;tls.conf&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Actual TLS specific config, pointing to all the files that were added.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ cat /etc/mosquitto/conf.d/tls.conf
port 8883
cafile /etc/mosquitto/ca_certificates/ca.crt
keyfile /etc/mosquitto/certs/server.key
certfile /etc/mosquitto/certs/server.crt
tls_version tlsv1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="reload-the-setup"&gt;Reload the setup&lt;/h2&gt;
&lt;p&gt;The package installer also conveniently creates the required (systemd) service for us.
Simply restart the service now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart mosquitto.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="client-subscribe"&gt;Client subscribe&lt;/h2&gt;
&lt;p&gt;With the broker running the new config, testing can easily be done with the mosquitto built-in &lt;code&gt;mosquitto_pub&lt;/code&gt; / &lt;code&gt;mosquitto_sub&lt;/code&gt; commands.
Obviously, all clients that want to use this config now need the CA file.&lt;/p&gt;
&lt;p&gt;On a client: subscribe:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ mosquitto_sub --cafile ca.crt -h raspberrypi.lan -t test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the host name should correspond to the host name in the certificate.&lt;/p&gt;
&lt;h2 id="client-publish"&gt;Client publish&lt;/h2&gt;
&lt;p&gt;On a client: publish&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~ mosquitto_pub --cafile ca.crt -h raspberrypi.lan -t test -m &amp;quot;hi there&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the window running the subscribe, the message should now appear.&lt;/p&gt;
&lt;p&gt;Here, the clients do &lt;em&gt;not&lt;/em&gt; verify themselves though, they just use the certificate to encrypt traffic over TLS.&lt;/p&gt;</content:encoded></item></channel></rss>