<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Linux &#8211; Johnny Morano&#039;s Tech Articles</title>
	<atom:link href="https://jmorano.moretrix.com/tag/linux/feed/" rel="self" type="application/rss+xml" />
	<link>https://jmorano.moretrix.com</link>
	<description>Ramblings of an old-fashioned space cowboy</description>
	<lastBuildDate>Wed, 08 Jun 2022 06:35:06 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>

<image>
	<url>https://jmorano.moretrix.com/wp-content/uploads/2022/04/cropped-jmorano_emblem-32x32.png</url>
	<title>Linux &#8211; Johnny Morano&#039;s Tech Articles</title>
	<link>https://jmorano.moretrix.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Using multipath together with mdadm on Debian</title>
		<link>https://jmorano.moretrix.com/2022/06/using-multipath-together-with-mdadm-on-debian/</link>
					<comments>https://jmorano.moretrix.com/2022/06/using-multipath-together-with-mdadm-on-debian/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Wed, 08 Jun 2022 06:35:04 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[Fiber Channel]]></category>
		<category><![CDATA[mdadm]]></category>
		<category><![CDATA[multipath]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1533</guid>

					<description><![CDATA[Using multipath together with mdadm on Debian Linux requires some changes to the initrd image, otherwise mdadm might&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Using multipath together with mdadm on Debian Linux requires some changes to the initrd image, otherwise mdadm might start before multipath, leading to only one FC path being used or even failure of starting multipath (no FC paths found at all).</p>



<p>To prevent this issue, three steps are required:</p>



<ul class="wp-block-list"><li>change the order kernel modules get loaded by initrd</li><li>disable the mdadm auto-assembly of disks when initrd is loaded</li><li>generate a new initrd image </li></ul>



<p>The initrd configuration files can be found at <code>/etc/initramfs-tools</code>.</p>



<p>First define the order of module loading during boot (ensure that <code>multipath</code> is loaded before <code>mdadm</code>). Edit the file <code>/etc/initramfs-tools/modules</code> to match:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># List of modules that you want to include in your initramfs.
# They will be loaded at boot time in the order below.
#
# Syntax: module_name [args ...]
#
# You must run update-initramfs(8) to effect this change.
#
# Examples:
#
# raid1
# sd_mod
libfc
megaraid_sas
scsi_dh_alua
scsi_transport_fc
dm_multipath
dm_service_time
multipath
md_mod</pre>



<p>This starts by loading the &#8220;fiber channel libs&#8221; (<code>libfc</code>) first, followed by the MegaRAID modules, SCSI over fiber channel, <code>multipath</code> and <code>mdadm</code>.</p>



<p>Now copy the mdadm initramfs hook to <code>/etc</code> to override the default one:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">cp /usr/share/initramfs-tools/hooks/mdadm /etc/initramfs-tools/hooks/</pre>



<p>Edit the hook file in <code>/etc/initramfs-tools/hooks/mdadm </code>and add an &#8216;<code>exit 0</code>&#8216; (see line 10):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/sh
#
# Copyright © 2006-2008 Martin F. Krafft &lt;madduck@debian.org>,
#
2012 Michael Tokarev &lt;mjt@tls.msk.ru>
# based on the scripts in the initramfs-tools package.
# released under the terms of the Artistic Licence.
#
set -eu
exit 0
PREREQ="udev"
prereqs()
{
echo "$PREREQ"
}</pre>



<p>Disable automatic startup of the <code>mdadm</code> service:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">rm /lib/systemd/system/mdadm.service
systemctl daemon-reload
systemctl disable mdadm.service</pre>



<p>Finally, update the initrd images for all kernel versions installed:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">update-initramfs -u -k all</pre>



<p>Reboot the server to see if modules are now loaded in the correct order, and multipath is loaded and started before mdadm.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/06/using-multipath-together-with-mdadm-on-debian/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Read the HAProxy UNIX socket file using Perl</title>
		<link>https://jmorano.moretrix.com/2022/04/read-the-haproxy-unix-socket-file-using-perl/</link>
					<comments>https://jmorano.moretrix.com/2022/04/read-the-haproxy-unix-socket-file-using-perl/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Mon, 25 Apr 2022 10:52:45 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[HAProxy]]></category>
		<category><![CDATA[Monitoring]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1515</guid>

					<description><![CDATA[HAProxy provides a socket file which can be used to do maintenance (enable/ disable backends, retrieve information and&#8230;]]></description>
										<content:encoded><![CDATA[
<p><a rel="noreferrer noopener" href="http://www.haproxy.org/" data-type="URL" data-id="http://www.haproxy.org/" target="_blank">HAProxy</a> provides a <a href="http://docs.haproxy.org/2.5/management.html#9.3" data-type="URL" data-id="http://docs.haproxy.org/2.5/management.html#9.3" target="_blank" rel="noreferrer noopener">socket file</a> which can be used to do maintenance (enable/ disable backends, retrieve information and statistics, &#8230;).</p>



<p>The statistics part contains quite some interesting information for monitoring and alerting.</p>



<p>The below Perl code snippit will loop over a <code>glob</code> of socket files (for instance when you have multiple HAProxy configurations running as separate processes) and print out the values returned by the &#8220;<code>show info</code>&#8221; command.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use IO::Socket::UNIX;

foreach my $socket_file (glob("/run/haproxy/*.sock")){
    print "- Reading socket: $socket_file\n";
    my $client = IO::Socket::UNIX->new(
        Type => SOCK_STREAM(),
        Peer => $socket_file,
    );

    print "- show info\n";
    print $client "show info\n";
    my $header = &lt;$client>;
    chomp($header);

    $header =~ s/^#\s+//;
    my @keys = split ',', $header;
    print "- header:$header\n";

    while (my $line = &lt;$client>){
        next unless $line =~ /^.+/;

        chomp($line);
        my @values = split ',', $line;
        print " - Got $line\n";
        print "   $keys[$_]: ".($values[$_]//'')."\n" foreach 0..$#keys;
    }

    close $client;
}</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/read-the-haproxy-unix-socket-file-using-perl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>A Prometheus Exporter framework written in Perl</title>
		<link>https://jmorano.moretrix.com/2022/04/a-prometheus-exporter-framework-written-in-perl/</link>
					<comments>https://jmorano.moretrix.com/2022/04/a-prometheus-exporter-framework-written-in-perl/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Mon, 25 Apr 2022 09:45:51 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Prometheus]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1513</guid>

					<description><![CDATA[I released a small project I wrote a while ago, to create quick Prometheus exporters in Perl for&#8230;]]></description>
										<content:encoded><![CDATA[
<p>I released a small project I wrote a while ago, to create quick Prometheus exporters in Perl for providing some custom data. The project itself can be found at <a rel="noreferrer noopener" href="https://github.com/insani4c/prometheus-exporter" target="_blank">https://github.com/insani4c/prometheus-exporter</a>. Back then I decided not to use <a rel="noreferrer noopener" href="Prometheus" target="_blank">Net::Prometheus</a> as I wanted to use <a rel="noreferrer noopener" href="https://metacpan.org/pod/HTTP::Daemon" data-type="URL" data-id="https://metacpan.org/pod/HTTP::Daemon" target="_blank">HTTP::Daemon</a> with <a rel="noreferrer noopener" href="https://metacpan.org/pod/threads" data-type="URL" data-id="https://metacpan.org/pod/threads" target="_blank">threads</a> and not <a href="https://metacpan.org/pod/Plack" data-type="URL" data-id="https://metacpan.org/pod/Plack" target="_blank" rel="noreferrer noopener">Plack</a>.</p>



<p>A small example of how to use the framework:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">my $exporter = Prometheus::Exporter->new({
    listen_port => 9090, 
    listen_addr => "127.0.0.1", 
    max_threads => 5,
});

$exporter->register_metrics({
    test_metric        => {type => "gauge",     desc => "A test metric"},
    test_metric_labels => {type => "gauge",     desc => "A test metric", labels => ["code=42", "code=99"]},
    test_counter       => {type => "counter",   desc => "A test metric"},
    test_histogram     => {type => "histogram", buckets => ['0.3', '0.6', '1.2', '+Inf']},
});

$exporter->register_collector(sub {
    my $timeout = int(rand(5));
    sleep $timeout;

    $exporter->get_metric("test_metric")->value(rand(100));
    $exporter->get_metric("test_metric_labels")->value([rand(42), rand(99)]);

    $test_counter += int(rand(20));
    $exporter->get_metric("test_counter")->value($test_counter);

    $histo_buckets{"0.3"}  += rand(20);
    $histo_buckets{"0.6"}  += $histo_buckets{"0.3"} + rand(20);
    $histo_buckets{"1.2"}  += $histo_buckets{"0.6"} + rand(20);
    $histo_buckets{"+Inf"} += $histo_buckets{"1.2"} + rand(20);
    my $histo_sum = 2.0 * $histo_buckets{"+Inf"};
    my $histo_count = $histo_buckets{"+Inf"};
    $exporter->get_metric("test_histogram")->value(\%histo_buckets, $histo_sum, $histo_count);
});

$exporter->run;
</pre>



<p>The framework will start a small HTTP daemon once <code>run()</code> is called and will handle all client requests by using <code>threads</code>. On each request, the framework will call the <code>subroutine</code> or <code>coderef</code> defined at <code>register_collector()</code>. Currently, that coderef must store the observed values by using the construct seen in the above example, by calling the <code>value()</code> method on the registered metric objects.</p>



<p>Also currently, the histogram implementation is not yet supporting labels.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/a-prometheus-exporter-framework-written-in-perl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Libvirt guest startup issue with AppArmor</title>
		<link>https://jmorano.moretrix.com/2022/04/libvirt-guest-startup-issue-with-apparmor/</link>
					<comments>https://jmorano.moretrix.com/2022/04/libvirt-guest-startup-issue-with-apparmor/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Sun, 10 Apr 2022 09:37:48 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[AppArmor]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Libvirt]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1487</guid>

					<description><![CDATA[With AppArmor enabled on Debian/ Ubuntu systems, starting up virtual machines with libvirt can cause startup failures if&#8230;]]></description>
										<content:encoded><![CDATA[
<p>With <a href="https://apparmor.net/" data-type="URL" data-id="https://apparmor.net/" target="_blank" rel="noreferrer noopener">AppArmor</a> enabled on Debian/ Ubuntu systems, starting up virtual machines with <a href="https://libvirt.org/" data-type="URL" data-id="https://libvirt.org/" target="_blank" rel="noreferrer noopener">libvirt</a> can cause startup failures if not AppArmor is not properly configured.</p>



<p>AppArmor will write messages to the kernel log (visible with either the <code>dmesg</code> command or in <code>kernel.log</code> if available) regarding its actions.</p>



<p>If your libvirt guests are not starting up or failing, have a look at <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">dmesg</mark></code>. Example:</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="82" src="https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-10-11-09-15-1024x82.png" alt="" class="wp-image-1488" srcset="https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-10-11-09-15-1024x82.png 1024w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-10-11-09-15-300x24.png 300w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-10-11-09-15-768x62.png 768w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-10-11-09-15-850x68.png 850w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-10-11-09-15.png 1242w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>In the above example AppArmor has <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">denied</mark> (<code>apparmor="DENIED"</code>) read access (<code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">requested_mask=r</mark></code>) to the file <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">/data/vms/cluster_storage/base-os-ubuntu-focal.qcow2</mark></code>. This blocks of course the startup guest machines we have previously created in the article: <a href="https://jmorano.moretrix.com/2022/03/terraform-and-libvirtd-nodes/" data-type="post" data-id="1302">Terraform and libvirtd nodes</a>.</p>



<p>To fix the issue, edit the file: <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">/etc/apparmor.d/libvirt/TEMPLATE.qemu</mark></code></p>



<p>By default it has the following content:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#
# This profile is for the domain whose UUID matches this file.
#

#include &lt;tunables/global>

profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
  #include &lt;abstractions/libvirt-qemu>
}
</pre>



<p>In order to allow <code>libvirt</code> to use the guest image files, change the content to (or add a similar line if your file path is different):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#
# This profile is for the domain whose UUID matches this file.
#

#include &lt;tunables/global>

profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
  #include &lt;abstractions/libvirt-qemu>
  /data/vms/cluster_storage/**.qcow2 rwk,
}
</pre>



<p>The added line (<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">line 9</mark>) will allow read (<code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">r</mark></code>), write (<code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">w</mark></code>) and lock (<code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">k</mark></code>) access to all <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">qcow2</mark></code> files in the directory <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">/data/vms/cluster_storage</mark></code>.</p>



<p>Once added, all libvirt guests will start up again without any (AppArmor) issues.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/libvirt-guest-startup-issue-with-apparmor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Deploy a PostgreSQL database with an initial schema using Ansible</title>
		<link>https://jmorano.moretrix.com/2022/04/deploy-a-postgresql-database-with-an-initial-schema-using-ansible/</link>
					<comments>https://jmorano.moretrix.com/2022/04/deploy-a-postgresql-database-with-an-initial-schema-using-ansible/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Sat, 09 Apr 2022 08:46:24 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Database]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Ansible]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Postgresql]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1479</guid>

					<description><![CDATA[Ansible is a great automation tool to manage operating systems, but also to manage database like PostgreSQL. Many&#8230;]]></description>
										<content:encoded><![CDATA[
<p><a rel="noreferrer noopener" href="https://www.ansible.com/" data-type="URL" data-id="https://www.ansible.com/" target="_blank">Ansible</a> is a great automation tool to manage operating systems, but also to manage database like <a rel="noreferrer noopener" href="https://postgresql.org" data-type="URL" data-id="https://postgresql.org" target="_blank">PostgreSQL</a>. Many <a rel="noreferrer noopener" href="https://docs.ansible.com/ansible/latest/collections/community/postgresql/index.html" data-type="URL" data-id="https://docs.ansible.com/ansible/latest/collections/community/postgresql/index.html" target="_blank">Ansible modules</a> are available to create playbooks which execute various database administration tasks.</p>



<p>In this article we will have a closer look how to ensure that </p>



<ul class="wp-block-list"><li>a default database has been created</li><li>a set of configured extensions have been installed</li><li>an initial database schema has been deployed</li></ul>



<p>The user that will log on to the remote host using Ansible (and <code>SSH</code>), must be able to become the <code>postgres</code> user using <code>sudo</code> without a password (in this example, can be changed of course).</p>



<p>The tasks below are split in two big tasks, both containing a block. <a rel="noreferrer noopener" href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html" data-type="URL" data-id="https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html" target="_blank">Blocks</a> allow to group certain tasks which might need the same variables (for instance the same tags, become variables, &#8230;).</p>



<p>The first block will ensure that the default database has been created, based on a YAML variable called <code>{{ db_name }}</code>. This variable must be set in either a <code>group_vars</code> file, <code>host_vars</code> file or supplied at the command line.</p>



<p>In the same block, Ansible will also ensure that the <code>pg_stat_statements</code> extension is enabled and installed in the above database. <a href="https://www.postgresql.org/docs/current/pgstatstatements.html" data-type="URL" data-id="https://www.postgresql.org/docs/current/pgstatstatements.html" target="_blank" rel="noreferrer noopener">pg_stat_statements</a> is a very useful extension to debug or display statistics regarding the SQL statements executed on that specific database.</p>



<p>And the end of the first block, a task was added to loop over a YAML variable called <code>{{ shared_preload_extensions }}</code>. The variable is a supposed to be a list and should contain all extra extensions required to enabled to the above database. Shared preload extensions also need to be configured in the <code>postgresql.conf</code> file, which is not covered in this article.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">- name: Default Database
  tags: default_database
  vars:
    ansible_become_user: postgres
    ansible_become_method: sudo
    ansible_become_pass: null
  block:
    - name: Ensure required database
      postgresql_db:
        name: "{{ db_name }}"
        encoding: UTF-8

    - name: Ensure pg_stat_statements extension
      postgresql_ext:
        name: "pg_stat_statements"
        db: "{{ db_name }}"
        schema: public
        state: present

    - name: Ensure shared_preload extensions
      postgresql_ext:
        name: "{{ item }}"
        db: "{{ db_name }}"
        state: present
      loop: "{{ shared_preload_libraries.split(',') }}"
      loop_control:
        label: " {{ item }}"

- name: Ensure initial database schema
  tags: default_database
  block:
    - name: Schema definition file
      template:
        src: "{{ db_name }}/schema_definition.sql.j2"
        dest: /var/lib/postgresql/initial_schema_definition.sql
        owner: postgres
        group: postgres
        mode: 0600
      when: ( role_path + "/templates/" + db_name + "/schema_definition.sql.j2" ) is file
      register: initial_schema

    - name: Apply the schema definition
      ignore_errors: True
      vars:
        ansible_become_user: postgres
        ansible_become_method: sudo
        ansible_become_pass: null
      community.postgresql.postgresql_query:
        db: "{{ db_name }}"
        path_to_script: /var/lib/postgresql/initial_schema_definition.sql
        encoding: UTF-8
        as_single_query: yes
      when: initial_schema.changed

  rescue:
    - debug:
        msg: "No schema definition found for {{db_name}}, skipping..."
</pre>



<p>The second block of tasks will ensure that an initial database schema is deployed. This block consists of two tasks only. </p>



<p>The first task will upload the schema definition SQL-based file to a specific path on the remote host.</p>



<p>The second task will try to execute that SQL file, if it was changed by the first task. This means of course that if the file was changed manually on the remote host (or even removed), Ansible <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">will update that file again</mark> in the first task and it will deploy the full file again in the second task! This might overwrite or break your database.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/deploy-a-postgresql-database-with-an-initial-schema-using-ansible/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Perl script to monitor the rate of logs</title>
		<link>https://jmorano.moretrix.com/2022/04/perl-script-to-monitor-the-rate-of-logs/</link>
					<comments>https://jmorano.moretrix.com/2022/04/perl-script-to-monitor-the-rate-of-logs/#respond</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 07 Apr 2022 12:39:50 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Dev]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[IPTables]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Logging]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1399</guid>

					<description><![CDATA[In a previous article (IPTables Logging in JSON with NFLOG and ulogd2) we learned how to log certain&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In a previous article (<a href="https://jmorano.moretrix.com/2022/03/logging-in-iptables-with-nflog-and-ulogd2/" data-type="post" data-id="1308">IPTables Logging in JSON with NFLOG and ulogd2</a>) we learned how to log certain IPTables rules to JSON log files.</p>



<p>Monitoring the logs in real-time on the command line, can also be very useful when debugging either the rules themselves or when analyzing certain issues. Rather than just looking at the logs, in some situations it might be useful to track the rate of the log messages. A self-written Perl script can be useful as it allows to be flexible when it comes to:</p>



<ul class="wp-block-list"><li>parsing logs</li><li>formatting the output (with colors or tables or &#8230;)</li><li>calculating statistics</li><li>&#8230;</li></ul>



<p>The following Perl script uses a few modules which need to be present:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">use IO::Async::Timer::Periodic;
use IO::Async::Loop;
use Time::HiRes qw/time/;
use Term::ANSIColor qw(:constants);
use Getopt::Long;</pre>



<p>The first two modules can be installed on Debian systems with:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">apt install libio-async-perl</pre>



<p>The others are part of the normal Perl packages and do not require any extra installation.</p>



<p>Next the script will use a polling mechanism to read from standard output at fixed intervals, to calculate the rate of the unique log lines. The default polling rate is set to 2 seconds but it can be managed through command line parameters:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">my $last_poll_time = time;

my $poll_rate = 2;
GetOptions (
    'p|pollrate=i' => \$poll_rate,
);

my $loop = IO::Async::Loop->new;
my $timer = IO::Async::Timer::Periodic->new(
   interval => $poll_rate,
   on_tick  => \&amp;log_rate
);

$timer->start;
$loop->add( $timer );
$loop->run;</pre>



<p>Finally, the script will define a subroutine called <code>log_rate</code>, which will read from standard output (or even a file) at each poll interval. Important is of course that the log lines from standard output do not contain unique data such as timestamps. The output must be as generic as possible.</p>



<p>Example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">tail -qf /var/log/ulog/blocked_detailed.json /var/log/ulog/blocked.json /var/log/ulog/passed.json  | jq -r --unbuffered '."oob.prefix"' 
blocked: invalid state
blocked: invalid state
blocked: invalid state
blocked: invalid state
blocked: invalid state
action=blocked
action=blocked
action=blocked
action=blocked
action=blocked
action=passed
action=passed
action=passed
action=passed</pre>



<p>The code snippit for <code>log_rate</code> could contain:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">sub log_rate {
    local $SIG{ALRM} = sub { die time, " time exceeded to read STDIN\n" };

    alarm($poll_rate);
    my $h;
    eval {
        local $| = 1;
        while (my $line = &lt;>) {
            chomp($line);
            $h->{$line}++;
        }
    };
    alarm(0);

    return unless keys %$h;

    my $delta_time = time - $last_poll_time;
    print DARK WHITE . sprintf("%d: ", time) . RESET;
    print( BOLD WHITE . $_ ." [" . GREEN . sprintf("%.2f/s", $h->{$_}/$delta_time) . BOLD WHITE "] | " . RESET) foreach keys %$h; 
    print "\n";

    $last_poll_time = time;
}</pre>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 2</mark> will start with declaring the &#8220;<code>ALARM</code>&#8221; signal. This signal is called when the <code>alarm</code> timeout has been reached (see further below).</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 4</mark> defines the <code>alarm</code> timeout in seconds: meaning: if everything below<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color"> line 4</mark> (until the next <code>alarm</code> line) takes longer than the defined timeout in seconds, the &#8220;ALRM&#8221; signal handler defined at <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">line 2</mark> will be called, which basically stops the code execution with a <code>die</code> (which in theory should stop the script with an <code>exit 1</code>).</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 5</mark> defines a hash reference which is required down below, to temporarily store unique log lines.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 6</mark> until <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">12</mark> define an <code>eval</code> block. The <code>eval</code> block will catch the ALRM signal <code>die</code> (once reached) without stopping the script with an <code>exit 1</code>. Inside the <code>eval</code> block, the standard output will be read with the diamond operator (<code>&lt;></code>) and unique lines will be counted and stored in the <code>$h</code> hash reference.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 13</mark>, right after the <code>eval</code> block, sets to <code>alarm</code> timeout to 0 again, which means it is disabled. This allows that only execution of the <code>eval</code> block will be evaluated for timeout. </p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 15</mark> ensures that only when log lines were discovered and stored in the temporary hash-ref<code> $h</code>, that rates will be printed to the screen.</p>



<p>The rest of the code will take care of printing the discovered log lines with their rates to the screen. Colors from <code>Term::ANSIColor</code> are used to make the output more vivid.</p>



<p>Example output:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="911" height="285" src="https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00.png" alt="" class="wp-image-1405" srcset="https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00.png 911w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00-300x94.png 300w, https://jmorano.moretrix.com/wp-content/uploads/2022/04/Screenshot-from-2022-04-06-14-14-00-768x240.png 768w" sizes="(max-width: 911px) 100vw, 911px" /></figure>



<p>The full version of the script can be found at: <a href="https://github.com/insani4c/perl_tools/tree/master/log_rate" target="_blank" rel="noreferrer noopener">https://github.com/insani4c/perl_tools/tree/master/log_rate</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/perl-script-to-monitor-the-rate-of-logs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Import configuration from Hiera or a Git repository with YAML files into Terraform</title>
		<link>https://jmorano.moretrix.com/2022/04/import-configuration-from-hiera-or-a-git-repository-with-yaml-files-into-terraform/</link>
					<comments>https://jmorano.moretrix.com/2022/04/import-configuration-from-hiera-or-a-git-repository-with-yaml-files-into-terraform/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Tue, 05 Apr 2022 11:26:43 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Terraform]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Hashicorp]]></category>
		<category><![CDATA[YAML]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1393</guid>

					<description><![CDATA[De-duplication of configuration information is key when managing large environments which use different types of automation (Terraform, Jenkins,&#8230;]]></description>
										<content:encoded><![CDATA[
<p>De-duplication of configuration information is key when managing large environments which use different types of automation (<a rel="noreferrer noopener" href="https://www.terraform.io/" data-type="URL" data-id="https://www.terraform.io/" target="_blank">Terraform</a>, <a rel="noreferrer noopener" href="https://www.jenkins.io/" data-type="URL" data-id="https://www.jenkins.io/" target="_blank">Jenkins</a>, <a rel="noreferrer noopener" href="https://www.ansible.com/" data-type="URL" data-id="https://www.ansible.com/" target="_blank">Ansible</a>, scripts executed as <a rel="noreferrer noopener" href="https://systemd.io/" data-type="URL" data-id="https://systemd.io/" target="_blank">Systemd</a> timers, <a rel="noreferrer noopener" href="https://puppet.com/" data-type="URL" data-id="https://puppet.com/" target="_blank">Puppet</a>&#8230;). Although many different configuration management tools exist (RDBMS, <a rel="noreferrer noopener" href="https://www.consul.io/" data-type="URL" data-id="https://www.consul.io/" target="_blank">Consul</a>, &#8230;), one of the easiest to use is <a href="https://puppet.com/docs/puppet/7/hiera_intro.html" data-type="URL" data-id="https://puppet.com/docs/puppet/7/hiera_intro.html" target="_blank" rel="noreferrer noopener">Hiera</a> or just a plain normal <a rel="noreferrer noopener" href="https://git-scm.com/" data-type="URL" data-id="https://git-scm.com/" target="_blank">Git</a> repository with <code><a rel="noreferrer noopener" href="https://yaml.org/" data-type="URL" data-id="https://yaml.org/" target="_blank">YAML</a></code> files, in some hierarchical way (which Hiera in theory is).</p>



<p> The YAML configuration hierarchy could be defined as the following file structure:</p>



<ul class="wp-block-list"><li><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">common.yaml</mark></code>: Default settings no matter which role or host. These can be overridden in all the below.</li><li><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">my_environment01.yaml</mark></code>: Environment specific configuration (example: development, staging, production, amsterdam, az01, az04, &#8230;). These can be overridden in all the below.</li><li><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">common/roles/some_server_role.yaml</mark></code>: A server role, or type definition, which contains role specific configuration parameters. The roles could implement an extra hierarchy as for instance:<br /><ul><li><code>debian::databases::postgres</code></li><li><code>debian::databases::postgres::timescale</code></li><li><code>debian::databases::postgres::timescale::prometheus</code></li><li><code>debian::loadbalancer::internal</code></li><li><code>debian::application::request_processor</code><br /><br />The hierarchy steps are divided by<code> :: </code>in the above example, and need to be inherited accordingly, each with their own YAML file.<br />These can be overridden in all the below.</li></ul></li><li><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">my_environment01/roles/some_server_role.yaml</mark></code>: override role configuration parameters per environment.<br />These can even be overridden on host level below.</li><li><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">my_environment01/hosts/my_hostname01.yaml</mark></code>: set host specific configuration parameters. This file is actually always required and should contain at least the IP address of the node and the server role string.</li></ul>



<p>Let&#8217;s take the following example: The host <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-cyan-blue-color">vmazdbprm01</mark></code> has the role <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color"><code>debian::databases::postgres::timescale::prometheus</code> </mark>and is deployed in the environment in <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">my_cool_location03</mark></code>. The configuration management should search for parameters in the following file locations (and first verify if the file path exists):</p>



<ol class="wp-block-list"><li><code>common.yaml</code></li><li><code>my_cool_location01.yaml</code></li><li><code>common/roles/debian.yaml</code></li><li><code>common/roles/debian::databases.yaml</code></li><li><code>common/roles/debian::databases::postgres.yaml</code></li><li><code>common/roles/debian::databases::postgres::timescale.yaml</code></li><li><code>common/roles/debian::databases::postgres::timescale::prometheus.yaml</code></li><li><code>my_cool_location01/roles/debian.yaml</code></li><li><code>my_cool_location01/roles/debian::databases.yaml</code></li><li><code>my_cool_location01/roles/debian::databases::postgres.yaml</code></li><li><code>my_cool_location01/roles/debian::databases::postgres::timescale.yaml</code></li><li><code>my_cool_location01/roles/debian::databases::postgres::timescale::prometheus.yaml</code></li><li><code>my_cool_location01/hosts/vmazdbprm01.yaml</code></li></ol>



<p>This means that any code which wants to implement the above configuration management, needs to verify if the above <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">13 files</mark> exists from top to bottom, and if yes loads the YAML file accordingly.</p>



<p>Terraform is an open-source infrastructure as code software tool that provides a consistent CLI workflow to manage hundreds of cloud services.</p>



<p>Implementing the above <code>YAML</code> hierarchy in Terraform, could be done as follows:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">locals {
  host_cfg             = yamldecode(fileexists("cfgmgmt/${var.environment}/hosts/${var.node}.yaml") ? file("cfgmgmt/${var.environment}/hosts/${var.node}.yaml") : "{server_role: debian}")

  roles_list           = split("::", local.host_cfg.server_role)
  all_roles_list       = [ for index in range(length(local.roles_list)): join("::",slice(local.roles_list, 0, index + 1))  ]

  common_cfg           = yamldecode(fileexists("cfgmgmt/common.yaml") ? file("cfgmgmt/common.yaml") : "{}")
  common_role_cfg_list = [ for file in local.all_roles_list:
      yamldecode(fileexists("cfgmgmt/common/roles/${file}.yaml") ? file("cfgmgmt/common/roles/${file}.yaml") : "{}" )]
    
  env_cfg              = yamldecode(fileexists("cfgmgmt/${var.environment}.yaml") ? file("cfgmgmt/${var.environment}.yaml") : "{}")
  env_role_cfg_list    = [ for file in local.all_roles_list:
      yamldecode(fileexists("cfgmgmt/${var.environment}/roles/${file}.yaml") ? file("cfgmgmt/${var.environment}/roles/${file}.yaml") : "{}") ]
        
  common_role_cfg_map  = merge(local.common_role_cfg_list...)
  env_role_cfg_map     = merge(local.env_role_cfg_list...)

  cfg                  = merge(local.common_cfg, local.env_cfg, local.common_role_cfg_map, local.env_role_cfg_map, local.host_cfg)
}</pre>



<p>Let&#8217;s have a look what actually happens in the above code.</p>



<p>All YAML files are stored in a Git/ Hiera repository, accessible in the sub-directory <code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">cfgmgmt</mark></code>.</p>



<p>The code declares &#8220;<code>local</code>&#8221; variables by issuing the resource &#8220;<code><a rel="noreferrer noopener" href="https://www.terraform.io/language/values/locals" data-type="URL" data-id="https://www.terraform.io/language/values/locals" target="_blank">locals</a></code>&#8220;, starting from <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">line 1</mark>.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 2</mark> will check if a file called <code>cfgmgmt/${var.environment}/hosts/${var.node}.yaml</code> exists and if <code>true</code>, loads the <code>YAML</code> content as an map into the local variable <code>host_cfg</code>. If the file doesn&#8217;t exists, a default <code>YAML</code> code will be loaded. In theory, each node/ host must have a file defined as it should have at least data configuration such as:</p>



<ul class="wp-block-list"><li>unique node host name</li><li>IP address</li><li>server role</li><li>(optionally) VLAN/ subnet configuration</li><li>&#8230;</li></ul>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 4</mark> splits the server role string, stored in <code>local.host_cfg.server_role</code>, into a list, to build the server role hierarchy further below.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 5 </mark>creates a list of top level server roles which need to be imported too. Example: if the server role was set to <code>debian::databases::postgres::timescale::prometheus</code> , the list <code>all_roles_list</code> will contain the following elements:</p>



<ul class="wp-block-list"><li><code>debian</code></li><li><code>debian::databases</code></li><li><code>debian::databases::postgres</code></li><li><code>debian::databases::postgres::timescale</code></li><li><code>debian::databases::postgres::timescale::prometheus</code></li></ul>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 7</mark> loads the YAML content of <code>common.yaml</code>, if it exists.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 8</mark> loops over the <code>all_roles_list</code> elements, created on <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">line 5</mark>, and will load the YAML content of the server roles (if the file exists) into a list element. The result is a list called <code>common_role_cfg_list</code>.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 11</mark> loads the general environment configuration YAML content (if it exists) into the local variable <code>env_cfg</code>.</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Line 12 </mark>will do the same thing as<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color"> line 8</mark>, but for environment specific roles. (for instance: when certain server roles have environment specific configuration parameters).</p>



<p><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Lines 15</mark> and <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">16</mark> will merge the elements (which in theory are <code><a rel="noreferrer noopener" href="https://www.terraform.io/language/values/variables#map" data-type="URL" data-id="https://www.terraform.io/language/values/variables#map" target="_blank">maps</a></code> of YAML data) of the server role lists into one big map, in the order of the list. This allows that keys can be overridden. The expansion <code>...</code> notation is explained at <a rel="noreferrer noopener" href="https://www.terraform.io/language/expressions/function-calls#expanding-function-arguments" target="_blank">https://www.terraform.io/language/expressions/function-calls#expanding-function-arguments</a>.</p>



<p>Finally on <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">line 18</mark>, a local variable called <code>cfg</code> will be created, which merges the values of:</p>



<ul class="wp-block-list"><li><code>local.common_cfg</code></li><li><code>local.env_cfg</code></li><li><code>local.common_role_cfg_map</code></li><li><code>local.env_role_cfg_map</code></li><li><code>local.host_cfg</code></li></ul>



<p>By providing the environment name and the host/ node name to the above code (as <code>var.environment</code> and <code>var.node</code> ), all required configuration parameters can be loaded per node in Terraform, but since we&#8217;ve used a Git repository, this information can be loaded in any kind of automation tool (required is of course that each automation implements the same kind of hierarchy code).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/04/import-configuration-from-hiera-or-a-git-repository-with-yaml-files-into-terraform/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>IPTables Logging in JSON with NFLOG and ulogd2</title>
		<link>https://jmorano.moretrix.com/2022/03/logging-in-iptables-with-nflog-and-ulogd2/</link>
					<comments>https://jmorano.moretrix.com/2022/03/logging-in-iptables-with-nflog-and-ulogd2/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Thu, 31 Mar 2022 08:00:00 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[IPTables]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[nflog]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<category><![CDATA[ulogd2]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1308</guid>

					<description><![CDATA[Logging with IPTables requires the use of an extra IPTables extension called NFLOG (https://manpages.debian.org/experimental/iptables/iptables-extensions.8.en.html#NFLOG) and a separate daemon&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Logging with IPTables requires the use of an extra IPTables extension called <code>NFLOG</code> (<a rel="noreferrer noopener" href="https://manpages.debian.org/experimental/iptables/iptables-extensions.8.en.html#NFLOG" target="_blank">https://manpages.debian.org/experimental/iptables/iptables-extensions.8.en.html#NFLOG</a>) and a separate daemon process, called <code>ulogd2</code> (<a rel="noreferrer noopener" href="https://www.netfilter.org/projects/ulogd/index.html" target="_blank">https://www.netfilter.org/projects/ulogd/index.html</a>). Ulogd2 reads out the packets sent to the above mentioned extension and stores them in local files or databases.</p>



<p>First, install the <code>ulogd2</code> package (example is based on Debian/ Ubuntu):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">apt install ulogd2
</pre>



<p>Example: log and drop packets which have an invalid state</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># Log and drop all invalid packets                                                                                                                                                                                         
iptables -A INPUT -m conntrack --ctstate INVALID -j NFLOG --nflog-group 123 --nflog-prefix "packet with invalid state"
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
</pre>



<p>To log all those packets to a file in JSON format, <code>ulogd2</code> requires the following configuration at <code>/etc/ulogd.conf</code></p>



<pre class="EnlighterJSRAW" data-enlighter-language="ini" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="true" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[global]                                                                                                                                                                                                           
logfile="syslog"
loglevel=3                                                                                                                                                                                                                   
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_inppkt_NFLOG.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_IFINDEX.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_IP2STR.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_IP2BIN.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_PRINTPKT.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_HWHDR.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_raw2packet_BASE.so"
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_output_JSON.so"

stack=log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,mac2str1:HWHDR,json1:JSON

[log1]
group=123

[json1]
sync=1
file="/var/log/ulog/netfilter_log.json"</pre>



<p>After creating the configuration file, ensure that <code>ulogd2</code> is restarted and that the directory <code>/var/log/ulog</code> exists</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">mkdir /var/log/ulog
chown ulog /var/log/ulog
systemctl restart ulogd2.service</pre>



<p>Once the above created rule matches, a JSON log line will be written to disk:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">tail -1 /var/log/ulog/netfilter_log.json | jq
{
  "timestamp": "2022-03-30T14:46:20.527282+0200",
  "dvc": "Netfilter",
  "raw.pktlen": 52,
  "raw.pktcount": 1,
  "oob.prefix": "packet with invalid state",
  "oob.time.sec": 1648644380,
  "oob.time.usec": 527282,
  "oob.mark": 0,
  "oob.ifindex_in": 2,
  "oob.hook": 1,
  "raw.mac_len": 14,
  "oob.family": 2,
  "oob.protocol": 2048,
  "raw.label": 0,
  "raw.type": 1,
  "raw.mac.addrlen": 6,
  "ip.protocol": 6,
  "ip.tos": 0,
  "ip.ttl": 116,
  "ip.totlen": 52,
  "ip.ihl": 5,
  "ip.csum": 41779,
  "ip.id": 16049,
  "ip.fragoff": 16384,
  "src_port": 58662,
  "dest_port": 445,
  "tcp.seq": 3872158206,
  "tcp.ackseq": 0,
  "tcp.window": 8192,
  "tcp.offset": 0,
  "tcp.reserved": 0,
  "tcp.urg": 0,
  "tcp.ack": 0,
  "tcp.psh": 0,
  "tcp.rst": 0,
  "tcp.syn": 1,
  "tcp.fin": 0,
  "tcp.res1": 0,
  "tcp.res2": 0,
  "tcp.csum": 60039,
  "oob.in": "eth0",
  "oob.out": "",
  "src_ip": "181.122.165.177",
  "dest_ip": "1.1.1.1",
  "mac.saddr.str": "94:f7:ad:4f:81:fc",
  "mac.daddr.str": "aa:aa:aa:aa:aa:aa",
  "mac.str": "aa:aa:aa:aa:aa:aa:94:f7:ad:4f:81:fc:08:00"
}</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/03/logging-in-iptables-with-nflog-and-ulogd2/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Terraform and libvirt nodes</title>
		<link>https://jmorano.moretrix.com/2022/03/terraform-and-libvirtd-nodes/</link>
					<comments>https://jmorano.moretrix.com/2022/03/terraform-and-libvirtd-nodes/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Wed, 30 Mar 2022 09:45:11 +0000</pubDate>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Terraform]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Hashicorp]]></category>
		<category><![CDATA[Hetzner]]></category>
		<category><![CDATA[KVM]]></category>
		<category><![CDATA[Libvirt]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Qemu]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1302</guid>

					<description><![CDATA[Libvirt (libvirtd) nodes (based on KVM and Qemu) are a great and cheap (read: free) alternative of deploying&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Libvirt (<a href="https://libvirt.org/manpages/libvirtd.html" data-type="URL" data-id="https://libvirt.org/manpages/libvirtd.html" target="_blank" rel="noreferrer noopener">libvirtd</a>) nodes (based on KVM and Qemu) are a great and cheap (read: free) alternative of deploying virtual nodes in a cloud. Required is a server which will act as a hypervisor, in our article we chose to use a <a href="https://www.hetzner.com/" data-type="URL" data-id="https://www.hetzner.com/" target="_blank" rel="noreferrer noopener">Hetzner</a> server installed with <a href="https://ubuntu.com/" data-type="URL" data-id="https://ubuntu.com/" target="_blank" rel="noreferrer noopener">Ubuntu Linux </a>20.4-lts.</p>



<p>After the default installation of Ubuntu 20.4-lts, the following packages are required to get started as a hypervisor:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">apt install qemu-kvm libvirt-daemon bridge-utils virtinst libvirt-daemon-system virt-top libguestfs-tools libosinfo-bin qemu-system virt-manager qemu pm-utils</pre>



<p>Once these are installed, the <code>vnet_hosts </code>module needs to be pre-loaded in <code>/etc/modules</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">echo vhost_net | tee -a /etc/modules
modprobe vhost_net</pre>



<p>The hypervisor is now ready to start creating and deploying virtual machines. </p>



<p>In this article, <a rel="noreferrer noopener" href="https://www.terraform.io/" data-type="URL" data-id="https://www.terraform.io/" target="_blank">Terraform</a> will be used to manage the virtual machines in libvirtd. All example code snippets are available on Github, under <a href="https://github.com/insani4c/terraform-libvirt" target="_blank" rel="noreferrer noopener">https://github.com/insani4c/terraform-libvirt</a></p>



<p>Terraform has an excellent provider (<a href="https://registry.terraform.io/providers/dmacvicar/libvirt/latest" data-type="URL" data-id="https://registry.terraform.io/providers/dmacvicar/libvirt/latest" target="_blank" rel="noreferrer noopener">dmacvicar/libvirtd</a>) to manage the libvirtd nodes, which needs to be loaded and initialized:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">terraform {
  required_providers {
    libvirt = {
      source = "dmacvicar/libvirt"
    }
  }
}

provider "libvirt" {
  uri = "qemu+ssh://root@192.168.1.1/system"
}</pre>



<p>In the above code snippet, Terraform will ensure the libvirt provider is loaded and it is configured to connect to the host with IP address <code>192.168.1.1</code> as the root user (this requires that a SSH public key is installed on the remote server, of the user who will be executing Terraform).</p>



<p>Next, the network will defined, where the virtual nodes will be deployed in.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">resource "libvirt_network" "my_network" {
  name = "my_net"

  mode = "nat"

  addresses = ["10.1.2.0/24"]
  domain    = var.dns_domain

  autostart = true

  dhcp {
    enabled = false
  }

  dns {
    enabled = true

    local_only = false
    forwarders { address = "127.0.0.53" }

    hosts  {
        hostname = "host01"
        ip = "10.1.2.10"
      }
    hosts {
        hostname = "host02"
        ip = "10.1.2.20"
      }
  }  
}</pre>



<p>The above code will ensure that a network of type <code>NAT</code> (to allow internal IPs, reachable from the hypervisor only) with network mask <code>10.1.2.0/24</code>, will be created. It will ensure that <code>DHCP</code> is disabled and it will enable a <code>DNS</code> setup (the package <code>dnsmasq</code> must be installed) with two predefined hosts, <code>host01</code> and <code>host02</code>.</p>



<p>Up next is the definition of the storage pools and volumes required for the virtual machines.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">resource "libvirt_pool" "default" {
  name = "default"
  type = "dir"
  path = "/data/vms/cluster_storage"
}

resource "libvirt_volume" "local_install_image" {
  name   = var.local_install_image
  pool   = libvirt_pool.default.name
  source = var.os_img_url
  format = "qcow2"
}</pre>



<p>The above defines the <code>libvirt_pool</code>, which basically configures the path on-disk for storing all sorts of volumes. Next it defines a volume called &#8220;<code>local_install_image</code>&#8220;, which will be used to set up the virtual machine as the volume will contain the &#8220;cloud image&#8221; for the installation. This volume requires two variables:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">variable "os_img_url" {
  description = "URL to the OS image"
  default     = "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img"
}

variable "local_install_image" {
    description = "The name of the local install image"
    default     = "base-os-ubuntu-focal.qcow2"
}</pre>


]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/03/terraform-and-libvirtd-nodes/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Block countries using IPtables and IPDeny.com</title>
		<link>https://jmorano.moretrix.com/2022/03/block-countries-using-iptables/</link>
					<comments>https://jmorano.moretrix.com/2022/03/block-countries-using-iptables/#comments</comments>
		
		<dc:creator><![CDATA[Johnny Morano]]></dc:creator>
		<pubDate>Tue, 01 Mar 2022 07:12:28 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[IPTables]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SysAdmin]]></category>
		<guid isPermaLink="false">https://jmorano.moretrix.com/?p=1285</guid>

					<description><![CDATA[Certain server setups do not require access for all countries or just want to block certain countries since&#8230;]]></description>
										<content:encoded><![CDATA[
<p>Certain server setups do not require access for all countries or just want to block certain countries since they are know for their malicious activity.</p>



<p>One simple (not full bullet-proof) way of doing this, is by setting up block rules on firewall level, which can be achieved on Linux servers with <code>iptables</code> and zone files of <a rel="noreferrer noopener" href="https://www.ipdeny.com/" target="_blank">https://www.ipdeny.com/</a>. These zone files contain the network ranges assigned to a specific country.</p>



<p>The network ranges are fed to a tool called <code>ipset</code>, which sets up of hash map that can be easily used within <code>iptables</code> rules.</p>



<p>On Debian/Ubuntu systems, <code>ipset</code> can be installed with the <code>apt</code> command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">apt install ipset</pre>



<p>Next, create an iptables chain called &#8220;<code>blocked_countries</code>&#8220;, to which we will add rules afterwards. Add this chain to the beginning of the <code>INPUT</code> and <code>FORWARD</code> chain, to have early blocking in your ruleset.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">iptables -N blocked_countries
iptables -I INPUT -j blocked_countries -m comment --comment "Blocked countries"
iptables -I FORWARD -j blocked_countries -m comment --comment "Blocked countries"</pre>



<p>Finally, create a shell script which will download the required zone files from <a rel="noreferrer noopener" href="https://ipdeny.com/" target="_blank">https://ipdeny.com/</a> and which feeds them to <code>ipset</code>. The  example script will try to block the countries China, Russia and Belarus:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/bash

COUNTRIES=('cn' 'ru' 'by')

for COUNTRY in "${COUNTRIES[@]}"; do
    ipset create "countries_${COUNTRY}" hash:net
done

iptables -v -F blocked_countries

for i in "${COUNTRIES[@]}"; do
    echo "Ban IP of country ${i}"
    ipset flush "countries_${i}"


    for IP in $(wget -O - https://www.ipdeny.com/ipblocks/data/countries/${i}.zone)
    do
        ipset add "countries_${i}" $IP
    done
    iptables -I blocked_countries   -m set --match-set "countries_${i}" src  -j LOGDROP -m comment   --comment "Block .${i}"
done
﻿</pre>



<p>You can check the <code>blocked_countries</code> chain if packets are being blocked by your new rules:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="monokai" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">iptables -v -n -L blocked_countries 
# Warning: iptables-legacy tables present, use iptables-legacy to see them
Chain blocked_countries (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    2   104 LOGDROP    all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set countries_by src /* Block .by */
 2182  155K LOGDROP    all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set countries_ru src /* Block .ru */
  344 21370 LOGDROP    all  --  *      *       0.0.0.0/0            0.0.0.0/0            match-set countries_cn src /* Block .cn */
﻿</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://jmorano.moretrix.com/2022/03/block-countries-using-iptables/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>
