<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Hints-and-Tips on Puppeteers Oy</title>
		<link>https://www.puppeteers.net/categories/hints-and-tips/</link>
		<description>Recent content in Hints-and-Tips on Puppeteers Oy</description>
		<generator>Hugo</generator>
		<language>en-US</language>
		
		
		
		
			<lastBuildDate>Mon, 02 Dec 2024 00:00:00 +0000</lastBuildDate>
		
			<atom:link href="https://www.puppeteers.net/categories/hints-and-tips/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Getting latest Rocky Linux AMI ID</title>
				<link>https://www.puppeteers.net/blog/getting-latest-rocky-linux-ami-id/</link>
				<pubDate>Mon, 02 Dec 2024 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/getting-latest-rocky-linux-ami-id/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-timmossholder-1934895.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://rockylinux.org/&#34;&gt;Rocky Linux&lt;/a&gt; 9 is a bug-by-bug clone of &lt;a href=&#34;https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux&#34;&gt;Red Hat Enterprise Linux&lt;/a&gt; 9 (RHEL 9). We generally recommend using RHEL 9 for mission-critical workloads, but for less important services Rocky Linux 9 is a very good choice if you&amp;rsquo;re in the Red Hat land already. Official Rocky images are only available in AWS Marketplace, which complicates things when you need to programmatically determine the latest version. This is a common requirement when you do &lt;a href=&#34;https://www.puppeteers.net/learn/understanding-infrastructure-as-code-iac/&#34;&gt;infrastructure as code&lt;/a&gt;, for example with Ansible, &lt;a href=&#34;https://www.puppeteers.net/learn/what-is-terraform-and-how-does-it-work/&#34;&gt;Terraform&lt;/a&gt; or Packer. Although we speak of latest Rocky Linux AMI here, everything in here is applicable for any other AWS Marketplace images, for example Alma Linux.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Fixing ansible-playbook hangs caused by SSH timeouts</title>
				<link>https://www.puppeteers.net/blog/fixing-ansible-playbook-hangs-caused-by-ssh-timeouts/</link>
				<pubDate>Fri, 29 Nov 2024 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/fixing-ansible-playbook-hangs-caused-by-ssh-timeouts/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-brent-mcquery-531366837-22300915.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.ansible.com/&#34;&gt;Ansible&lt;/a&gt; is an &lt;a href=&#34;https://www.puppeteers.net/learn/understanding-infrastructure-as-code-iac/&#34;&gt;infrastructure as code&lt;/a&gt; tool that uses SSH as its transport mechanism. When the targeted nodes are close, latency-vise to the Ansible controller things usually work fine with the default settings. However, when the targets are far away or latency is big, you may notice that ansible-playbook hangs on some long-running task and never recovers. When you rerun the playbook it may proceed further and then hang at some other task. Or the same task. This is incredibly frustrating, but fortunately also fairly easy to fix.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Alldomains.hosting IPv6 support</title>
				<link>https://www.puppeteers.net/blog/alldomains-hosting-ipv6-support/</link>
				<pubDate>Wed, 25 Sep 2024 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/alldomains-hosting-ipv6-support/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-maksgelatin-5605061.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;ve recently started using &lt;a href=&#34;https://alldomains.hosting/en/index.html&#34;&gt;alldomains.hosting&lt;/a&gt; for what we call &amp;ldquo;programmatic emails&amp;rdquo;. For that purpose alldomains.hosting is dirt cheap (~1€/year/email address), which makes one quite motivated to suffer through the confusing website to figure out how to buy stuff from them. Once you&amp;rsquo;ve managed to buy your domain and/or mail services they just &amp;ldquo;tend to work&amp;rdquo;. Alldomains.hosting IPv6 support, or lack of it, may be a problem for you, though.&lt;/p&gt;&#xA;&lt;p&gt;For example, in AWS EC2 you nowadays really want to avoid assigning public IPv4 addresses to EC2 instances as they cost quite a lot. So, you set up an IPv6-only egress-only internet gateway in your VPC and, for the most part, everything &amp;ldquo;just works&amp;rdquo;. Then you try to add mail delivery via alldomains.hosting and IPv6 and &amp;ldquo;boom&amp;rdquo;, it does not work. According to their support (as of 25.9.2024) their mail servers lack IPv6 support.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Stop EC2 instances automatically with Terraform</title>
				<link>https://www.puppeteers.net/blog/stop-ec2-instances-automatically-with-terraform/</link>
				<pubDate>Thu, 04 Jan 2024 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/stop-ec2-instances-automatically-with-terraform/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-mikhail-nilov-7663143.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;AWS EC2 is a very convenient place for experimenting with and testing things on virtual machines (or &amp;ldquo;EC2 instances&amp;rdquo;). However, it is also very easy to forget such EC2 instances running for no reason, which can become very costly very quickly. Fortunately In AWS computing (e.g. CPU, memory and I/O usage) and storage (disk) usage are billed separately. In other words: if your EC2 instance is stopped, you only pay for its storage. This cuts down the costs &lt;em&gt;significantly&lt;/em&gt;, especially if your are using powerful EC2 instance types. So, you definitely should to stop EC2 instances automatically, say, every evening, if you don&amp;rsquo;t need them to be up. There is no built-in way in to do, though, so this article shows a couple ways it can be done.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Excluding down targets in Prometheus</title>
				<link>https://www.puppeteers.net/blog/excluding-down-targets-in-prometheus/</link>
				<pubDate>Fri, 11 Aug 2023 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/excluding-down-targets-in-prometheus/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-kelly-2543201-trimmed-1-1021x1024.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;Excluding down targets in Prometheus is easy. It allow you to monitor volatile systems that start and shut down on demand.&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://prometheus.io/&#34;&gt;Prometheus&lt;/a&gt; is an open source metrics and alerting solution used to monitor a wide range of things. Unlike many classic network monitoring systems Prometheus is focused on colleting metrics. This makes it quite easy to do things that would be difficult in others systems. For example excluding down targets in Prometheus is quite trivial. This can be highly useful when you have volatile, static targets that come and go - for example Jenkins slaves (or build agents). Even on such targets you&amp;rsquo;d want to monitor things like systemd service status, memory usage and disk usage.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Keycloak Javascript policy deployment</title>
				<link>https://www.puppeteers.net/blog/keycloak-javascript-policy-deployment/</link>
				<pubDate>Wed, 19 Apr 2023 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/keycloak-javascript-policy-deployment/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-serinus-5149241.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;This article tries to include &lt;em&gt;everything&lt;/em&gt; you need to know about Keycloak Javascript policy deployment. By Javascript policies I mean authorization policies of Javascript type attached to Keycloak clients that have authorization enabled. If you don&amp;rsquo;t know what Keycloak &lt;a href=&#34;https://www.puppeteers.net/blog/keycloak-authorization-services-terminology/&#34;&gt;authorization services&lt;/a&gt; are, you are probably in the wrong place.&lt;/p&gt;&#xA;&lt;p&gt;While the process of creating and deploying the JAR files is pretty well documented, it is not altogether clear for the uninitiated how the policies are shown in Keycloak.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Wrong Route53 domain contact details</title>
				<link>https://www.puppeteers.net/blog/wrong-route53-domain-contact-details/</link>
				<pubDate>Thu, 02 Mar 2023 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/wrong-route53-domain-contact-details/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-pixabay-4169741.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-symptoms&#34;&gt;The symptoms&lt;/h2&gt;&#xA;&lt;p&gt;I recently received and email from Amazon Route53 asking me to verify the contact details for one of our registered DNS domains (replaced here with example.org). And sure enough, I saw wrong Route53 domain contact details in the email:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Subject: Confirm that contact information for example.org is correct.&#xA;Message body:&#xA;&#xA;Dear AWS customer,&#xA;&#xA;This message is a reminder to help you keep the contact information for your domain registration up to date. WHOIS records include the following information:&#xA;&#xA;  Domains:&#xA;  example.org&#xA;&#xA;  Registrant:&#xA;  Name: On behalf of example.org owner&#xA;  Organization Name: Identity Protection Service&#xA;  Address: PO Box 786&#xA;  City: Hayes&#xA;  State/Province: Middlesex&#xA;  Country: GB&#xA;  Postal Code: UB3 9TR&#xA;&#xA;  Administrative Contact:&#xA;  Name: On behalf of example.org owner&#xA;  Organization Name: Identity Protection Service&#xA;  Address: PO Box 786&#xA;  City: Hayes&#xA;  State/Province: Middlesex&#xA;  Country: GB&#xA;  Postal Code: UB3 9TR&#xA;  Phone: +44.1483307527&#xA;  Fax: +44.1483304031&#xA;  Email: e793d865-97ab-42ab-805c-50cc36fd9202@identity-protect.org&#xA;&#xA;  Technical Contact:&#xA;  Name: On behalf of example.org owner&#xA;  Organization Name: Identity Protection Service&#xA;  Address: PO Box 786&#xA;  City: Hayes&#xA;  State/Province: Middlesex&#xA;  Country: GB&#xA;  Postal Code: UB3 9TR&#xA;  Phone: +44.1483307527&#xA;  Fax: +44.1483304031&#xA;  Email: e793d865-97ab-42ab-805c-50cc36fd9202@identity-protect.org&#xA;&#xA;The contact information provided above is what is shown in the public WHOIS. If your domain is privacy protected, the information will be different than the contact data you have submitted. To verify your contact information, please open the Amazon Route 53 console to view your current contact information and make any necessary corrections:&#xA;https://console.aws.amazon.com/route53/home#DomainDetail:example.org&#xA;&#xA;If your information is accurate, you do not need to take any action.&#xA;&#xA;Important&#xA;Under the terms of your registration agreement, providing false contact data can be grounds for the cancellation of your domain name registration.&#xA;&#xA;Regards,&#xA;Amazon Route 53 &lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;These wrong Route53 domain contact details originate from &lt;a href=&#34;https://en.wikipedia.org/wiki/WHOIS&#34;&gt;whois data&lt;/a&gt; for the registered domain. One might reasonably expect the whois data to match the contact details for AWS Route53 domain. However, in this case the Route 53 contact details were correct, but whois data &lt;em&gt;seemed&lt;/em&gt; to be out of sync with those.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Keycloak realm SMTP settings with Ansible</title>
				<link>https://www.puppeteers.net/blog/keycloak-realm-smtp-settings-with-ansible/</link>
				<pubDate>Mon, 27 Feb 2023 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/keycloak-realm-smtp-settings-with-ansible/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-brett-jordan-5426403.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;Ansible has reasonably good support for managing various aspects of Keycloak. You can use the &lt;a href=&#34;https://docs.ansible.com/ansible/latest/collections/community/general/keycloak_realm_module.html&#34;&gt;community.general.keycloak_realm&lt;/a&gt; module to handle realm management, including Keycloak realm SMTP server settings. However, in the true Ansible fashion the documentation looks good, but does not help you much. In fact, documentation only mentions that the &lt;em&gt;smtp_server&lt;/em&gt; parameter is a dictionary, but that&amp;rsquo;s it. To be honest, that&amp;rsquo;s about as helpful as the description of the &lt;a href=&#34;https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_realmrepresentation&#34;&gt;RealmRepresentation&lt;/a&gt; for Keycloak. This article aims to fill this documentation void. I also want to share some general advise for similar cases that pop up when you &lt;a href=&#34;https://www.puppeteers.net/blog/managing-keycloak-configuration-programmatically/&#34;&gt;manage Keycloak programmatically&lt;/a&gt;.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Convert a hash into JSON in Puppet</title>
				<link>https://www.puppeteers.net/blog/convert-a-hash-into-json-in-puppet/</link>
				<pubDate>Thu, 22 Dec 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/convert-a-hash-into-json-in-puppet/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-jess-loiterton-9259842.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;This article shows you how to convert a hash into JSON in Puppet using a simple &lt;a href=&#34;https://www.puppet.com/docs/puppet/7/lang_template_erb.html&#34;&gt;ERB template&lt;/a&gt; that gets its data from &lt;a href=&#34;https://www.puppet.com/docs/puppet/7/hiera_intro.html&#34;&gt;Hiera&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Suppose you have this data in Hiera:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;myhash:&#xA;  mykey:&#xA;    foo: one&#xA;    bar: two&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;Converting a hash into a JSON file on the target node is surprisingly easy. First look up the data:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$myhash = lookup(&amp;#39;myhash&amp;#39;, Hash)&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;Then create a simple ERB template (here: &lt;em&gt;mymodule/templates/myhash.erb&lt;/em&gt;)&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform downloads the deprecated azure provider</title>
				<link>https://www.puppeteers.net/blog/terraform-downloads-the-deprecated-azure-provider/</link>
				<pubDate>Tue, 20 Dec 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-downloads-the-deprecated-azure-provider/</guid>
				<description>&lt;p&gt;You are 100% sure that all your Terraform resources are using &lt;a href=&#34;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs&#34;&gt;terraform-provider-azurerm&lt;/a&gt;, yet Terraform attempts to download the deprecated &amp;ldquo;azure&amp;rdquo; provider:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ terraform init&#xA;Initializing modules...&#xA;&#xA;Initializing the backend...&#xA;&#xA;Initializing provider plugins...&#xA;- Finding hashicorp/local versions matching &amp;#34;2.2.3&amp;#34;...&#xA;- Finding latest version of hashicorp/azure...&#xA;- Finding hashicorp/azurerm versions matching &amp;#34;3.17.0&amp;#34;...&#xA;- Installing hashicorp/local v2.2.3...&#xA;- Installed hashicorp/local v2.2.3 (signed by HashiCorp)&#xA;- Installing hashicorp/azurerm v3.17.0...&#xA;- Installed hashicorp/azurerm v3.17.0 (signed by HashiCorp)&#xA;╷&#xA;│ Error: Failed to query available provider packages&#xA;│ &#xA;│ Could not retrieve the list of available versions for provider hashicorp/azure: provider registry registry.terraform.io does not have a provider named&#xA;│ registry.terraform.io/hashicorp/azure&#xA;│ &#xA;│ Did you intend to use terraform-providers/azure? If so, you must specify that source address in each module which requires that provider. To see which&#xA;│ modules are currently depending on hashicorp/azure, run the following command:&#xA;│     terraform providers&#xA;╵&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;You grep the state file and find no references to the &amp;ldquo;azure&amp;rdquo; provider. You assume that the cause is some nested module that depends on it, but no, that&amp;rsquo;s not it. You run &amp;ldquo;terraform providers&amp;rdquo; and see that indeed, the &amp;ldquo;azure&amp;rdquo; provider is required:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Writing Ansible modules: when and why?</title>
				<link>https://www.puppeteers.net/blog/writing-ansible-modules-when-and-why/</link>
				<pubDate>Wed, 23 Nov 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/writing-ansible-modules-when-and-why/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-harrison-haines-5247937.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-are-ansible-modules&#34;&gt;What are Ansible modules?&lt;/h2&gt;&#xA;&lt;p&gt;Ansible modules provide the &lt;a href=&#34;https://www.puppeteers.net/learn/understanding-infrastructure-as-code-iac/&#34;&gt;infrastructure as code&lt;/a&gt; building blocks for your Ansible roles, plays and playbooks. Modules manage things such as packages, files and services. The scope of a module is typically quite narrow: it does one thing but attempts to do it well. Writing custom Ansible modules is not particularly difficult. The first step is to solve the problem with raw Python, then you can convert that Python code to an Ansible module&lt;/p&gt;</description>
			</item>
			<item>
				<title>Version locking Ansible Collections</title>
				<link>https://www.puppeteers.net/blog/version-locking-ansible-collections/</link>
				<pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/version-locking-ansible-collections/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/pexels-pixabay-4145791.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-are-ansible-collections&#34;&gt;What are Ansible Collections?&lt;/h2&gt;&#xA;&lt;p&gt;Ansible is an &lt;a href=&#34;https://www.puppeteers.net/learn/understanding-infrastructure-as-code-iac/&#34;&gt;infrastructure as code&lt;/a&gt; tool used for configuration management, network device management, orchestration and other tasks. &lt;a href=&#34;https://docs.ansible.com/ansible/latest/user_guide/collections_using.html&#34;&gt;Ansible Collections&lt;/a&gt; are a way to distribute Ansible content such as roles, playbooks and modules. They &lt;a href=&#34;https://docs.ansible.com/ansible/latest/cli/ansible-galaxy.html&#34;&gt;can be downloaded&lt;/a&gt; from Ansible Galaxy, Git repositories or local directories. Basically collections are a more modern packaging format compared to standalone roles which they &lt;a href=&#34;https://docs.ansible.com/ansible/latest/dev_guide/migrating_roles.html&#34;&gt;are in the process of replacing&lt;/a&gt;. Version-locking Ansible Collections allows you to run the exact same Ansible code every time.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Importing DNS records from Cloudflare to Terraform</title>
				<link>https://www.puppeteers.net/blog/importing-dns-records-from-cloudflare-to-terraform/</link>
				<pubDate>Fri, 30 Sep 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/importing-dns-records-from-cloudflare-to-terraform/</guid>
				<description>&lt;p&gt;&lt;img src=&#34;https://puppeteers-public.hel1.your-objectstorage.com/bpmn-cloudflare-dns2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;Computers were supposed to relieve us humans from boring and repetitive jobs. Here we turn this upside down and do the boring and repetitive job of a computer by importing Cloudflare DNS records to Terraform ourselves. Not fun, but someone’s gotta do it sometimes. If you’re reading this, that someone is probably you. Condolences. My hope is that someone lazy enough gets bored enough to write a program to do this. I didn’t. Yet. &lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform, AWS Cloudfront and CNAMEAlreadyExists error</title>
				<link>https://www.puppeteers.net/blog/terraform-aws-cloudfront-and-cnamealreadyexists-error/</link>
				<pubDate>Sat, 17 Sep 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-aws-cloudfront-and-cnamealreadyexists-error/</guid>
				<description>&lt;p&gt;When you create a distribution, AWS creates several DNS A records with the same name (e.g. &lt;em&gt;d25gma2ea3ckma.cloudfront.net&lt;/em&gt;) which point to IPs the distribution is using. Then, typically, you would define CNAME(s) pointing to that cloudfront.net address in your own DNS. Each Cloudfront distribution has a list of aliases, similar to Subject Alternative Names (&amp;ldquo;SAN&amp;rdquo;) in SSL certificates. The aliases should match the CNAME(s) you&amp;rsquo;ve set in DNS.&lt;/p&gt;&#xA;&lt;p&gt;Now, this is all good until you need to migrate those Cloudfront distributions to, say, a new AWS account. If you attempt to create a new distribution (here with Terraform) with the same alias you will get this error:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform, Azure and MissingSubscriptionRegistration</title>
				<link>https://www.puppeteers.net/blog/terraform-azure-and-missingsubscriptionregistration/</link>
				<pubDate>Sat, 20 Aug 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-azure-and-missingsubscriptionregistration/</guid>
				<description>&lt;p&gt;When deploying with Terraform to Azure you may sometimes encounter errors such as this:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;╷                                                                                                                                                             &#xA;│ Error: creating Automation Account (Subscription: &amp;#34;83fa140a-2bea-175a-395c-914cae2902ce&amp;#34;                                                                    &#xA;│ Resource Group Name: &amp;#34;production&amp;#34;                                             &#xA;│ Automation Account Name: &amp;#34;backup-automation&amp;#34;): automationaccount.AutomationAccountClient#CreateOrUpdate: Failure responding to request: StatusCode=409 -- Or&#xA;iginal Error: autorest/azure: Service returned an error. Status=409 Code=&amp;#34;MissingSubscriptionRegistration&amp;#34; Message=&amp;#34;The subscription is not registered to use &#xA;namespace &amp;#39;Microsoft.Automation&amp;#39;. See https://aka.ms/rps-not-found for how to register subscriptions.&amp;#34; Details=[{&amp;#34;code&amp;#34;:&amp;#34;MissingSubscriptionRegistration&amp;#34;,&amp;#34;mes&#xA;sage&amp;#34;:&amp;#34;The subscription is not registered to use namespace &amp;#39;Microsoft.Automation&amp;#39;. See https://aka.ms/rps-not-found for how to register subscriptions.&amp;#34;,&amp;#34;targe&#xA;t&amp;#34;:&amp;#34;Microsoft.Automation&amp;#34;}]&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;The problem is that in Azure you may need to register the &lt;em&gt;provider&lt;/em&gt; for the service you intend to manage with Terraform. If you add resources from Azure Portal this registration part is handled automation. In the above case the Azure Automation provider was missing.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Finding which MySQL/MariaDB tables are modified by a GUI</title>
				<link>https://www.puppeteers.net/blog/finding-which-mysql-mariadb-tables-are-modified-by-a-gui/</link>
				<pubDate>Fri, 19 Aug 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/finding-which-mysql-mariadb-tables-are-modified-by-a-gui/</guid>
				<description>&lt;p&gt;I was working with &lt;a href=&#34;https://www.keycloak.org/&#34;&gt;Keycloak&lt;/a&gt; realm private/public key automation and it was not immediately obvious where Keycloak stores the keys. Figuring it out was actually easy, and this method applies to any web application that uses MySQL/MariaDB, not just Keycloak.&lt;/p&gt;&#xA;&lt;p&gt;Anyhow, on Ubuntu, you&amp;rsquo;d navigate to &lt;em&gt;/var/lib/mysql/name-of-database&lt;/em&gt;. For example:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd /var/lib/mysql/keycloak&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;Make sure that no changes have been made recently (within one minute):&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ find -mmin -1&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;Then go to the web application and change whatever you&amp;rsquo;re trying to locate from the database. Here I changed the Keycloak RSA key priority. Then run the find command again:&lt;/p&gt;</description>
			</item>
			<item>
				<title>AWS: creating AMIs from EBS snapshots shared with you</title>
				<link>https://www.puppeteers.net/blog/aws-creating-amis-from-ebs-snapshots-shared-with-you/</link>
				<pubDate>Mon, 15 Aug 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/aws-creating-amis-from-ebs-snapshots-shared-with-you/</guid>
				<description>&lt;p&gt;In AWS &lt;a href=&#34;https://aws.amazon.com/ebs/&#34;&gt;EBS&lt;/a&gt; (&amp;ldquo;Elastic Block Storage&amp;rdquo;) is the underlying technology that (virtual) hard disks of your instances (virtual machines) use. You can take snapshots of those virtual hard disks and use those snapshots to, for example:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Debugging issues with unbootable virtual machines: attach and then mount the snapshot on another virtual machine and investigate what is wrong.&lt;/li&gt;&#xA;&lt;li&gt;Creating AMI images from which you can launch new instances&lt;/li&gt;&#xA;&lt;li&gt;Sharing EBS volumes (between regions and AWS accounts), e.g. for purposes of migrating VMs&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Here we&amp;rsquo;ll focus on the last use-case: being able to create copies of virtual machines on another AWS account. The reason why I even bothered writing this blog post is that most of the articles on the Internet do not cover this use-case: they assume you&amp;rsquo;re working within one AWS account and/or one region. The use-case covered here requires a few extra steps:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Joining nodes to FreeIPA using a non-admin user</title>
				<link>https://www.puppeteers.net/blog/joining-nodes-to-freeipa-using-a-non-admin-user/</link>
				<pubDate>Tue, 09 Aug 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/joining-nodes-to-freeipa-using-a-non-admin-user/</guid>
				<description>&lt;p&gt;Typically Linux nodes are joined to &lt;a href=&#34;https://www.freeipa.org/page/Main_Page&#34;&gt;FreeIPA&lt;/a&gt; using admin credentials. While this works, it exposes fully privileged credentials unnecessarily, for example when used within a configuration management system (see for example &lt;a href=&#34;https://github.com/Puppet-Finland/puppet-ipa/&#34;&gt;puppet-ipa&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;Fortunately joining nodes to FreeIPA is possible with more limited privileges. The first step is to create a new FreeIPA role, e.g. &amp;ldquo;Enrollment administrator&amp;rdquo; with three privileges:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;DNS Administrators&lt;/li&gt;&#xA;&lt;li&gt;Host Administrators&lt;/li&gt;&#xA;&lt;li&gt;Host Enrollment&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Then you create a new user, e.g. &amp;ldquo;enrollment&amp;rdquo;, and join it to the &amp;ldquo;Enrollment administrator&amp;rdquo; role. After that you should be able to join nodes using that &amp;ldquo;enrollment&amp;rdquo; user.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Using .sync.yml in Puppet Development Kit (PDK)</title>
				<link>https://www.puppeteers.net/blog/using-sync-yml-in-puppet-development-kit-pdk/</link>
				<pubDate>Tue, 09 Aug 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/using-sync-yml-in-puppet-development-kit-pdk/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://puppet.com/try-puppet/puppet-development-kit/&#34;&gt;Puppet Development Kit&lt;/a&gt; is probably the best thing since sliced bread if you work a lot with Puppet. It makes adding basic validation and unit tests trivial with help from &lt;a href=&#34;https://rspec-puppet.com/&#34;&gt;rspec-puppet&lt;/a&gt;. It also makes it very easy to build module packages for the &lt;a href=&#34;https://forge.puppet.com/&#34;&gt;Puppet Forge&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;That said, there is a minor annoyance with it: whenever you run &amp;ldquo;pdk update&amp;rdquo; to update PDK to the latest version, all your local changes to files such as .gitignore get destroyed, because PDK creates those files from &lt;a href=&#34;https://github.com/puppetlabs/pdk-templates/&#34;&gt;PDK templates&lt;/a&gt; and any local changes are mercilessly wiped. Fortunately there is a way to &lt;em&gt;sync&lt;/em&gt; your local changes to the files generated from the templates with &lt;em&gt;.sync.yml&lt;/em&gt;. While .sync.yml is is &lt;a href=&#34;https://github.com/puppetlabs/pdk-templates/blob/main/README.md&#34;&gt;pretty well documented&lt;/a&gt;, documentation is lots and lots of words with very few examples. So, here&amp;rsquo;s a fairly trivial .sync.yml example that ensures local changes to &lt;em&gt;.pdkignore&lt;/em&gt; and &lt;em&gt;.gitignore&lt;/em&gt; persist across PDK updates:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Use content of a file as a variable with Puppet</title>
				<link>https://www.puppeteers.net/blog/load-content-of-a-file-on-an-agent-node-to-a-variable-with-puppet/</link>
				<pubDate>Sat, 16 Jul 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/load-content-of-a-file-on-an-agent-node-to-a-variable-with-puppet/</guid>
				<description>&lt;p&gt;Every now and then a need to use the content of a file as a variable &lt;strong&gt;on an agent node&lt;/strong&gt; arises. Here&amp;rsquo;s one way to do it with the help of a &lt;a href=&#34;https://puppet.com/docs/puppet/7/custom_facts.html&#34;&gt;custom fact&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;First create a custom fact &lt;strong&gt;on the puppet serve&lt;/strong&gt;r:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Facter.add(:my_file_content) do&#xA;  setcode do&#xA;    path = &amp;#34;/path/to/my_file&amp;#34;&#xA;    File.read(path) if File.exist?(path)&#xA;   end&#xA;end&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;You can &lt;strong&gt;confine&lt;/strong&gt; this to restrict it to be available only on selected nodes, for example:&lt;/p&gt;</description>
			</item>
			<item>
				<title>How to add OpenID to WordPress with Keycloak</title>
				<link>https://www.puppeteers.net/blog/how-to-add-openid-to-wordpress-with-keycloak/</link>
				<pubDate>Thu, 14 Jul 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/how-to-add-openid-to-wordpress-with-keycloak/</guid>
				<description>&lt;p&gt;Adding &lt;a href=&#34;https://openid.net/what-is-openid/&#34;&gt;OpenID&lt;/a&gt; to Wordpress allows for existing users on a domain to connect without having to manage another account. In this setup, we will be using Keycloak to provide the existing accounts which the OpenID plugin will use.&lt;/p&gt;&#xA;&lt;p&gt;There are a few plugins for Wordpress that allow this functionality, but we will be using &lt;a href=&#34;https://wordpress.org/plugins/daggerhart-openid-connect-generic/&#34;&gt;OpenID-Connect-Generic&lt;/a&gt; by &lt;a href=&#34;https://www.daggerhartlab.com/&#34;&gt;Daggerhart&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;keycloak-client-setup&#34;&gt;Keycloak Client Setup&lt;/h2&gt;&#xA;&lt;p&gt;First step is to create a client in Keycloak. In the Keycloak web UI, create an &lt;strong&gt;openid-connect&lt;/strong&gt; client by going to &lt;strong&gt;Clients&lt;/strong&gt; and click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;</description>
			</item>
			<item>
				<title>X11 Connection Rejected</title>
				<link>https://www.puppeteers.net/blog/x11-connection-rejected/</link>
				<pubDate>Thu, 28 Apr 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/x11-connection-rejected/</guid>
				<description>&lt;p&gt;When switching to root after the typical SSH with X11 forwarding enabled this error can appear:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ X11 connection rejected because of wrong authentication.&#xA;$ Error: Can&amp;#39;t open display: localhost:10.0&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;The workaround seems to include copying the &lt;strong&gt;MIT-MAGIC-COOKIE-1&lt;/strong&gt; from the user who ssh&amp;rsquo;d in to the root user using xauth. Here&amp;rsquo;s how:&lt;/p&gt;&#xA;&lt;p&gt;First verify the &lt;strong&gt;$DISPLAY&lt;/strong&gt; being used and list the &lt;strong&gt;MIT-MAGIC-COOKIE-1&lt;/strong&gt; used for it :&lt;/p&gt;</description>
			</item>
			<item>
				<title>Enabling system tray on Fedora 35 and 36</title>
				<link>https://www.puppeteers.net/blog/enabling-system-tray-on-fedora-35/</link>
				<pubDate>Tue, 12 Apr 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/enabling-system-tray-on-fedora-35/</guid>
				<description>&lt;p&gt;System tray is a &amp;ldquo;legacy&amp;rdquo; tray where various applications (e.g. Nextcloud, Pidgin and Signal) have an icon with which you can interact with the application without actually opening the main application window. I said &amp;ldquo;legacy&amp;rdquo;, because phasing it out was the plan in the Gnome 3 project, but it seems like we&amp;rsquo;re not getting rid of the tray any time soon, if ever.&lt;/p&gt;&#xA;&lt;p&gt;In any case the system tray is not enabled by default in recent Fedoras, and the proper way to enable it has changed several times in the recent past as Gnome has been upgraded. This outlines the most recent way to do it, and hopefully this one sticks for longer than a year or so.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform Enum data type</title>
				<link>https://www.puppeteers.net/blog/terraform-enum-data-type/</link>
				<pubDate>Tue, 12 Apr 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-enum-data-type/</guid>
				<description>&lt;p&gt;In Terraform you have access to basic data types like &lt;em&gt;bool&lt;/em&gt; or &lt;em&gt;string&lt;/em&gt;. Defining the data type is a good start for starting to improve the quality of your modules. However, you may want to validate that a certain string matches a list of pre-defined options, and if not, fail validation early.&lt;/p&gt;&#xA;&lt;p&gt;Terraform, &lt;a href=&#34;https://puppet.com/docs/puppet/7/lang_data_abstract.html#enum-data-type&#34;&gt;unlike Puppet&lt;/a&gt;, does not have a built-in Enum data type. However, you can emulate an Enum like this:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform remote-exec: wait until a webpage is reachable</title>
				<link>https://www.puppeteers.net/blog/terraform-remote-exec-wait-until-a-webpage-is-reachable/</link>
				<pubDate>Fri, 18 Feb 2022 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-remote-exec-wait-until-a-webpage-is-reachable/</guid>
				<description>&lt;p&gt;Terraform&amp;rsquo;s remote-exec provisioner fails immediately if any command in a script exists with a non-zero exit code. This makes building polling loops a bit more involved than it normally is. So, here is an example loop that checks if a URL can be reached:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;while true; do&#xA;    curl -k https://puppet:8140/puppet-ca/v1 &amp;amp;&amp;amp; break || sleep 3&#xA;done&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;When the URL is unreachable, the &amp;ldquo;||&amp;rdquo; will ensure that &amp;ldquo;sleep&amp;rdquo; gets run, which returns 0. If the URL is reachable, curl itself will return 0 and &amp;ldquo;&amp;amp;&amp;amp;&amp;rdquo; ensures that we break out of the loop.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Showing SQL statements with Keycloak</title>
				<link>https://www.puppeteers.net/blog/showing-sql-statements-with-keycloak/</link>
				<pubDate>Fri, 31 Dec 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/showing-sql-statements-with-keycloak/</guid>
				<description>&lt;p&gt;Sometimes you might find yourself wondering whether there is some paranormal activity going on with your keycloak and its database. To check if things are still in the realm of physical reality, and to restore your child&amp;rsquo;s faith in the programmer who never makes mistakes, it might be soothing to check what&amp;rsquo;s actually happening to the database. To do this, create a new property named &lt;strong&gt;showSql&lt;/strong&gt; and give it a value of &lt;strong&gt;true&lt;/strong&gt; in your connectionsJpa spi. This is located in standalone.xml, standalone-ha.xml, or in domain.xml (if you&amp;rsquo;ve already graduated from kindergarten).&lt;/p&gt;</description>
			</item>
			<item>
				<title>Multiple Bitbucket Cloud accounts with SSH authentication</title>
				<link>https://www.puppeteers.net/blog/multiple-bitbucket-cloud-accounts-with-ssh-authentication/</link>
				<pubDate>Tue, 07 Dec 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/multiple-bitbucket-cloud-accounts-with-ssh-authentication/</guid>
				<description>&lt;p&gt;In Bitbucket usernames are unique across whole of Bitbucket. Moreover, the same SSH key can only be configured for one user. If you registered your Bitbucket account using a corporate email and used your primary SSH key with it, you&amp;rsquo;re pretty much hosed if you then need to create another corporate Bitbucket account and wanted to use the same SSH key with it - that&amp;rsquo;s just not going to happen. The solution to this dilemma is to create another SSH keypair and somehow make SSH, Git and Bitbucket co-operate. As the correct solution was not immediately obvious it is documented here.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Managing OpenVPN-based Azure VPN Gateway certificates with easyrsa3</title>
				<link>https://www.puppeteers.net/blog/managing-openvpn-based-azure-vpn-gateway-certificates-with-easyrsa3/</link>
				<pubDate>Fri, 19 Nov 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/managing-openvpn-based-azure-vpn-gateway-certificates-with-easyrsa3/</guid>
				<description>&lt;p&gt;The Azure VPN Gateway supports the OpenVPN protocol (except the &amp;ldquo;Basic SKU&amp;rdquo;). Unlike, for example, the commercial Access Server, the VPN Gateway does not have a built-in certificate authority (CA) tool for managing client certificates. And client certificates are essentially a requirement if you need to support clients other than Windows and Mac, such as Linux, iOS or Android. The use of easy-rsa-old as a CA for Azure VPN Gateway is documented quite well in this &lt;a href=&#34;https://www.thomasmaurer.ch/2020/02/create-azure-p2s-vpn-with-azure-ad-authentication/&#34;&gt;blog post&lt;/a&gt;, but this post is about using the newer &lt;a href=&#34;https://github.com/OpenVPN/easy-rsa&#34;&gt;easyrsa3&lt;/a&gt; instead. Basic familiarity with easyrsa3 is assumed, so we just go through the steps you&amp;rsquo;d normally take.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform Azure resource provider registration fails</title>
				<link>https://www.puppeteers.net/blog/terraform-azure-resource-provider-registration-fails/</link>
				<pubDate>Tue, 26 Oct 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-azure-resource-provider-registration-fails/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://www.terraform.io/&#34;&gt;Terraform&lt;/a&gt; has good support for &lt;a href=&#34;https://azure.microsoft.com/en-us/&#34;&gt;Microsoft Azure&lt;/a&gt; through the &lt;a href=&#34;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs&#34;&gt;Terraform Azure provider&lt;/a&gt; and the &lt;a href=&#34;https://www.terraform.io/docs/language/settings/backends/azurerm.html&#34;&gt;AzureRM backend&lt;/a&gt;. However, you may hit a glitch when adding or importing resources if you lack permissions to register Azure resource providers:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;│ Error: Error ensuring Resource Providers are registered.&#xA;|&#xA;│ Terraform automatically attempts to register the Resource Providers it supports&#xA;| to ensure it&amp;#39;s able to provision resources.&#xA;│                                                                                                                                                                                   &#xA;│ If you don&amp;#39;t have permission to register Resource Providers you may wish to use&#xA;| the &amp;#34;skip_provider_registration&amp;#34; flag in the Provider block to disable this&#xA;| functionality.&#xA;│                                                                                                                                                                                   &#xA;│ Please note that if you opt out of Resource Provider Registration and Terraform&#xA;| tries to provision a resource from a Resource Provider which is unregistered,&#xA;| then the error may appear misleading - for example&#xA;|                                                                                                                                                                                   &#xA;│ &amp;gt; API version 2019-XX-XX was not found for Microsoft.Foo&#xA;|                                                                                                                                                                                   &#xA;│ Could indicate either that the Resource Provider &amp;#34;Microsoft.Foo&amp;#34; requires&#xA;| registration, but this could also indicate that this Azure Region doesn&amp;#39;t support | this API version.&#xA;|&#xA;│ More information on the &amp;#34;skip_provider_registration&amp;#34; flag can be found here:&#xA;|&#xA;│ https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#skip_provider_registration&#xA;│&#xA;│ Original Error: Cannnot register providers: Microsoft.DataLakeAnalytics, Microsoft.DBforPostgreSQL, Microsoft.AVS, Microsoft.Media, Microsoft.ApiManagement, Microsoft.Search, Mic&#xA;rosoft.Maintenance, Microsoft.ServiceBus, Microsoft.HealthcareApis, Microsoft.Relay, Microsoft.DesktopVirtualization, Microsoft.NotificationHubs, Microsoft.SecurityInsights, Micros&#xA;oft.DBforMariaDB, Microsoft.Kusto, Microsoft.DataLakeStore, Microsoft.Automation, Microsoft.CognitiveServices, Microsoft.ManagedServices, Microsoft.EventGrid, Microsoft.BotService,&#xA;&#xA;--- snip ---&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;At first glance this error look cryptic, but it is quite clear once you understand &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types&#34;&gt;what Azure resource providers are&lt;/a&gt; - I like to think of them as &lt;em&gt;&amp;ldquo;enabled Azure services&amp;rdquo;&lt;/em&gt; or &lt;em&gt;&amp;ldquo;enabled Azure features&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Recruiting for a small business - lessons learned, part 2: Tips for job seekers</title>
				<link>https://www.puppeteers.net/blog/recruiting-for-a-small-business-lessons-learned-part-2-tips-for-job-seekers/</link>
				<pubDate>Fri, 20 Aug 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/recruiting-for-a-small-business-lessons-learned-part-2-tips-for-job-seekers/</guid>
				<description>&lt;p&gt;I previously wrote about takeaways from our first recruitment process as a recruiter in January 2021. This time I’m looking back to the recruitment process in order to gather tips for the other half, job applicants and seekers. Part of the advice are also based on my own experiences as a job seeker and some even on common sense. A recruiter of a bigger company could formulate this article a bit differently but these aspects mattered to us, as a small business.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Making modals responsive in Oxygen Builder for Wordpress</title>
				<link>https://www.puppeteers.net/blog/making-modals-responsive-in-oxygen-builder-for-wordpress/</link>
				<pubDate>Wed, 14 Jul 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/making-modals-responsive-in-oxygen-builder-for-wordpress/</guid>
				<description>&lt;p&gt;I always thought that it would be impossible for me to make convincing looking websites. But I did learn it and enjoy it very much. But as soon as I’m ready with a new, beautiful web page on my screen, my joy is cut short by the fact that I still need to make it responsive. Learning website development is in fact a double battle: first learning the techniques to make nice things on a web page, and then learning another set of techniques on how to keep the design beautiful on smaller screens. In my experience, the latter battle is way bigger, and thus hope that sharing the lesson I learned will save someone from the agony I had to go through. I’ll focus on modals because making them responsive has been the toughest part for me.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Packer QEMU performance: speed things up with two-stage builds</title>
				<link>https://www.puppeteers.net/blog/two-stage-qemu-builds-with-packer/</link>
				<pubDate>Thu, 08 Jul 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/two-stage-qemu-builds-with-packer/</guid>
				<description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;In the &amp;ldquo;&lt;a href=&#34;https://www.puppeteers.net/blog/building-ubuntu-20-04-qemu-images-with-packer/&#34;&gt;Packer QEMU: Building Ubuntu 20.04/22.04/24.04 VM images&lt;/a&gt;&amp;rdquo; blog post we briefly mentioned ways to improve Packer QEMU performance. In this article we&amp;rsquo;ll go into the details on how this is implemented. The key in improving Packer QEMU performance is to split the build into two distinct stages:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Operating system installation stage&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Provisioning stage&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This setup cuts down development time considerably as repeating the provisioning stage becomes a lot faster.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Creating Puppet Bolt groups based on AWS tags</title>
				<link>https://www.puppeteers.net/blog/creating-puppet-bolt-groups-based-on-aws-tags/</link>
				<pubDate>Thu, 01 Jul 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/creating-puppet-bolt-groups-based-on-aws-tags/</guid>
				<description>&lt;p&gt;The &lt;a href=&#34;https://www.puppeteers.net/blog/using-tags-in-puppet-bolt-aws_inventory-target_mapping/&#34;&gt;Using tags in Puppet Bolt aws_inventory target_mapping&lt;/a&gt; showed how to use AWS &amp;ldquo;Name&amp;rdquo; tag as the target name for Puppet Bolt. Use of tags can be extended to creating Bolt target groups:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;groups:&#xA;  - name: webservers&#xA;    targets:&#xA;     - _plugin: aws_inventory&#xA;       region:&#xA;         _plugin: env_var&#xA;         var: AWS_DEFAULT_REGION&#xA;       filters:&#xA;         - name: tag:Type&#xA;           values: [webserver]&#xA;       target_mapping:&#xA;         name: tags.Name&#xA;         uri: public_ip_address&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;All you need to do is add a &amp;ldquo;filter&amp;rdquo; section with one filter. The &amp;ldquo;name&amp;rdquo; parameter tells Bolt that the filter is based on tags and what tag to look for. The &amp;ldquo;Type&amp;rdquo; that follows &amp;ldquo;tag&amp;rdquo; is the name of the tag you want to filter by. The &amp;ldquo;values&amp;rdquo; array lists acceptable values for that tag. Any nodes that don&amp;rsquo;t have the tag (here: &amp;ldquo;Type&amp;rdquo;) or where tag value is not acceptable (e.g. &amp;ldquo;db&amp;rdquo;) will not be shown in the group.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Using xfreerdp with Vagrant</title>
				<link>https://www.puppeteers.net/blog/using-xfreerdp-with-vagrant/</link>
				<pubDate>Tue, 15 Jun 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/using-xfreerdp-with-vagrant/</guid>
				<description>&lt;p&gt;Vagrant has built-in support for connecting to Windows VMs using the &lt;a href=&#34;https://www.vagrantup.com/docs/cli/rdp&#34;&gt;vagrant rdp command&lt;/a&gt;. It does basically does the same as &lt;a href=&#34;https://www.vagrantup.com/docs/cli/ssh&#34;&gt;vagrant ssh&lt;/a&gt; for Linux VMs. There are a few shortcomings, though: vagrant rdp fails to automatically accept the host key of the VM it is connecting to, at least when using it from Linux with xfreerdp. So, you first need to connect to the VM directly once with raw xfreerdp, then vagrant rdp will start working. Then, when you wipe out the VM, you need to repeat the process again. If you have a high resolution screen you will also notice that vagrant rdp will give you a tiny Windows desktop where you need a magnifying glass to read the text.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Installing individual components to Visual Studio 2019 from the command-line</title>
				<link>https://www.puppeteers.net/blog/installing-individual-components-to-visual-studio-2019-from-the-command-line/</link>
				<pubDate>Mon, 07 Jun 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/installing-individual-components-to-visual-studio-2019-from-the-command-line/</guid>
				<description>&lt;p&gt;I&amp;rsquo;ve been doing lots of Windows automation recently. My goal has been to be able to reproducibly create a Windows Server 2019-based &lt;a href=&#34;http://buildbot.net/&#34;&gt;Buildbot&lt;/a&gt; workers, first in a &lt;a href=&#34;https://www.vagrantup.com/&#34;&gt;Vagrant&lt;/a&gt; environment, then later in AWS EC2 using pre-built images created with &lt;a href=&#34;https://www.packer.io/&#34;&gt;Packer&lt;/a&gt;. This task requires installing Visual Studio 2019 build tools automatically and fetching the project&amp;rsquo;s dependencies with &lt;a href=&#34;https://vcpkg.io/en/index.html&#34;&gt;vcpkg&lt;/a&gt; automatically. It is possible to install Visual Studio 2019 with &lt;a href=&#34;https://community.chocolatey.org/packages/visualstudio2019buildtools&#34;&gt;Chocolatey&lt;/a&gt; quite easily, but what if you need to install additional components to Visual Studio without using the Visual Studio installer GUI? Looking around did not quickly reveal any de facto instructions on how to do it, so this article aims to fill that gap.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Using tags in Puppet Bolt aws_inventory target_mapping</title>
				<link>https://www.puppeteers.net/blog/using-tags-in-puppet-bolt-aws_inventory-target_mapping/</link>
				<pubDate>Sat, 05 Jun 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/using-tags-in-puppet-bolt-aws_inventory-target_mapping/</guid>
				<description>&lt;p&gt;We&amp;rsquo;re migrating away from Ansible to Puppet Bolt and the fact that Ansible updates broke the old &lt;a href=&#34;https://github.com/ansible/ansible/blob/stable-2.9/contrib/inventory/ec2.py&#34;&gt;ec2.py inventory script&lt;/a&gt; expedited that process. While that inventory script was quite rudimentary, it was able to automatically add human-readable names to the EC2 instance names. So, for example, you could target a node using &amp;ldquo;server_example_org&amp;rdquo; if the Name tag was set to &amp;ldquo;server.example.org&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Puppet Bolt&amp;rsquo;s &lt;a href=&#34;https://github.com/puppetlabs/puppetlabs-aws_inventory&#34;&gt;aws_inventory&lt;/a&gt; &lt;em&gt;seemed&lt;/em&gt; to lack that feature, but after taking a good look at its code this was just a case of missing documentation, which I fixed in &lt;a href=&#34;https://github.com/puppetlabs/puppetlabs-aws_inventory/pull/19&#34;&gt;this PR&lt;/a&gt;. Here&amp;rsquo;s an example of &lt;em&gt;inventory.yaml&lt;/em&gt; for Bolt that maps the value of the &amp;ldquo;Name&amp;rdquo; tag to the name of the Bolt target:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Delegating external parties access to AWS</title>
				<link>https://www.puppeteers.net/blog/delegating-external-parties-access-to-aws/</link>
				<pubDate>Fri, 14 May 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/delegating-external-parties-access-to-aws/</guid>
				<description>&lt;p&gt;This blog post shows how to grant access to an AWS account for some external party. For simplicity we will call this external party a &amp;ldquo;contractor&amp;rdquo;. If you want to do the same for Microsoft Azure look at &lt;a href=&#34;https://www.puppeteers.net/blog/delegating-external-parties-access-to-azure/&#34;&gt;this blog post&lt;/a&gt; instead.&lt;/p&gt;&#xA;&lt;p&gt;This method requires the contractor to have its own AWS account, but it has several benefits:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;No need to create AWS user accounts for every single person working for the contractor&lt;/li&gt;&#xA;&lt;li&gt;You can force the contractor&amp;rsquo;s personnel to use two-factor authentication (2FA)&lt;/li&gt;&#xA;&lt;li&gt;You can force the contractor to use an additional password (shared secret)&lt;/li&gt;&#xA;&lt;li&gt;You can disable contractor&amp;rsquo;s access completely by just removing the IAM role&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This approach works equally well for AWS Console access, AWS CLI and tools like Terraform. Some tools may not like the extra security measures required here or be able to assume a role out of the box.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Delegating external parties access to Azure</title>
				<link>https://www.puppeteers.net/blog/delegating-external-parties-access-to-azure/</link>
				<pubDate>Fri, 14 May 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/delegating-external-parties-access-to-azure/</guid>
				<description>&lt;p&gt;In &lt;a href=&#34;https://azure.microsoft.com/&#34;&gt;Microsoft Azure&lt;/a&gt; you can grant an external user (&amp;ldquo;contractor&amp;rdquo;) guest access to your Azure subscription. The approach is quite different from what &lt;a href=&#34;https://aws.amazon.com&#34;&gt;AWS&lt;/a&gt; uses (see &lt;a href=&#34;https://www.puppeteers.net/blog/delegating-external-parties-access-to-aws/&#34;&gt;Delegating external parties access to AWS&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;Here we grant access to &lt;em&gt;john.doe@contoso.com&lt;/em&gt; using the Azure Portal. Preferably &lt;em&gt;john.doe@contoso.com&lt;/em&gt; would already exist as a Microsoft account.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Login to your Azure Portal&lt;/li&gt;&#xA;&lt;li&gt;Type &amp;ldquo;Users&amp;rdquo; in the search box&lt;/li&gt;&#xA;&lt;li&gt;Click on the &amp;ldquo;Users&amp;rdquo; service&lt;/li&gt;&#xA;&lt;li&gt;Click on &amp;ldquo;New guest user&amp;rdquo;&lt;/li&gt;&#xA;&lt;li&gt;Select &amp;ldquo;Invite User&amp;rdquo;&lt;/li&gt;&#xA;&lt;li&gt;Fill in the fields. The email address should be the one given by the external user.&lt;/li&gt;&#xA;&lt;li&gt;Click on &amp;ldquo;Invite&amp;rdquo; at the bottom&lt;/li&gt;&#xA;&lt;li&gt;The external user will receive an invite to your Azure in the email address you defined above.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Now the user has been created. However, it is lacking permissions to do anything useful. We will fix that in the next steps by assigning it a role:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Gitlab SAML to Keycloak Setup</title>
				<link>https://www.puppeteers.net/blog/gitlab-saml-with-keycloak/</link>
				<pubDate>Wed, 12 May 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/gitlab-saml-with-keycloak/</guid>
				<description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;Integrating Keycloak with Gitlab SAML makes it easier to manage users in an organization from a central point. You could manage separate accounts on Gitlab, but for us it makes sense to utilize Keycloak as we are already using it for other applications.&lt;/p&gt;&#xA;&lt;h2 id=&#34;setting-up-keycloak&#34;&gt;Setting up Keycloak&lt;/h2&gt;&#xA;&lt;p&gt;The first step is to create a Gitlab SAML client in the Keycloak dashboard. With the correct realm selected, navigate to the Clients configuration page and click the create button to add a new client.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Cutting down Azure storage costs for a Marketplace seller</title>
				<link>https://www.puppeteers.net/blog/cutting-down-azure-storage-costs-for-a-marketplace-seller/</link>
				<pubDate>Mon, 19 Apr 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/cutting-down-azure-storage-costs-for-a-marketplace-seller/</guid>
				<description>&lt;p&gt;A few days ago I looked at our Azure costs and they were around $8. That&amp;rsquo;s a ridiculously small amount, except for the fact that we don&amp;rsquo;t really &lt;em&gt;use&lt;/em&gt; Azure for anything. So I started investigating.&lt;/p&gt;&#xA;&lt;p&gt;After having published the Configuration Manager (Puppet master installer image) and LibreNMS in Azure Marketplace I knew that we had a bunch of BLOBs lying around. I knew how to find these blobs using the Storage Explorer. So I tested wiping them all out, as I knew Azure does not delete them immediately, but only after one day. Then I attempted to launch Configuration Manager and LibreNMS from the Azure Marketplace GUI. My thought was that those blobs had already been copied elsewhere (for global distribution) and would not be used by the Marketplace anymore, but it &lt;em&gt;looked like&lt;/em&gt; I was wrong. At least I got odd behavior when trying to launch those images and reverting the blob deletion fixed things.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Making per-link DNS servers added by Wireguard to systemd-resolved survive NetworkManager connection changes</title>
				<link>https://www.puppeteers.net/blog/making-per-link-dns-servers-added-by-wireguard-to-systemd-resolved-survive-networkmanager-connection-changes/</link>
				<pubDate>Tue, 13 Apr 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/making-per-link-dns-servers-added-by-wireguard-to-systemd-resolved-survive-networkmanager-connection-changes/</guid>
				<description>&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; the problem described in this article seems to be resolved in Fedora 36 (NetworkManager 1.38.0-2.fc36). It is still present in Fedora 35 (NetworkManager 1.32.12-2.fc35).&lt;/p&gt;&#xA;&lt;p&gt;The title of this blog post may seem familiar to some, as &lt;a href=&#34;https://www.puppeteers.net/blog/making-per-link-dns-servers-pushed-from-openvpn-to-systemd-resolved-survive-networkmanager-connection-changes/&#34;&gt;I solved this problem&lt;/a&gt; for OpenVPN earlier. I won&amp;rsquo;t repeat myself here, so please check the &lt;a href=&#34;https://www.puppeteers.net/blog/making-per-link-dns-servers-pushed-from-openvpn-to-systemd-resolved-survive-networkmanager-connection-changes/&#34;&gt;OpenVPN article&lt;/a&gt; to understand why this is happening.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.wireguard.com/&#34;&gt;Wireguard&lt;/a&gt; servers - unlike &lt;a href=&#34;https://openvpn.net/community/&#34;&gt;OpenVPN&lt;/a&gt; servers - are not able to push any settings such as DNS servers to their clients. However, Wireguard client itself is able to run PostUp commands to configure per-link DNS. The same approach can be used with OpenVPN &amp;ldquo;up&amp;rdquo; scripts as well, if you prefer having full control at the client-side. Here&amp;rsquo;s a Wireguard example:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Puppet Bolt: taking action based on exit codes</title>
				<link>https://www.puppeteers.net/blog/puppet-bolt-taking-action-based-on-exit-codes/</link>
				<pubDate>Tue, 13 Apr 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/puppet-bolt-taking-action-based-on-exit-codes/</guid>
				<description>&lt;p&gt;Puppet resources like Package, File or Service are idempotent. In other words, you define the state of the resource and Puppet enforces it. The resources can be used in Bolt plans as well, which frees you from having to do state checks yourself. However, if the thing you&amp;rsquo;re managing is not a Puppet resource you have to check its state manually and use that information to drive login in your plan. Here&amp;rsquo;s a crude but functional PoC for checking if a node&amp;rsquo;s certificate is present on the Puppetmaster and acting accordingly:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Passing Vagrant parameters to Puppet Litmus</title>
				<link>https://www.puppeteers.net/blog/passing-vagrant-parameters-to-puppet-litmus/</link>
				<pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/passing-vagrant-parameters-to-puppet-litmus/</guid>
				<description>&lt;p&gt;Puppet Litmus allows creating SUTs (&amp;ldquo;System Under Test&amp;rdquo;) with Vagrant using Virtualbox or Hyper-V as the virtualization backend. However, the default virtual machine settings like 1024MB of memory are only suitable for very simple modules and applications. For example, Litmus testing our &lt;a href=&#34;https://github.com/Puppet-Finland/puppet-puppetmaster&#34;&gt;puppetmaster-installer&lt;/a&gt; will fail with the default settings.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://puppetlabs.github.io/litmus/Litmus-core-commands.html&#34;&gt;Litmus documentation&lt;/a&gt; tells us that you can define parameters in provision.yaml which get then converted to environment variables. What it does not tell what to use these environment variables for. Looking at the Vagrant code in Litmus you can see that those environment variables can be used to drive the Virtualbox settings, among other things:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Making vagrant-hostmanager UAC prompts go away on Windows</title>
				<link>https://www.puppeteers.net/blog/making-vagrant-hostmanager-uac-prompts-go-away-on-windows/</link>
				<pubDate>Wed, 03 Mar 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/making-vagrant-hostmanager-uac-prompts-go-away-on-windows/</guid>
				<description>&lt;p&gt;The &lt;a href=&#34;https://github.com/devopsgroup-io/vagrant-hostmanager&#34;&gt;vagrant-hostmanager&lt;/a&gt; plugin is a really nice plugin which helps Vagrant VMs communicate with each other using names instead of IP addresses. Essentially it manages hosts entries for the VMs, acting like a poor-man&amp;rsquo;s DNS. Now, on *NIX operating systems you typically use sudo, which caches your authorization. So, when hostmanager starts, you type your password once and then it is able to add all the host entries it needs to &lt;em&gt;/etc/hosts&lt;/em&gt;. This is not so on Windows: you will get one UAC prompt per modification to &lt;em&gt;c:\windows\system32\drivers\etc\hosts&lt;/em&gt;. This will eventually drive you crazy. To fix this you need to permit your Windows user account to modify the hosts-file without privilege elevation. To pull this off, you will need to modify that file&amp;rsquo;s access control lists. The process is quite convoluted imho, but here it goes:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Fixing vagrant-vbguest for the centos/7 base box</title>
				<link>https://www.puppeteers.net/blog/fixing-vagrant-vbguest-for-the-centos-7-base-box/</link>
				<pubDate>Thu, 25 Feb 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/fixing-vagrant-vbguest-for-the-centos-7-base-box/</guid>
				<description>&lt;p&gt;We&amp;rsquo;re using the centos/7 base box in Vagrant a lot and often hit this problem when bringing up new VMs with Vagrant:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;umount: /mnt: not mounted&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;This error comes from vagrant-vbguest plugin which tried to install the Virtualbox Guest Additions but failed. The error message is misleading, because the real issue is related to upstream (CentOS) yum repositories. As explained in this &lt;a href=&#34;https://github.com/hashicorp/vagrant/issues/12095&#34;&gt;Vagrant bug report&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;&amp;ldquo;Basically the CentOS that you&amp;rsquo;re installing is containing an old kernel version which has been removed from CentOS repositories. Vagrant is trying to install Guest Additions, which has as a dependency the kernel-devel package. kernel-devel package must be the same version as the kernel package that you have it installed in your CentOS box, version which is removed from the CentOS repos.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description>
			</item>
			<item>
				<title>Creating PXE bootable Virtualbox VMs</title>
				<link>https://www.puppeteers.net/blog/creating-pxe-bootable-virtualbox-vms/</link>
				<pubDate>Wed, 24 Feb 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/creating-pxe-bootable-virtualbox-vms/</guid>
				<description>&lt;p&gt;We use &lt;a href=&#34;https://www.vagrantup.com/&#34;&gt;Vagrant&lt;/a&gt; and &lt;a href=&#34;https://www.virtualbox.org/&#34;&gt;Virtualbox&lt;/a&gt; heavily when developing Puppet code. We also do provisioning of baremetal servers with &lt;a href=&#34;https://theforeman.org/&#34;&gt;Foreman&lt;/a&gt;. While Vagrant is a really powerful tool, it is not designed for creating VMs that boot from the network (&lt;a href=&#34;https://en.wikipedia.org/wiki/Preboot_Execution_Environment&#34;&gt;PXE boot&lt;/a&gt;). Fortunately Virtualbox itself provides a rich command-line with which you can create and configure virtual machines and configure them to boot from the network. It is assumed below that there is a PXE boot server (e.g. Foreman) created into a hostonly network with Vagrant. Here&amp;rsquo;s an example script that create a VM with vboxmanage and adds it the same hostonly network interface (vboxnet15) as the PXE boot server:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform resources with dynamic provider values</title>
				<link>https://www.puppeteers.net/blog/terraform-resources-with-dynamic-provider-values/</link>
				<pubDate>Thu, 11 Feb 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-resources-with-dynamic-provider-values/</guid>
				<description>&lt;p&gt;Sometimes you&amp;rsquo;d like to pass a dynamic value to a Terraform resource&amp;rsquo;s provider parameter. This can be done, but some background first.&lt;/p&gt;&#xA;&lt;p&gt;Terraform allows you to &lt;a href=&#34;https://www.terraform.io/docs/language/meta-arguments/resource-provider.html&#34;&gt;define multiple providers of the same type&lt;/a&gt; using provider &lt;em&gt;aliases&lt;/em&gt;. This is useful when you&amp;rsquo;re working with a provider that is tied to a region, the AWS provider being a prime example. The moment you want to create resources in multiple regions you will have to learn provider aliases.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Getting the last inactive vboxnet interface with Ruby</title>
				<link>https://www.puppeteers.net/blog/getting-the-last-inactive-vboxnet-interface-with-ruby/</link>
				<pubDate>Sat, 30 Jan 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/getting-the-last-inactive-vboxnet-interface-with-ruby/</guid>
				<description>&lt;p&gt;This could possibly be the most esoteric blog post I&amp;rsquo;ve ever written, but here it goes. So, for reasons even we have difficulty comprehending (hint: related to PXE booting in a Vagrant/Virtualbox environment) we had to find out which &lt;em&gt;inactive&lt;/em&gt; vboxnet (hostonly) interface had the highest index. We wanted the code to (hopefully) run on all platforms (Linux, MacOS, Windows) so we wanted to write this code in Ruby. So, this was the brutal but working end-result:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Authenticate and authorize Grafana users from FreeIPA groups via Keycloak</title>
				<link>https://www.puppeteers.net/blog/authenticating-and-authorizing-users-from-freeipa-groups-via-keycloak/</link>
				<pubDate>Thu, 28 Jan 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/authenticating-and-authorizing-users-from-freeipa-groups-via-keycloak/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://grafana.com&#34;&gt;Grafana&lt;/a&gt; is a common tool to visualize data from multiple datasources. Perhaps the most common datasource is &lt;a href=&#34;https://prometheus.io/docs/introduction/overview/&#34;&gt;Prometheus&lt;/a&gt;. If an organization has a Single-Sign On solution, it makes sense to authenticate users centrally with that solution That will make authentication easier and friendlier for end users (authenticate once and then access multiple services), and also enable stronger authentication by adding &lt;a href=&#34;https://en.wikipedia.org/wiki/Multi-factor_authentication&#34;&gt;multifactor authentication&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In addition to authenticating user, it is also possible to authorize Grafana users based on their group membership. &lt;/p&gt;</description>
			</item>
			<item>
				<title>Limiting the scope of puppet-rspec tests</title>
				<link>https://www.puppeteers.net/blog/limiting-the-scope-of-puppet-rspec-tests/</link>
				<pubDate>Fri, 22 Jan 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/limiting-the-scope-of-puppet-rspec-tests/</guid>
				<description>&lt;p&gt;Some Puppet modules like &lt;a href=&#34;https://github.com/treydock/puppet-module-keycloak/&#34;&gt;puppet-module-keycloak&lt;/a&gt; have hundreds of unit tests. That is good for test coverage, but waiting for test results hurts your productivity when you&amp;rsquo;re developing tests for your new code. There are at least two ways to (temporarily) limit the scope of the tests that you run. First method is baked in into the &lt;a href=&#34;https://puppet.com/try-puppet/puppet-development-kit/&#34;&gt;Puppet Development Kit&lt;/a&gt; command-line:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ pdk test unit --tests=spec/classes/init_spec.rb&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;This runs only the tests in the specified file.&lt;/p&gt;</description>
			</item>
			<item>
				<title>AWS to Azure resource translation table</title>
				<link>https://www.puppeteers.net/blog/aws-to-azure-resource-translation-table/</link>
				<pubDate>Sun, 17 Jan 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/aws-to-azure-resource-translation-table/</guid>
				<description>&lt;p&gt;We use Terraform for managing our Cloud infrastructure. Our customers typically use AWS and that&amp;rsquo;s what we&amp;rsquo;re most familiar with. Each public Cloud has its own terminology, so this page is a translation table between Terraform resource names in the AWS and Azure providers:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;&lt;strong&gt;AWS&lt;/strong&gt;&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;&lt;strong&gt;Azure&lt;/strong&gt;&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_instance&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_linux_virtual_machine&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_network_interface&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_network_interface&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_security_group&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_network_security_group&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_security_group_rule&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_network_security_rule&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_vpc&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_virtual_network&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_subnet&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_subnet&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;inapplicable&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_resource_group&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;aws_s3_bucket&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;azurerm_storage_account&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;region&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;location&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Mapping of Terraform AWS resource names to Azure resource names&lt;/p&gt;</description>
			</item>
			<item>
				<title>Querying DNS in Puppet code</title>
				<link>https://www.puppeteers.net/blog/querying-dns-in-puppet-code/</link>
				<pubDate>Wed, 06 Jan 2021 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/querying-dns-in-puppet-code/</guid>
				<description>&lt;p&gt;I recently had to add access control rules to OpenVPN Access Server to permit certain groups to access certain IP addresses. As we manage our Access Server configurations with Puppet using &lt;a href=&#34;https://github.com/OpenVPN/puppet-openvpnas&#34;&gt;puppet-openvpnas&lt;/a&gt; (by the author), we needed a way to parameterize the allowed IP addresses while keeping the Puppet code readable.&lt;/p&gt;&#xA;&lt;p&gt;This is where &lt;a href=&#34;https://forge.puppet.com/modules/dalen/dnsquery&#34;&gt;puppet-dnsquery&lt;/a&gt; came really handy. It allows using data that&amp;rsquo;s already available in DNS and using it in Puppet code. A simplistic example:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Migrating from Puppet 5 to 6 with Puppet Bolt</title>
				<link>https://www.puppeteers.net/blog/migrating-from-puppet-5-to-6-with-puppet-bolt/</link>
				<pubDate>Thu, 31 Dec 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/migrating-from-puppet-5-to-6-with-puppet-bolt/</guid>
				<description>&lt;p&gt;The main annoyance with agent-based configuration management is the management of the agents themselves. They usually don&amp;rsquo;t require any handholding after the initial install, &lt;em&gt;except&lt;/em&gt; when you need to migrate them to a new environment. For example, migrate from a Puppet 5-based server to Puppet 6.&lt;/p&gt;&#xA;&lt;p&gt;The migration process is not overly difficult, but in order to scale to more than a handful of nodes you will need to orchestrate the process. Historically we would have used Ansible for this task, but nowadays we prefer Puppet Bolt, which is more powerful and flexible in our use-cases. The Bolt plans typically end up being ugly, but based on my experience with Ansible Playbooks that tends to be the case anyways. I guess this is the natural result of mixing state-based configuration management, ad hoc commands and orchestration together.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Sed spell to ease moving to Terraform 0.13</title>
				<link>https://www.puppeteers.net/blog/sed-spell-to-ease-moving-to-terraform-0-13/</link>
				<pubDate>Mon, 28 Dec 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/sed-spell-to-ease-moving-to-terraform-0-13/</guid>
				<description>&lt;p&gt;When moving from older versions of Terraform 0.12.x to latest 0.12.x (now: 0.12.29) you may notice the following warnings:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Warning: Interpolation-only expressions are deprecated                                                                                                                              &#xA;&#xA;  on backup.tf line 11, in resource &amp;#34;hcloud_server_network&amp;#34; &amp;#34;backup_example_org&amp;#34;:                                                                                                 &#xA;  11:   server_id = &amp;#34;${module.backup_example_org.id}&amp;#34;        &lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;These make sense, but may come as a surprise as earlier 0.12.x versions were perfectly fine with them.&lt;/p&gt;&#xA;&lt;p&gt;You may have tons of these, so fixing them manually gets tiresome. The fix is not too difficult, but is quite unreadable due to escaping:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Using the Pomodoro technique to control distractions</title>
				<link>https://www.puppeteers.net/blog/using-of-the-pomodoro-technique-to-control-distractions/</link>
				<pubDate>Sat, 12 Dec 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/using-of-the-pomodoro-technique-to-control-distractions/</guid>
				<description>&lt;p&gt;We - like many others - have the problem that our work gets easily interrupted. Some interruptions come from the inside. For example, you remember &amp;ldquo;a thing you need to do&amp;rdquo; and without thinking start doing it immediately. Other interruptions are external, for example a colleague ask you for advise or a customer calls you asking for help. Almost always these things &lt;em&gt;could&lt;/em&gt; wait a bit, but the person simply acted on an impulse.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Making Prometheus and Alertmanager work behind a reverse proxy</title>
				<link>https://www.puppeteers.net/blog/making-prometheus-and-alertmanager-work-behind-a-reverse-proxy/</link>
				<pubDate>Wed, 09 Dec 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/making-prometheus-and-alertmanager-work-behind-a-reverse-proxy/</guid>
				<description>&lt;p&gt;We maintain a &lt;a href=&#34;https://prometheus.io/docs/introduction/overview/&#34;&gt;Prometheus&lt;/a&gt; and &lt;a href=&#34;https://prometheus.io/docs/alerting/latest/alertmanager/&#34;&gt;Alertmanager&lt;/a&gt; setup where both services are running on the same host and an Apache reverse proxy handles TLS and authentication, authorization and single sign-on with Apache &lt;a href=&#34;https://github.com/latchset/mod_auth_mellon&#34;&gt;mod_auth_mellon&lt;/a&gt; configured as a &lt;a href=&#34;https://www.keycloak.org/&#34;&gt;Keycloak&lt;/a&gt; SAML client. This way we can limit access to Prometheus and Alertmanager to people who belong to a certain &lt;a href=&#34;https://www.freeipa.org/page/Main_Page&#34;&gt;FreeIPA&lt;/a&gt; group.&lt;/p&gt;&#xA;&lt;p&gt;From end-user perspective Prometheus and Alertmanager are available at these locations:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://prometheus.example.org/prometheus&#34;&gt;https://prometheus.example.org/prometheus&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://prometheus.example.org/alertmanager&#34;&gt;https://prometheus.example.org/alertmanager&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In this setup both Prometheus and Alertmanager require additional startup options to work correctly. First Prometheus:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Improving Vim&#39;s Puppet integration</title>
				<link>https://www.puppeteers.net/blog/improving-vims-puppet-integration/</link>
				<pubDate>Thu, 15 Oct 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/improving-vims-puppet-integration/</guid>
				<description>&lt;p&gt;A recent package upgrade seemed to break Puppet syntax highlighting horribly on my Fedora 32 laptop. Not only did syntax highlighting not work, it was actually completely wrong and confusing. It seemed as if the Puppet file type detection was missing, so vim was probably using some a syntax highlighter for a wrong type. To see which file types are enabled I used &lt;code&gt;:set filetype&lt;/code&gt; followed by a couple of tabs (more details on &lt;a href=&#34;https://vi.stackexchange.com/questions/5780/list-known-filetypes&#34;&gt;StackExchange&lt;/a&gt;).&lt;/p&gt;</description>
			</item>
			<item>
				<title>Restrict access to a location in Apache reverse proxy with TLS</title>
				<link>https://www.puppeteers.net/blog/restrict-access-to-a-location-in-apache-reverse-proxy-with-tls/</link>
				<pubDate>Fri, 04 Sep 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/restrict-access-to-a-location-in-apache-reverse-proxy-with-tls/</guid>
				<description>&lt;p&gt;Ever had a case where you needed to use a name based Apache reverse proxy in front of some application server, while restring access to some proxied location at the same time? Here’s how to do it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;first-define-a-virtual-host&#34;&gt;First define a virtual host:&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;lt;VirtualHost *:443&amp;gt;&#xA;    ServerName myserver.example.com&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;h3 id=&#34;set-the-request-headers-you-are-of-course-using-tls-arent-you&#34;&gt;Set the request headers (you are of course using TLS, aren&amp;rsquo;t you):&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;RequestHeader set X-Forwarded-Proto &amp;#34;https&amp;#34;&#xA;RequestHeader set X-Forwarded-Port &amp;#34;443&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;h3 id=&#34;proxy-to-some-internal-address-here-to-localhost-port-8080&#34;&gt;Proxy to some internal address, here to localhost port 8080:&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ProxyRequests Off&#xA;ProxyPreserveHost On&#xA;ProxyPass http://127.0.0.1:8080/&#xA;ProxyPassReverse http://127.0.0.1:8080/&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;h3 id=&#34;restrict-access-to-the-host-or-networks-you-need-to&#34;&gt;Restrict access to the host or networks you need to:&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;lt;Location &amp;#34;/my/location/&amp;#34;&amp;gt;&#xA; Require ip 10.0.0.0/8&#xA;&amp;lt;/Location&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;Note: this will work with Apache 2.4 and up. With older versions you can use the same idea.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Taming apt timers in Ubuntu 18.04</title>
				<link>https://www.puppeteers.net/blog/taming-apt-timers-in-ubuntu-18-04/</link>
				<pubDate>Wed, 05 Aug 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/taming-apt-timers-in-ubuntu-18-04/</guid>
				<description>&lt;p&gt;Ubuntu 18.04 AWS images - and probably other Cloud images as well - have automated upgrades turned on. This is done via two &lt;a href=&#34;https://www.freedesktop.org/software/systemd/man/systemd.timer.html&#34;&gt;systemd timers&lt;/a&gt;, &lt;em&gt;apt-daily.timer&lt;/em&gt; and &lt;em&gt;apt-daily-upgrade.timer&lt;/em&gt;. The timers kick of periodically as well as when an instance is rebooted. This sounds reasonable until you are provisioning something: you&amp;rsquo;re unable to install or configure any packages while apt and/or dpkg are running. With a really fresh cloud base image this &lt;em&gt;may&lt;/em&gt; not be an issue, but when the base image gets sufficiently old then &lt;em&gt;apt-get upgrade&lt;/em&gt; starts taking enough time to cause issues.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Testing connection recovery with ss</title>
				<link>https://www.puppeteers.net/blog/testing-connection-recovery-with-ss/</link>
				<pubDate>Sat, 30 May 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/testing-connection-recovery-with-ss/</guid>
				<description>&lt;p&gt;As mentioned in our &lt;a href=&#34;https://www.puppeteers.net/blog/email-to-xmpp-bridge-milter-xmpp-released/&#34;&gt;earlier blog post&lt;/a&gt; we created an &amp;ldquo;email to XMPP bridge&amp;rdquo; called &lt;a href=&#34;https://github.com/Puppet-Finland/milter-xmpp/&#34;&gt;milter-xmpp&lt;/a&gt; for forwarding alert emails from monitoring systems to an XMPP chatroom. It was known that if the connection to the XMPP server ever died the bridge would stop working, but we wanted to know how fast that would happen. It took some days, but eventually the connection died.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/Puppet-Finland/milter-xmpp/commit/753381339cb4a750e758a33a61c0050236fc0297&#34;&gt;fix&lt;/a&gt; ended up being quite trivial, but we needed a way to break the connection. Fortunately the good folks at StackExchange already &lt;a href=&#34;https://unix.stackexchange.com/questions/71940/killing-tcp-connection-in-linux&#34;&gt;had a good solution&lt;/a&gt;: use the &amp;ldquo;ss&amp;rdquo; tool (&amp;ldquo;another utility to investigate sockets&amp;rdquo;) which provides output similar to netstat:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Ubuntu and Debian preseeding tips</title>
				<link>https://www.puppeteers.net/blog/ubuntu-and-debian-preseeding-tips/</link>
				<pubDate>Mon, 18 May 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/ubuntu-and-debian-preseeding-tips/</guid>
				<description>&lt;p&gt;Over the years I&amp;rsquo;ve had to automate installation of Ubuntu and Debian installers a number of times. The primary motivation has always been to avoid all the questions about timezone, locale, partitioning and which packages to install &lt;em&gt;and&lt;/em&gt; to not have to babysit the installation. In theory automating the installation is as simple as writing a &lt;a href=&#34;https://wiki.debian.org/DebianInstaller/Preseed&#34;&gt;preseed file&lt;/a&gt;. In practice it is much harder for a number of reasons:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Documentation on the Internet around custom installation media and preseeding setup is anecdotal at best - it gets you going and then lets you fall on your face. The official documentation is pretty good, though.&lt;/li&gt;&#xA;&lt;li&gt;The exact format of the preseed questions varies by operating system &lt;em&gt;version&lt;/em&gt;, not just by operating system (Debian, Ubuntu).&lt;/li&gt;&#xA;&lt;li&gt;In most cases questions about locale and keyboard layout must have answers &lt;em&gt;before&lt;/em&gt; the installer preseed is even loaded. This is misleading, because the preseed file &lt;em&gt;does&lt;/em&gt; have those questions, even though the answers have zero practical effect for the installer.&lt;/li&gt;&#xA;&lt;li&gt;There are many ways to accomplish preseeding, most notably by creating a custom installation medium with the preseed file, or by getting the preseed from an URL (&amp;ldquo;auto mode&amp;rdquo;).&lt;/li&gt;&#xA;&lt;li&gt;The default keyboard layout in most Ubuntu/Debian installer is en-US. This is fine, except when you don&amp;rsquo;t have an American keyboard: then you will be struggling to find characters such as &amp;ldquo;:&amp;rdquo; or &amp;ldquo;/&amp;rdquo;, which are relatively essential when loading the preseed file from an URL.&lt;/li&gt;&#xA;&lt;li&gt;It is impossible to remember how to start &amp;ldquo;automated install&amp;rdquo; from a vanilla installer menu, let alone figure out what the keyboard and locale settings should look like on the kernel command-line.&lt;/li&gt;&#xA;&lt;li&gt;The installer is happy to produce unworking configurations (e.g. no bootloader or no kernel) without giving any hints why things are not working.&lt;/li&gt;&#xA;&lt;li&gt;Each mistake is costly in time, especially if it affects something like Grub configuration which happens at the end of the install.&lt;/li&gt;&#xA;&lt;li&gt;Because of above, you tend to try to make as many changes to the kernel command line and/or preseeding as possible in one go. This means that when you get things working you don&amp;rsquo;t know what the proper fix was, and don&amp;rsquo;t feel like repeating the process to find out.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In order not to be completely negative here is the general recipe for success:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Using snmpwalk to debug SNMPv3 issues</title>
				<link>https://www.puppeteers.net/blog/using-snmpwalk-to-debug-snmpv3-issues/</link>
				<pubDate>Thu, 14 May 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/using-snmpwalk-to-debug-snmpv3-issues/</guid>
				<description>&lt;p&gt;Our &lt;a href=&#34;https://github.com/Puppet-Finland/puppet-snmpd&#34;&gt;puppet-snmpd module&lt;/a&gt; support SNMPv3 very well across a wide range of operating systems. Because o that SNMPv3 is what we use across the board in multiple environments. However, in the rare cases when I have to debug SNMP problems I can never remember the exact snmpwalk command-lines to use. So, here is the usual way to run snmpwalk to figure out if SNMP is providing data:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ snmpwalk -Os -v 3 -u monitor -l authPriv -a sha -A &amp;lt;auth-pass&amp;gt; -x AES -X &amp;lt;encryption-pass&amp;gt; hostname&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;Run from the monitoring server (e.g. &lt;a href=&#34;https://www.librenms.org/&#34;&gt;LibreNMS&lt;/a&gt; configured with &lt;a href=&#34;https://github.com/Puppet-Finland/puppet-librenms/&#34;&gt;puppet-librenms&lt;/a&gt;) to make sure basic connectivity and access controls are correct as well. Or against localhost if you&amp;rsquo;re interested in the data and not connectivity.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Manage Linux client DNS settings in OpenVPN and Wireguard</title>
				<link>https://www.puppeteers.net/blog/manage-linux-client-dns-settings-in-openvpn-and-wireguard/</link>
				<pubDate>Tue, 31 Mar 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/manage-linux-client-dns-settings-in-openvpn-and-wireguard/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://openvpn.net/&#34;&gt;OpenVPN&lt;/a&gt; and &lt;a href=&#34;https://www.wireguard.com/&#34;&gt;Wireguard&lt;/a&gt; are both used to provide secure, remote access to an organization&amp;rsquo;s internal services such as network shares (NFS and Samba), desktops and servers (SSH and RDP) or web services. Linux is generally a first-class citizen as far as clients are concerned but with one exception: accessing DNS through the VPN tunnel.&lt;/p&gt;&#xA;&lt;p&gt;OpenVPN servers have the concept of pushing settings such as DNS servers to client. This happens by emulating a DHCP server. Windows OpenVPN clients for example are able to process this information automatically to update the (per-link) DNS server settings automatically. But Linux traditionally has not had this capability without support from &amp;ldquo;up&amp;rdquo; and &amp;ldquo;down&amp;rdquo; scripts. When one is using &lt;a href=&#34;https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html&#34;&gt;systemd-resolved&lt;/a&gt; the traditional ways such as using resolvconf or modifying /etc/resolv.conf directly no longer work or cause issues. Fortunately there is a good script, &lt;a href=&#34;https://github.com/jonathanio/update-systemd-resolved&#34;&gt;update-systemd-resolved&lt;/a&gt;, for automating the DNS server setup process for systemd-resolved. Basically it takes the list of DNS settings OpenVPN provides and converts them into dbus calls that modify the running systemd-resolved&amp;rsquo;s configuration.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Giving special treatment to the last entry when iterating over a Puppet hash</title>
				<link>https://www.puppeteers.net/blog/giving-special-treatment-to-the-last-entry-when-iterating-over-a-puppet-hash/</link>
				<pubDate>Thu, 20 Feb 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/giving-special-treatment-to-the-last-entry-when-iterating-over-a-puppet-hash/</guid>
				<description>&lt;p&gt;When you&amp;rsquo;re iterating over a hash you may end up having to treat the last hash entry differently. For example, if you&amp;rsquo;re creating entries to a JSON file you should have &amp;ldquo;},&amp;rdquo; in between the entries &lt;em&gt;until&lt;/em&gt; you reach the last entry, where you &lt;em&gt;must&lt;/em&gt; have just &amp;ldquo;}&amp;rdquo; or JSON data will be invalid.&lt;/p&gt;&#xA;&lt;p&gt;Now, hashes do not have indexes, nor does Puppet support variable reassignment along the lines of $x = $x +1, so you&amp;rsquo;re out of luck trying to get the length and comparing that to a counter. So, you need some other way to detect when iteration has reached the last entry in the hash.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Debugging rspec-puppet</title>
				<link>https://www.puppeteers.net/blog/debugging-rspec-puppet/</link>
				<pubDate>Fri, 07 Feb 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/debugging-rspec-puppet/</guid>
				<description>&lt;p&gt;Usually writing rspec-puppet tests is straightforward. Then you run into some problem that does not make any sense. In my case I ran into a couple of duplicate resource declaration issues when adding rspec tests to our &lt;a href=&#34;https://github.com/Puppet-Finland/puppet-puppetmaster&#34;&gt;puppetmaster-installer&lt;/a&gt;. The error message was not that useful:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ pdk test unit --tests=spec/classes/puppetboard_spec.rb&#xA;--- snip ---&#xA; Failures:&#xA; 1) puppetmaster::puppetboard minimal settings on ubuntu-18.04-x86_64 is expected&#xA;    to compile into a catalogue without dependency cycles&#xA;    Failure/Error: it { is_expected.to compile }&#xA;      error during compilation: Evaluation Error: Error while evaluating a&#xA;      Resource Statement, Duplicate declaration: Class[Apache] is already&#xA;      declared; cannot redeclare (file: /home/samuli/opt/puppeteers/puppet-&#xA;      puppetmaster/spec/fixtures/modules/puppetmaster/manifests/puppetboard.pp,&#xA;      line: 156) (file: /home/samuli/opt/puppeteers/puppet-puppetmaster&#xA;      /spec/fixtures/modules/puppetmaster/manifests/puppetboard.pp, line: 156,&#xA;      column: 3) on node puppet.example.org&#xA;--- snip ---&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;That particular block of code in &lt;a href=&#34;https://github.com/Puppet-Finland/puppet-puppetmaster/blob/master/manifests/puppetboard.pp&#34;&gt;puppetboard.pp&lt;/a&gt; did include a parameterized call to the apache class. But nothing else (afaics) included that class. Also, in Vagrant the same code worked just fine, which made things seem even more bizarre. To add insult to the injury the error message did not tell where &lt;em&gt;else&lt;/em&gt; the apache class was included. That would probably have been way too helpful.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Terraform: Importing resources from a non-default AWS region</title>
				<link>https://www.puppeteers.net/blog/terraform-importing-resources-from-a-non-default-aws-region/</link>
				<pubDate>Tue, 04 Feb 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/terraform-importing-resources-from-a-non-default-aws-region/</guid>
				<description>&lt;p&gt;I recently - again - had to import resources to Terraform (0.11.14) from AWS that were outside of the default AWS region and were managed in a nested (non-root) module. The root module contained the provider configuration, including multiple aliases to allow creation of resources in different regions, as well as the nested module call, here in a very simple version:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;provider &amp;#34;aws&amp;#34; {&#xA;  region = &amp;#34;us-west-2&amp;#34;&#xA;}&#xA;&#xA;provider &amp;#34;aws&amp;#34; {&#xA;  region = &amp;#34;us-east-1&amp;#34;&#xA;  alias  = &amp;#34;us-east-1&amp;#34;&#xA;}&#xA;&#xA;provider &amp;#34;aws&amp;#34; {&#xA;  region = &amp;#34;us-west-2&amp;#34;&#xA;  alias  = &amp;#34;us-west-2&amp;#34;&#xA;}&#xA;&#xA;module &amp;#34;cloud&amp;#34; {&#xA;  param = value&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;The goal was to import some &lt;a href=&#34;https://www.terraform.io/docs/providers/aws/r/ses_email_identity.html&#34;&gt;aws_ses_email_identity&lt;/a&gt; resources into the &amp;ldquo;cloud&amp;rdquo; module. So, originally the cloud module had this:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Modifying XML files with Puppet&#39;s Augeas resource</title>
				<link>https://www.puppeteers.net/blog/modifying-xml-files-with-puppets-augeas-resource/</link>
				<pubDate>Fri, 31 Jan 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/modifying-xml-files-with-puppets-augeas-resource/</guid>
				<description>&lt;p&gt;This article is based on an &lt;a href=&#34;https://www.puppeteers.net/blog/xml-tiedostojen-muokkaus-puppetin-augeas-resurssilla/&#34;&gt;earlier article&lt;/a&gt; written in Finnish.&lt;/p&gt;&#xA;&lt;p&gt;Here I describe how to make changes to an XML file with Augeas - a useful approach when managing the whole file as a template is out of the question. The configuration file used as an example, web.xml, belong to &lt;a href=&#34;https://github.com/pwm-project/pwm&#34;&gt;Pwm&lt;/a&gt;, and we intend to set the webapp context parameter.&lt;/p&gt;&#xA;&lt;p&gt;As discussed before it is important to get an idea of how Augeas sees the file before attempting to modify it with Puppet. First we tell which Augeas lens to use (here: Xml) and which file(s) to associate with the Xml lens. This can be done with in one step:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Allowing changes to a Puppet-managed resource outside of Puppet</title>
				<link>https://www.puppeteers.net/blog/allowing-changes-to-a-puppet-managed-resource-outside-of-puppet/</link>
				<pubDate>Wed, 29 Jan 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/allowing-changes-to-a-puppet-managed-resource-outside-of-puppet/</guid>
				<description>&lt;p&gt;When a resource is managed by Puppet it is typically managed fully, or not at all. Exceptions to this rule, such as the &amp;ldquo;replace&amp;rdquo; parameter in the &lt;a href=&#34;https://puppet.com/docs/puppet/5.5/type.html#file-attribute-replace&#34;&gt;File resource&lt;/a&gt;, are rare. However, sometimes you end up having to change the properties of a resource outside of Puppet &lt;em&gt;without&lt;/em&gt; making Puppet overwrite those changes on the next run. A good example is a set of firewall rules managed with &lt;a href=&#34;https://forge.puppet.com/puppetlabs/firewall&#34;&gt;puppetlabs-firewall&lt;/a&gt; - you generally want to manage the whole ruleset with Puppet so that you can be sure of the state of the ruleset. For example, if you have libvirt installed, a bunch of virbr0-related rules may appear without your consent. On RedHat-based distros you may also see several rules getting removed by puppetlabs-firewall. Or a fellow sysadmin may have added some random iptables rules for debugging purposes. These are good reasons to have&lt;/p&gt;</description>
			</item>
			<item>
				<title>Getting the interface that matches an IP in Puppet</title>
				<link>https://www.puppeteers.net/blog/getting-the-interface-that-matches-an-ip-in-puppet/</link>
				<pubDate>Tue, 14 Jan 2020 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/getting-the-interface-that-matches-an-ip-in-puppet/</guid>
				<description>&lt;p&gt;I was recently in a situation where I needed to figure out which interface name matched a particular, static IPv4 address. On a single or a handful of nodes this could have been hardcoded in Hiera, for example, but there were dozens of nodes. The way I solved this was by creating a custom fact that uses the IPv4 address and loops through the interfaces list to find the matching interface name:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Why modules in Puppetfile should be sorted</title>
				<link>https://www.puppeteers.net/blog/why-modules-in-puppetfile-should-be-sorted/</link>
				<pubDate>Mon, 30 Dec 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/why-modules-in-puppetfile-should-be-sorted/</guid>
				<description>&lt;p&gt;This is probably one of the shortest blog postings in the history of this blog. But as this topic does not seem to be covered in more generic articles about avoiding Git merge conflicts (e.g. &lt;a href=&#34;https://geshan.com.np/blog/2016/04/3-simple-rules-for-less-or-no-git-conflicts/&#34;&gt;this&lt;/a&gt;, &lt;a href=&#34;https://medium.com/front-end-weekly/avoid-80-of-merge-conflicts-with-git-rebase-b5d755a082a6&#34;&gt;this&lt;/a&gt; and &lt;a href=&#34;https://team-coder.com/avoid-merge-conflicts/&#34;&gt;this&lt;/a&gt;), here it goes.&lt;/p&gt;&#xA;&lt;p&gt;Adding modules to the Puppetfile is a quite common task. When you or your team has multiple feature branches at the same time many of those branches may add new Puppet modules to the Puppetfile. This is fine, unless the new modules are added to the bottom of the Puppetfile: Git will get confused because it sees that multiple commits have changed the exact same place in the Puppetfile.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Testing Puppet feature branches in Foreman</title>
				<link>https://www.puppeteers.net/blog/testing-puppet-feature-branches-in-foreman/</link>
				<pubDate>Tue, 26 Nov 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/testing-puppet-feature-branches-in-foreman/</guid>
				<description>&lt;p&gt;With default settings &lt;a href=&#34;https://theforeman.org/&#34;&gt;Foreman&lt;/a&gt; does not allow Puppet Agents to specify their own environment when connecting to the Puppetserver. This makes it impossible to use r10k and feature branch workflow like you can with a normal Puppetserver. This behavior can fortunately be reverted easily by modifying Foreman configuration file, &lt;em&gt;/etc/foreman/settings.yaml&lt;/em&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Allow testing feature branches on nodes&#xA;:enc_environment: false&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;After modifying settings restart the webserver, e.g.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Reprovisioning bare metal Linux servers with Foreman and IPMI</title>
				<link>https://www.puppeteers.net/blog/reprovisioning-bare-metal-linux-servers-with-foreman-and-ipmi/</link>
				<pubDate>Wed, 25 Sep 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/reprovisioning-bare-metal-linux-servers-with-foreman-and-ipmi/</guid>
				<description>&lt;p&gt;Recently we had to reprovision a bare metal server that was in Foreman and had IPMI support. The goal was to check if our automated &amp;ldquo;force-join to FreeIPA domain after reprovisioning&amp;rdquo; logic worked as intented. We did not have access to any graphical IPMI console, nor Foreman&amp;rsquo;s GUI, so we had to do everything from the command-line. The host we reprovisioned was a CentOS 7 instance. It is assumed that &lt;a href=&#34;https://community.pivotal.io/s/article/How-to-work-on-IPMI-and-IPMITOOL&#34;&gt;IPMI tools are installed&lt;/a&gt;. This post was inspired by the &lt;a href=&#34;https://hobo.house/2016/08/10/automate-supermicro-server-provisioning-or-die-trying/&#34;&gt;Automate SuperMicro Server Provisioning or Die Trying&lt;/a&gt; blog post.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Enabling PHP 7.4 on CentOS 7 with Puppet</title>
				<link>https://www.puppeteers.net/blog/enabling-php-7-4-on-centos-7-with-puppet/</link>
				<pubDate>Tue, 17 Sep 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/enabling-php-7-4-on-centos-7-with-puppet/</guid>
				<description>&lt;p&gt;The PHP version comes in default CentOS 7 repositories is rather horribly outdated. This is problematic when running Wordpress, which has to be upgraded constantly, and really old PHP versions may not be accepted, security-patched or not. The solution is to get updated PHP from the &lt;a href=&#34;https://rpms.remirepo.net/&#34;&gt;remi yum repositories.&lt;/a&gt; There are two sets of PHP packages &lt;a href=&#34;https://blog.remirepo.net/pages/English-FAQ#scl&#34;&gt;as described here&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Install updated packages that replace the old PHP 5 packages (i.e. names are the same)&lt;/p&gt;</description>
			</item>
			<item>
				<title>Lack of default connection settings in Terraform 0.12.x</title>
				<link>https://www.puppeteers.net/blog/lack-of-default-connection-settings-in-terraform-0-12-x/</link>
				<pubDate>Thu, 29 Aug 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/lack-of-default-connection-settings-in-terraform-0-12-x/</guid>
				<description>&lt;p&gt;I was recently bit by a feature (one might call it a bug) in Terraform. We wanted to try out the &amp;ldquo;Networks&amp;rdquo; feature in &lt;a href=&#34;https://www.hetzner.com/cloud&#34;&gt;Hetzner Cloud&lt;/a&gt; but that required upgrading &lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-hcloud&#34;&gt;terraform-provider-hcloud&lt;/a&gt;, which in turn required upgrading Terraform to 0.12.x. For the most part migration from 0.11.x code to 0.12.x code was straightforward with some help from the automated migration tool. I even went as far as read some of the migration documentation, but missed one important part: &lt;a href=&#34;https://www.terraform.io/upgrade-guides/0-12.html#default-settings-in-connection-blocks&#34;&gt;connection blocks no longer have default values.&lt;/a&gt; To make the matter worse Terraform 0.12 does not error out if values are unset: instead if just runs the provisioner and then times out. This seems to be related to &lt;a href=&#34;https://github.com/hashicorp/terraform/issues/21690&#34;&gt;Terraform bug #21690&lt;/a&gt;.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Moving Jenkins user home directory</title>
				<link>https://www.puppeteers.net/blog/moving-jenkins-user-home-directory/</link>
				<pubDate>Thu, 29 Aug 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/moving-jenkins-user-home-directory/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://jenkins.io/&#34;&gt;Jenkins&lt;/a&gt; is a continuous integration (CI) / continuous delivery (CD) platform consisting of a Jenkins master - essentially a controller - which launches jobs on Jenkins slaves. Often slaves are controlled via SSH.&lt;/p&gt;&#xA;&lt;p&gt;Recently I ran into an issue with a legacy Jenkins slave where the &amp;ldquo;jenkins&amp;rdquo; where Puppet tried to enforce the home directory of &amp;ldquo;jenkins&amp;rdquo; to be /home/jenkins instead of /var/lib/jenkins:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error: Could not set home on user[jenkins]: Execution of &amp;#39;/usr/sbin/usermod -d /home/jenkins jenkins&amp;#39; returned 8: usermod: user jenkins is currently used by p rocess 802  &lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;The usermod command failed because several Jenkins processes (ssh-agent, Java) locked the user from modification. The solution was relatively straightforward:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Foreman and the Puppetserver environment cache</title>
				<link>https://www.puppeteers.net/blog/foreman-and-the-puppetserver-environment-cache/</link>
				<pubDate>Sat, 17 Aug 2019 00:00:00 +0000</pubDate>
				<guid>https://www.puppeteers.net/blog/foreman-and-the-puppetserver-environment-cache/</guid>
				<description>&lt;p&gt;Importing classes is a two-phase process when you&amp;rsquo;re using r10k with Foreman. Say you&amp;rsquo;ve created a new class, &amp;ldquo;role::ipa_server&amp;rdquo; to the &amp;ldquo;production&amp;rdquo; environment; without Foreman it would be sufficient to deploy the change on the Puppetserver with r10k:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ r10k deploy environment production -vp&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;At this point Foreman does not, however, know anything about this new class deployed with r10k:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block&#34;&gt;&#xA;    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;&#xA;      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ hammer puppet-class list --environment production|grep ipa_server&lt;/code&gt;&lt;/pre&gt;&#xA;  &lt;/div&gt;&lt;p&gt;The &amp;ldquo;Smart proxy&amp;rdquo; component in Foreman - also known as &amp;ldquo;foreman-proxy&amp;rdquo; - is responsible for fetching new classes from Puppetserver. With the latest Puppet versions it uses the Puppetserver API to do the import. It is fairly trivial to update Foreman&amp;rsquo;s list of classes using the command-line&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
