<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Timofey Chuchkanov</title>
    <description>The latest articles on DEV Community by Timofey Chuchkanov (@crt0r).</description>
    <link>https://dev.to/crt0r</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F820669%2Ffb71496c-e59d-4353-8da6-4964a0ef430c.jpg</url>
      <title>DEV Community: Timofey Chuchkanov</title>
      <link>https://dev.to/crt0r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crt0r"/>
    <language>en</language>
    <item>
      <title>Encrypted Remote Terraform State with Postgres and LUKS</title>
      <dc:creator>Timofey Chuchkanov</dc:creator>
      <pubDate>Wed, 03 Jan 2024 12:33:07 +0000</pubDate>
      <link>https://dev.to/crt0r/encrypted-remote-terraform-state-with-postgres-and-luks-1mcc</link>
      <guid>https://dev.to/crt0r/encrypted-remote-terraform-state-with-postgres-and-luks-1mcc</guid>
      <description>&lt;p&gt;Either if you have already decided to use a PostgreSQL remote state backend for Terraform, or stumbled on this guide out of curiosity, let me help you set everything up in a more secure manner 😁!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*upd 12/26/2024: Dev Community formatting has gone nuts&lt;br&gt;
edit 2024-01-20: Added a tip on workspaces.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why?&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;What Versions of Software are Used?&lt;/li&gt;
&lt;li&gt;
Set Up a Postgres Server

&lt;ul&gt;
&lt;li&gt;Prepare Storage&lt;/li&gt;
&lt;li&gt;Configure Postgres&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Configure a Terraform Client&lt;/li&gt;

&lt;li&gt;What to Do After a Reboot?&lt;/li&gt;

&lt;li&gt;The Result&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;When you work with Terraform by yourself, don't store sensitive data in state, and have a couple of projects, it's OK to use the &lt;a href="https://developer.hashicorp.com/terraform/language/settings/backends/local" rel="noopener noreferrer"&gt;&lt;em&gt;local&lt;/em&gt;&lt;/a&gt; state backend.&lt;/p&gt;

&lt;p&gt;But as soon as the number of projects increases, you start working in a team, and suddenly realize there're things like passwords and API keys in your state file, uhmm... Things start to get tricky 🫠.&lt;/p&gt;

&lt;p&gt;A number of questions arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I protect sensitive data stored in state?&lt;/li&gt;
&lt;li&gt;How can I collaborate with my colleagues? The state must be somehow shared in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's where a remote state backend comes in handy. It can be encrypted, allows to team up on the same projects simultaneously by having a SSOT for state, and supports locking to prevent collisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  But Why PostgreSQL?
&lt;/h3&gt;

&lt;p&gt;It's quicker and easier to set up than other remote state backends. It doesn't require a ton of resources. The thing just works, yet highly flexible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;What you need is a single server and a way to obtain a TLS certificate for it. For me, it's a VM on Proxmox and a self-signed certificate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to use self-signed certs for this, check out my old article on how to generate them via openssl 👇.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/crt0r" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F820669%2Ffb71496c-e59d-4353-8da6-4964a0ef430c.jpg" alt="crt0r"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/crt0r/trusted-self-signed-tls-certificates-for-dummies-w-thorough-explanations-included-da7" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Trusted self-signed TLS certificates for dummies (w/ thorough explanations included)&lt;/h2&gt;
      &lt;h3&gt;Timofey Chuchkanov ・ Nov 25 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#security&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#https&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ssl&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;p&gt;Play around and find the exact amount of resources suitable for your specific case. Maybe less cores and RAM, more storage, etc.&lt;/p&gt;

&lt;p&gt;Ubuntu was chosen as one of the most popular server distros. The configuration can be adjusted for any OS.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OS&lt;/th&gt;
&lt;th&gt;CPU&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;Disk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu Server&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;8 GB + 1 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;📒 NOTE: For this demo, 1 GB disk is dedicated for a Postgres database cluster. Be aware, in production such disk size may be inappropriately small.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Versions of Software are Used?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu 22.04.3 LTS&lt;/li&gt;
&lt;li&gt;PostgreSQL 14.10&lt;/li&gt;
&lt;li&gt;cryptsetup 2.4.3&lt;/li&gt;
&lt;li&gt;Terraform v1.5.7 (on a separate host)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  What is LUKS, btw 🤔?
  &lt;br&gt;
Check out &lt;em&gt;cryptsetup&lt;/em&gt;'s README to learn more: &lt;a href="https://gitlab.com/cryptsetup/cryptsetup/" rel="noopener noreferrer"&gt;https://gitlab.com/cryptsetup/cryptsetup/&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up a Postgres Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prepare Storage
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create a LUKS Device
&lt;/h4&gt;

&lt;p&gt;First, we need to find out our disk's block device name. In my case, it's &lt;em&gt;/dev/sdb&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;lsblk
&lt;span class="go"&gt;NAME    MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0     7:0    0  63.5M  1 loop /snap/core20/2015
loop1     7:1    0 111.9M  1 loop /snap/lxd/24322
loop2     7:2    0  40.8M  1 loop /snap/snapd/20092
sda       8:0    0     8G  0 disk 
├─sda1    8:1    0   7.9G  0 part /
├─sda14   8:14   0     4M  0 part 
└─sda15   8:15   0   106M  0 part /boot/efi
sdb       8:16   0     1G  0 disk 
sr0      11:0    1     4M  0 rom  
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, create a new partition table and a partition on this block device.&lt;/p&gt;

&lt;p&gt;I prefer to use GPT instead of MBR even for smaller disks most of the time.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fdisk /dev/sdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  fdisk example
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Welcome to fdisk (util-linux 2.37.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x20d6b116.

Command (m for help): g
Created a new GPT disklabel (GUID: 0B0CD5AB-337A-CF47-A2BF-F377A158D0FE).

Command (m for help): n
Partition number (1-128, default 1): 
First sector (2048-2097118, default 2048): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-2097118, default 2097118): 

Created a new partition 1 of type 'Linux filesystem' and of size 1023 MiB.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;When the partition is created, LUKS can be initialized using &lt;em&gt;cryptsetup&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Considering that block device naming in Linux is not persistent between reboots, we won't rely on device names. Instead, we'll use labels for both a LUKS partition and a filesystem on it later.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cryptsetup luksFormat /dev/sdb1 &lt;span class="nt"&gt;--type&lt;/span&gt; luks2 &lt;span class="nt"&gt;--label&lt;/span&gt; tfstate-pg-luks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;👆 LUKS version 2 is used because it supports labels. Use any label of your liking. Don't forget to adjust all the commands and configurations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Configure crypttab and fstab
&lt;/h4&gt;

&lt;p&gt;Opening an encrypted disk partition requires providing a password or a key file. We could automate this process, but as always, there's a trade-off between security and convenience.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;/etc/crypttab&lt;/em&gt; and &lt;em&gt;/etc/fstab&lt;/em&gt; — these two files can greatly simplify the experience of opening and mounting an encrypted disk manually after reboots.&lt;/p&gt;
&lt;h5&gt;
  
  
  crypttab
&lt;/h5&gt;

&lt;p&gt;Open &lt;em&gt;/etc/crypttab&lt;/em&gt; with your editor of choice and add the line below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tfstate-pg-luks LABEL=tfstate-pg-luks   none    luks,noauto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;📒 NOTE: 👆 The &lt;code&gt;noauto&lt;/code&gt; option prevents automatic opening and mapping of the partition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;
  
  
  fstab
&lt;/h5&gt;

&lt;p&gt;Do the same thing with &lt;em&gt;/etc/fstab&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📒 NOTE: Don't forget to change the filesystem name here if you don't use Btrfs 👇.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can use a single mount point, and mount the device straight to &lt;em&gt;/var/lib/postgres&lt;/em&gt;. It's all a matter of preference. I like my devices to be mounted under &lt;em&gt;/media&lt;/em&gt;, and then bind-mounted to other places.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LABEL=tfstate-pg        /media/tfstate-pg       btrfs   defaults,noauto 0       0
/media/tfstate-pg       /var/lib/postgres       none    bind,noauto     0       0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create a Filesystem
&lt;/h4&gt;

&lt;p&gt;Choose whatever filesystem you're comfortable with. Don't forget to update &lt;em&gt;fstab&lt;/em&gt; accordingly.&lt;/p&gt;

&lt;p&gt;To create a filesystem on the encrypted disk partition, connect to it first.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cryptdisks_start tfstate-pg-luks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Create the filesystem itself with the label specified in &lt;em&gt;fstab&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkfs.btrfs &lt;span class="nt"&gt;--label&lt;/span&gt; tfstate-pg 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Mount and Prepare the Filesystem
&lt;/h4&gt;

&lt;p&gt;At this point, we're halfway there.&lt;/p&gt;

&lt;p&gt;Create directories for the mount points specified in &lt;em&gt;/etc/fstab&lt;/em&gt;, and mount the LUKS-encrypted partition.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /media/tfstate-pg /var/lib/postgres
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mount /media/tfstate-pg/
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mount /var/lib/postgres/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To check if everything is appropriately mounted, issue &lt;em&gt;findmnt&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;findmnt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  Example output
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;findmnt
&lt;span class="go"&gt;***OUTPUT TRUNCATED***
├─/var/lib/postgres                           /dev/mapper/tfstate-pg-luks btrfs       rw,relatime,space_cache=v2,subvolid=5,subvol=/
└─/media/tfstate-pg                           /dev/mapper/tfstate-pg-luks btrfs       rw,relatime,space_cache=v2,subvolid=5,subvol=/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;The directory where the database cluster will be located must be owned by the &lt;em&gt;postgres&lt;/em&gt; user and group.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; postgres: /var/lib/postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Configure Postgres
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Install
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Disable and Stop the Service
&lt;/h4&gt;

&lt;p&gt;Since the database cluster will be located on the encrypted partition that must be manually connected after a reboot, the Postgres service should be disabled.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;💡 TIP: 👆 The &lt;code&gt;--now&lt;/code&gt; option stops the daemon.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Copy a TLS Certificate and Key
&lt;/h4&gt;

&lt;p&gt;Encrypted storage is useless if the network traffic between the server and its remote clients is not encrypted.&lt;/p&gt;

&lt;p&gt;Grab a TLS certificate and its key, and copy them to your server. You can place them wherever you want — filenames don't matter either, as long as the &lt;em&gt;postgres&lt;/em&gt; user has access.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;deathroll-internal.pem /etc/ssl/certs/
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;deathroll-internal-key.pem /etc/ssl/private/
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;postgres: /etc/ssl/private/deathroll-internal-key.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  File permissions
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;--printf&lt;/span&gt; &lt;span class="s1"&gt;'%n\nMode: %a (%A)\nOwner: %u (%U)\nGroup: %g (%G)\n'&lt;/span&gt; /etc/ssl/certs/deathroll-internal.pem
&lt;span class="go"&gt;/etc/ssl/certs/deathroll-internal.pem
Mode: 644 (-rw-r--r--)
Owner: 1000 (t_chuchkanov)
Group: 1000 (t_chuchkanov)
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;--printf&lt;/span&gt; &lt;span class="s1"&gt;'%n\nMode: %a (%A)\nOwner: %u (%U)\nGroup: %g (%G)\n'&lt;/span&gt; /etc/ssl/private/deathroll-internal-key.pem
&lt;span class="go"&gt;/etc/ssl/private/deathroll-internal-key.pem
Mode: 600 (-rw-------)
Owner: 113 (postgres)
Group: 122 (postgres)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;
&lt;h4&gt;
  
  
  Configure
&lt;/h4&gt;
&lt;h5&gt;
  
  
  Settings
&lt;/h5&gt;

&lt;p&gt;Instead of editing the config file manually, it's easier and quicker to use &lt;em&gt;pg_conftool&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pg_conftool &lt;span class="nb"&gt;set &lt;/span&gt;ssl_cert_file /etc/ssl/certs/deathroll-internal.pem
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pg_conftool &lt;span class="nb"&gt;set &lt;/span&gt;ssl_key_file /etc/ssl/private/deathroll-internal-key.pem
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pg_conftool &lt;span class="nb"&gt;set &lt;/span&gt;listen_addresses localhost,tfstate-pg.deathroll.internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To check if all the values are correct:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;P &lt;span class="k"&gt;in &lt;/span&gt;listen_addresses ssl_cert_file ssl_key_file&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;pg_conftool show &lt;span class="nv"&gt;$P&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h5&gt;
  
  
  Access Control
&lt;/h5&gt;

&lt;p&gt;Configure access control for your Postgres installation. Append the line below to the &lt;em&gt;/etc/postgresql/14/main/pg_hba.conf&lt;/em&gt; file:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📒 NOTE: 👆 Pay attention to the Postgres version. It may differ in your case.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hostssl all             all             172.17.0.0/24            scram-sha-256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;📒 NOTE: 👆 Change the allowed network to whatever you need. It may even be &lt;code&gt;0.0.0.0/0&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Start
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl start postgresql.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If your configuration is correct, the Postgres systemd unit must be in the &lt;em&gt;running&lt;/em&gt; state, the Postgres cluster &lt;em&gt;online&lt;/em&gt;, and the daemon listening on the port &lt;em&gt;5432&lt;/em&gt;. Congrats 🥳.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl list-units postgres&lt;span class="k"&gt;*&lt;/span&gt;.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  Example
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl list-units postgres&lt;span class="k"&gt;*&lt;/span&gt;.service
&lt;span class="go"&gt;  UNIT                       LOAD   ACTIVE SUB     DESCRIPTION               
  postgresql.service         loaded active exited  PostgreSQL RDBMS
  postgresql@14-main.service loaded active running PostgreSQL Cluster 14-main

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.
2 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pg_lsclusters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  Example
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pg_lsclusters
&lt;span class="go"&gt;Ver Cluster Port Status Owner    Data directory              Log file
14  main    5432 online postgres /var/lib/postgresql/14/main /var/log/postgresql/postgresql-14-main.log
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ss &lt;span class="nt"&gt;-tlpn&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  Example
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ss &lt;span class="nt"&gt;-tlpn&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;postgres
&lt;span class="go"&gt;LISTEN 0      244      172.17.0.40:5432      0.0.0.0:*    users:(("postgres",pid=19728,fd=7))       
LISTEN 0      244        127.0.0.1:5432      0.0.0.0:*    users:(("postgres",pid=19728,fd=6))       
LISTEN 0      244            [::1]:5432         [::]:*    users:(("postgres",pid=19728,fd=5))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;Almost there! The last few steps of Postgres configuration are left.&lt;/p&gt;
&lt;h4&gt;
  
  
  Configure a Database
&lt;/h4&gt;

&lt;p&gt;Now we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the &lt;em&gt;postgres&lt;/em&gt; user password&lt;/li&gt;
&lt;li&gt;Create a Postgres user for Terraform clients&lt;/li&gt;
&lt;li&gt;Create a database for Terraform state&lt;/li&gt;
&lt;li&gt;Grant some privileges on this database to the Terraform user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's roll!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;📒 NOTE: 👆 Depending on your &lt;em&gt;$PWD&lt;/em&gt;, psql may throw a warning about its inability to access this directory. Ignore it.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;postgres=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ALTER USER postgres ENCRYPTED PASSWORD &lt;span class="s1"&gt;'******'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="gp"&gt;postgres=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;CREATE USER terraform ENCRYPTED PASSWORD &lt;span class="s1"&gt;'******'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="gp"&gt;postgres=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;CREATE DATABASE terraform_backend&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="gp"&gt;postgres=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;GRANT CREATE,CONNECT,TEMPORARY ON DATABASE terraform_backend TO terraform&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  Example
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxk9ce7l8ebt2i5r89eu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxk9ce7l8ebt2i5r89eu6.png" alt="Postgres shell example" width="800" height="366"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;
&lt;h4&gt;
  
  
  Configure a Firewall
&lt;/h4&gt;

&lt;p&gt;Don't forget to configure a firewall on your system.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ufw allow 22,5432/tcp
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To check the status:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it! It's time to test the connection from another host and configure a Terraform client 😎.&lt;/p&gt;
&lt;h4&gt;
  
  
  Check Connection from Another Host
&lt;/h4&gt;

&lt;p&gt;Install &lt;em&gt;psql&lt;/em&gt; and issue the command below:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;psql postgres://terraform@tfstate-pg.deathroll.internal/terraform_backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;💡 TIP: The connection string scheme is: &lt;code&gt;postgres://&amp;lt;user&amp;gt;@&amp;lt;host&amp;gt;/&amp;lt;database_name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 TIP: On Ubuntu, the package is called "postgresql-client".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can quit out of the psql client prompt right after it establishes the connection, because there's nothing to do in it apart from testing connectivity.&lt;/p&gt;

&lt;p&gt;
  Example
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;psql postgres://terraform@tfstate-pg.deathroll.internal/terraform_backend
&lt;span class="go"&gt;Password for user terraform: 
psql (15.5 (Debian 15.5-0+deb12u1), server 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.

&lt;/span&gt;&lt;span class="gp"&gt;terraform_backend=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;n
&lt;span class="go"&gt;  List of schemas
  Name  |  Owner   
--------+----------
 public | postgres
(1 row)

&lt;/span&gt;&lt;span class="gp"&gt;terraform_backend=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;
&lt;h2&gt;
  
  
  Configure a Terraform Client
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;💡TIP: When you need to work with multiple terraform projects, use &lt;a href="https://developer.hashicorp.com/terraform/cli/workspaces" rel="noopener noreferrer"&gt;workspaces&lt;/a&gt;, since all of the freshly initialized projects have only the &lt;em&gt;default&lt;/em&gt; workspace. Hence, there will be remote state conflicts, if a single workspace is used for several independent states.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a Terraform project's main file, create a &lt;code&gt;backend&lt;/code&gt; block inside of &lt;code&gt;terraform&lt;/code&gt;. Provide a &lt;em&gt;string&lt;/em&gt; value for &lt;em&gt;conn_str&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It is really &lt;a href="https://developer.hashicorp.com/terraform/language/settings/backends/pg#example-configuration" rel="noopener noreferrer"&gt;that simple&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"pg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;conn_str&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres://terraform@tfstate-pg.deathroll.internal/terraform_backend"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Before issuing any Terraform commands, export the Postgres password as an environment variable:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; PGPASSWORD
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;PGPASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For a fresh project, &lt;code&gt;terraform init&lt;/code&gt; is sufficient. For an existing project, add the &lt;code&gt;-reconfigure&lt;/code&gt; flag to this command.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform init
&lt;span class="go"&gt;
Initializing the backend...

Successfully configured the backend "pg"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding telmate/proxmox versions matching "2.9.11"...
- Installing telmate/proxmox v2.9.11...
- Installed telmate/proxmox v2.9.11 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

╷
│ Warning: Incomplete lock file information for providers
│ 
│ Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers:
│   - telmate/proxmox
│ 
│ The current .terraform.lock.hcl file only includes checksums for linux_amd64, so Terraform running on another platform will fail to install these providers.
│ 
│ To calculate additional checksums for another platform, run:
│   terraform providers lock -platform=linux_amd64
│ (where linux_amd64 is the platform to generate)
╵

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;If you want to learn how to use Terraform providers in isolated environments, I have a yet another article for you 😉.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/crt0r" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F820669%2Ffb71496c-e59d-4353-8da6-4964a0ef430c.jpg" alt="crt0r"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/crt0r/using-terraform-providers-in-isolation-17n0" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Using Terraform Providers in Isolation&lt;/h2&gt;
      &lt;h3&gt;Timofey Chuchkanov ・ Aug 6 '23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#opensource&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#terraform&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;br&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What to Do After a Reboot?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open and map the LUKS-encrypted partition&lt;/li&gt;
&lt;li&gt;Mount its filesystem&lt;/li&gt;
&lt;li&gt;Start the Postgres service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As simple as three to four commands!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fox23q4ey42zzcs3eynv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fox23q4ey42zzcs3eynv1.png" alt="A set of commands to do after a reboot" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;As the result, we have a PostgreSQL server with a database cluster located on a LUKS-encrypted partition.&lt;/p&gt;

&lt;p&gt;This Postgres installation is used for storing remote Terraform state, and the network traffic between it and its remote clients is encrypted.&lt;/p&gt;

&lt;p&gt;Sounds great, innit 🤌?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6tdm79o9p8du0df3ob4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6tdm79o9p8du0df3ob4.png" alt="A drawn diagram representing the resulting architecture" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Scroll down to the comments section to get some enhancement tips.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>postgres</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Using Terraform Providers in Isolation</title>
      <dc:creator>Timofey Chuchkanov</dc:creator>
      <pubDate>Sun, 06 Aug 2023 07:54:32 +0000</pubDate>
      <link>https://dev.to/crt0r/using-terraform-providers-in-isolation-17n0</link>
      <guid>https://dev.to/crt0r/using-terraform-providers-in-isolation-17n0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;*upd 12/26/2024: Dev Community formatting has gone nuts&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Is This Guide for Me?
&lt;/h2&gt;

&lt;p&gt;If you need to use Terraform providers in an isolated environment, or in case you don't have access to the Terraform Registry, look no further.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;Telmate/proxmox&lt;/code&gt; provider is used as an example in this guide. Configurations are performed on a Linux distribution, but can be applied anywhere with slight modifications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
&lt;p&gt;Quick tip: To install Terraform CLI on Linux or Mac without hassle, check out &lt;a href="https://brew.sh" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Find the Source Code&lt;/li&gt;
&lt;li&gt;Get the Binaries&lt;/li&gt;
&lt;li&gt;Place the Binaries Somewhere Locally&lt;/li&gt;
&lt;li&gt;Configure the Terraform CLI&lt;/li&gt;
&lt;li&gt;Test It Out!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Find the Source Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1. Go to GitHub and Search for Providers There
&lt;/h3&gt;

&lt;p&gt;By convention, all providers' names start with the &lt;code&gt;terraform-provider-&lt;/code&gt; prefix. Try typing this prefix in the search bar in combination with the name of the target provider — no hyphens needed. For example, &lt;code&gt;terraform provider proxmox&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There's a high chance the first link will be a good choice. Also, I'd look for a greater number of stars. Most of the time, popular projects have lots of them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwl8tkryrx44qixbfm1ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwl8tkryrx44qixbfm1ob.png" alt="Using GitHub search capabilities to find a Terraform provider" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2. Search the Registry Beforehand or Ask Someone to Do This for You
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Yeah, I know. That's kind of obvious.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just type the name of the target provider in the search bar and pick one. Each provider's main page has a link to the source code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxv68ctron4ucy5djhr2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxv68ctron4ucy5djhr2o.png" alt="Using the Terraform Registry search bar to find a provider" width="544" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wyp2uxg0x1yuowmp7qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wyp2uxg0x1yuowmp7qx.png" alt="Locating the source code repo link" width="667" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the Binaries
&lt;/h2&gt;

&lt;p&gt;Many providers' devs are kind enough to provide archives with binaries compiled for various platforms. They are located on the &lt;code&gt;Releases&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;
  But Not Always
  &lt;blockquote&gt;
&lt;p&gt;That is the case with HashiCorp, of course.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhd4wwk1x2rvzsufcv3m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhd4wwk1x2rvzsufcv3m6.png" alt="Viewing HashiCorp's release assets" width="529" height="210"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;
&lt;br&gt;


&lt;blockquote&gt;
&lt;p&gt;If there're no binaries, then you have to compile the provider yourself. Search for docs in the repo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97a0qxpf26h7b85q3yyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97a0qxpf26h7b85q3yyl.png" alt="Locating the Releases link on a GitHub repo page" width="529" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbu8k2zh4flf54eitn0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbu8k2zh4flf54eitn0s.png" alt="Downloading the archives" width="560" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Place the Binaries Somewhere Locally
&lt;/h2&gt;

&lt;p&gt;Terraform expects a directory structure of one of the two layouts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Packed (.zip archive)&lt;/li&gt;
&lt;li&gt;Unpacked (binaries)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  About the Layouts
  &lt;br&gt;
From the &lt;a href="https://developer.hashicorp.com/terraform/cli/config/config-file#filesystem_mirror" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;:

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Packed layout: &lt;code&gt;HOSTNAME/NAMESPACE/TYPE/terraform-provider-TYPE_VERSION_TARGET.zip&lt;/code&gt; is the distribution zip file obtained from the provider's origin registry.&lt;/li&gt;
&lt;li&gt;Unpacked layout: &lt;code&gt;HOSTNAME/NAMESPACE/TYPE/VERSION/TARGET&lt;/code&gt; is a directory containing the result of extracting the provider's distribution zip file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;



&lt;/p&gt;

&lt;p&gt;
  Packed Layout Visualized
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YOUR.REGISTRY.HOSTNAME/
└── ORGANIZATION-NAME
    └── PROVIDER
        └── terraform-provider-PROVIDER_X.X.X_PLATFORM.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We'll cover the first one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There're no strict rules on where you should place a local mirror on the filesystem. Just make sure the files are accessible by other users if you're not the only one using the system.&lt;/p&gt;

&lt;p&gt;I'm a Linux user, so according to &lt;a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html" rel="noopener noreferrer"&gt;FHS&lt;/a&gt;, &lt;code&gt;/usr/libexec&lt;/code&gt; is a suitable option. There will be a directory for Terraform. Providers will have a separate directory under it — &lt;code&gt;terraform/providers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;
  /usr/libexec summary
  &lt;br&gt;
From the Linux Foundation Referenced Specifications website:

&lt;blockquote&gt;
&lt;p&gt;/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a proper directory layout&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/libexec/terraform/providers/registry.terraform.io/Telmate/proxmox
&lt;/code&gt;&lt;/pre&gt;


&lt;blockquote&gt;
&lt;p&gt;Note: The path before &lt;code&gt;registry.terraform.io&lt;/code&gt; is up to you, but after it the layout must be either of two types mentioned earlier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the archive(s) so the result looks similar to this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;deathroll@hellish:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree /usr/libexec/terraform/
&lt;span class="go"&gt;/usr/libexec/terraform/
└── providers
    └── registry.terraform.io
        └── Telmate
            └── proxmox
                ├── terraform-provider-proxmox_2.9.11_linux_amd64.zip
                └── terraform-provider-proxmox_2.9.14_linux_amd64.zip

5 directories, 2 files
&lt;/span&gt;&lt;span class="gp"&gt;deathroll@hellish:~$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configure the Terraform CLI
&lt;/h2&gt;

&lt;p&gt;According to the &lt;a href="https://developer.hashicorp.com/terraform/cli/config/config-file#locations" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, the config file for the cli utility on Linux is placed in the home directory and is called &lt;code&gt;.terraformrc&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the config file.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Populate it with the &lt;a href="https://developer.hashicorp.com/terraform/cli/config/config-file#explicit-installation-method-configuration" rel="noopener noreferrer"&gt;following content&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure to replace the path with your own.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider_installation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filesystem_mirror&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/usr/libexec/terraform/providers"&lt;/span&gt;
    &lt;span class="nx"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# */* is a shorthand for registry.terraform.io/*/*&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;direct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;blockquote&gt;
&lt;p&gt;Note: The path doesn't include the registry host name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test It Out!
&lt;/h2&gt;

&lt;p&gt;Now you can go to your Terraform project directory and start working on it. Everything should work as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b17ed9o4tekkgfxox0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b17ed9o4tekkgfxox0z.png" alt="Running Terraform without access to the registry" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>terraform</category>
    </item>
    <item>
      <title>A Linux Geek Tried PowerShell — It Feels *Power*ful Indeed</title>
      <dc:creator>Timofey Chuchkanov</dc:creator>
      <pubDate>Sun, 14 May 2023 10:47:19 +0000</pubDate>
      <link>https://dev.to/crt0r/a-linux-geek-tried-powershell-it-feels-powerful-indeed-1jl4</link>
      <guid>https://dev.to/crt0r/a-linux-geek-tried-powershell-it-feels-powerful-indeed-1jl4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article represents my personal opinion and is meant mostly for Linux (and other Unix-like) users, but feel free to join anyways ;)&lt;/p&gt;

&lt;p&gt;*upd 12/26/2024: Dev Community formatting has gone nuts&lt;br&gt;
Update 2023-05-17: Changed the parameters examples formatting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Preamble&lt;/li&gt;
&lt;li&gt;
The Good Stuff

&lt;ul&gt;
&lt;li&gt;Working With Types&lt;/li&gt;
&lt;li&gt;Object-oriented Approach&lt;/li&gt;
&lt;li&gt;Standard Cmdlet Names&lt;/li&gt;
&lt;li&gt;Script and Function Parameters&lt;/li&gt;
&lt;li&gt;Full Integration With the Underlying Platform (.NET)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preamble
&lt;/h2&gt;

&lt;p&gt;I'm on the finish line to get a college degree as a Network and System Administrator. Even though I use Unix-like operating systems for more than five years and want to specialize in them — especially those based on the Linux kernel — the enterprise still runs on Windows partially. That's why even we, the Unix(-like) enthusiasts, still may have to learn it to some extent.&lt;/p&gt;

&lt;p&gt;Less than a week ago, I was preparing for one of the last exams of the last semester. As a part of an assignment, an administrator is to create multiple AD OUs and populate them with a defined number of users. Each user is a member of a security group with a name similar to the OU's. Since the number of users for each OU is somewhat huge, up to 30, the most productive way to create them is via a script. A PowerShell script. It takes less than five minutes to write such a script but can save a lot of time and prevent headaches in the future. In a real-world scenario, this script could be placed in a version control system repository.&lt;/p&gt;

&lt;p&gt;While I was writing the script, I learned a bit about PowerShell and liked how some things are approached. The Unix-like systems' communities would benefit from borrowing some techniques or even using the PowerShell itself, I think. Don't get me wrong, I know who's behind this project, and not trying to convince anyone to abandon their beloved Unix shells ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good Stuff
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Working With Types
&lt;/h3&gt;

&lt;p&gt;Oh boy, it is so satisfying when a shell enables you to work with types like most of the modern programming languages do. Type casting? Check. Type-specific auto-completion? Here you go. Explicit type declarations? Check.&lt;/p&gt;

&lt;p&gt;While using this shell, one can nearly stop worrying about weird behavior when working with data of mixed types. For example, collections like arrays and lists may contain objects of one specific type or multiple types.&lt;/p&gt;

&lt;p&gt;
  Unambiguous Types
  &lt;br&gt;
A String is a String, a Double is a Double, and so on...&lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Math]::PI.GetType&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Double                                   System.ValueType

&lt;/span&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;665&lt;span class="si"&gt;)&lt;/span&gt;.GetType&lt;span class="o"&gt;()&lt;/span&gt; 
&lt;span class="go"&gt;
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

&lt;/span&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  Type Casting
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Int] 14.15&lt;span class="si"&gt;)&lt;/span&gt;          
&lt;span class="go"&gt;14
&lt;/span&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;String] 14&lt;span class="si"&gt;)&lt;/span&gt;.GetType&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

&lt;/span&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  Type-Specific Auto-Completion
  &lt;br&gt;
It also prints the last command containing the input string. It can be inserted by pressing the right arrow key (&lt;code&gt;→&lt;/code&gt;).

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1rsu9x3u73rg6h9eu8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1rsu9x3u73rg6h9eu8f.png" alt="PowerShell auto-completion with type-based suggestions" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Yes, what you see is indeed PowerShell on a Linux distribution.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;
&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Object-oriented Approach
&lt;/h2&gt;

&lt;p&gt;Processing complex data is not a problem anymore when you can work with types and utilize one of the most widely used programming paradigms. One can use tons of predefined types from the .NET platform or create their own.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Everything is heavily documented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/" rel="noopener noreferrer"&gt;.NET API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/powershell/" rel="noopener noreferrer"&gt;PowerShell Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's a quick example. We have created a custom class &lt;code&gt;User&lt;/code&gt; that has a &lt;code&gt;Name&lt;/code&gt;, an &lt;code&gt;Age&lt;/code&gt;, and a list of &lt;code&gt;Interests&lt;/code&gt;. Now we can create objects of this type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS /home/deathroll&amp;gt; class User {                                     
&amp;gt;&amp;gt;     [String] $Name                                  
&amp;gt;&amp;gt;     [Int] $Age                                                  
&amp;gt;&amp;gt;     [System.Collections.Generic.List[String]] $Interests
&amp;gt;&amp;gt; 
&amp;gt;&amp;gt;     User($Name, $Age, $Interests) { $this.Name = $Name; $this.Age = $Age; $this.Interests = $Interests }
&amp;gt;&amp;gt; }
PS /home/deathroll&amp;gt; [User[]] $ExampleUsers = @(                      
&amp;gt;&amp;gt;     [User]::new("Jonh Doe", 47, @("Who knows O_o")),
&amp;gt;&amp;gt;     [User]::new("Jake Smith", 22, @("Programming", "Unix-like"))
&amp;gt;&amp;gt; )
PS /home/deathroll&amp;gt; $($ExampleUsers[1]).Interests.Add("Writing Docs")
PS /home/deathroll&amp;gt; $ExampleUsers                                    

Name       Age Interests
----       --- ---------
Jonh Doe    47 {Who knows O_o}
Jake Smith  22 {Programming, Unix-like, Writing Docs}

PS /home/deathroll&amp;gt; $ExampleUsers[0].GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    User                                     System.Object

PS /home/deathroll&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Do you feel how empowering is this?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Standard Cmdlet Names
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Cmdlets is the PowerShell's way of referring to what we know as "commands."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's a document for PowerShell that defines how cmdlets should be named. According to it, each cmdlet consists of a &lt;code&gt;verb&lt;/code&gt; and a &lt;code&gt;noun&lt;/code&gt;. This makes the names easy to read, understand, and memorize. All of the predefined commands adhere to such a convention.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is it obvious what this command does?&lt;/p&gt;


&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;PS /home/deathroll&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ConvertTo-Json &lt;span class="nt"&gt;-InputObject&lt;/span&gt; &lt;span class="nv"&gt;$ExampleUsers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;
  Output
  
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jonh Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Interests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Who knows O_o"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jake Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Interests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Programming"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Unix-like"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Writing Docs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is just a recommendation, and people don't &lt;em&gt;have&lt;/em&gt; to follow it, but it's better to do so to be consistent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands?view=powershell-7.3" rel="noopener noreferrer"&gt;Approved verbs&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Script and Function Parameters
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Please note that the script below may not be secure or heavily polished. Also, it's not a good idea to generate passwords in such a way, but that was part of an assignment to simplify things a bit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Remember I mentioned a script in the Preamble of this article?&lt;/p&gt;

&lt;p&gt;Here it is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Have you already noticed the &lt;code&gt;Param()&lt;/code&gt; statement? That's how you define parameters for a script or a function. No more &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt; and &lt;code&gt;case...esac&lt;/code&gt;, my friend.&lt;/p&gt;

&lt;p&gt;Just look at this beauty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS /home/deathroll&amp;gt; function Greet-User {
&amp;gt;&amp;gt;     param([Switch] $Informal, [String] $UserName)
&amp;gt;&amp;gt; 
&amp;gt;&amp;gt;     Write-Host $($(If ($Informal) { "Hey" } Else { "Hello" } ) + ", $UserName")
&amp;gt;&amp;gt; } 
PS /home/deathroll&amp;gt; Greet-User $env:USER      
Hello, deathroll
PS /home/deathroll&amp;gt; Greet-User $env:USER -Informal
Hey, deathroll
PS /home/deathroll&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are multiple types of parameters that can be declared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dynamic&lt;/code&gt; (available only in certain circumstances)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;switch&lt;/code&gt; (work like on and off flags)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They can have attributes like &lt;code&gt;Mandatory&lt;/code&gt; (required) and so on.&lt;/p&gt;

&lt;p&gt;And a cherry on top of this is that PowerShell reads parameters defined in a script and can perform their names auto-completion when you type them on the command line.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is an example for PowerShell &amp;gt;=7.0. It contains the &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_if?view=powershell-7.3#using-the-ternary-operator-syntax" rel="noopener noreferrer"&gt;ternary&lt;/a&gt; operator.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS /home/deathroll&amp;gt; Get-Content ./Greet.ps1 # Read the contents of the file, like the `cat` command.           
Param([Switch] $Informal, [String] $UserName)

Write-Host $($($Informal ? "Hey" : "Hello") + ", $UserName")
PS /home/deathroll&amp;gt; ./Greet.ps1 - # Here the TAB key was smashed :D
Informal  UserName  
PS /home/deathroll&amp;gt; ./Greet.ps1 -Informal $env:USER
Hey, deathroll
PS /home/deathroll&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full Integration With the Underlying Platform (.NET)
&lt;/h3&gt;

&lt;p&gt;While everything aforementioned are killer features, this is a killer-killer feature.&lt;/p&gt;

&lt;p&gt;Normally we complement shell scripts with Python, Ruby, JavaScript, or whatever scripting languages if the logic needs to be more complex. But in theory, we could just use PowerShell for all of this and tackle specific tasks by calling the .NET API where needed.  Maybe even extend the shell itself using other .NET languages like C#.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;PowerShell borrows some features from Unix shells: reverse and forward history search, pipes, etc., and adds tons of useful stuff. Why don't we borrow features from it or accept the technology itself where it can make our lives easier? Think about it.&lt;/p&gt;

&lt;p&gt;Meanwhile, I'll discuss with our team if we should use PowerShell to automate things on Linux.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>productivity</category>
      <category>learning</category>
      <category>linux</category>
    </item>
    <item>
      <title>Mastering Bash 0 — Internal Field Separators</title>
      <dc:creator>Timofey Chuchkanov</dc:creator>
      <pubDate>Sun, 23 Apr 2023 09:01:09 +0000</pubDate>
      <link>https://dev.to/crt0r/mastering-bash-0-internal-field-separators-157g</link>
      <guid>https://dev.to/crt0r/mastering-bash-0-internal-field-separators-157g</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Preamble&lt;/li&gt;
&lt;li&gt;
A Bit of Theory

&lt;ul&gt;
&lt;li&gt;Shell Expansions&lt;/li&gt;
&lt;li&gt;Builtins&lt;/li&gt;
&lt;li&gt;Fields&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A Word on Internal Field Separators&lt;/li&gt;

&lt;li&gt;Time for Tuning | Use IFS to Your Advantage&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;*upd 12/26/2024: Dev Community formatting has gone nuts.&lt;br&gt;
*upd 08/06/2025: there's no point in rewriting the manual&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Preamble
&lt;/h2&gt;

&lt;p&gt;Are you a developer, sysadmin, or DevOps engineer? Then you probably spend lots of time on your terminal. I bet you use the GNU Bash shell or something highly similar — take Zsh, for example.&lt;/p&gt;

&lt;p&gt;It's essential to know your tools well to work more efficiently. As a Linux geek, I use Bash to automate my daily routine even when it's not for my job, but tired of constantly searching the Web to learn how to achieve certain things in Bash. So, recently I've read through the whole manual. It's relatively small, a little more than 190 pages in total.&lt;/p&gt;

&lt;p&gt;&lt;del&gt;This article is going to start a series on mastering GNU Bash.&lt;/del&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bit of Theory
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Throwing a bunch of commands at the reader without somewhat detailed explanations is as helpful as teaching a five-year-old how to swim by throwing them in the sea. That's why tutorials should start at least with some general knowledge IMHO.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Shell Expansions
&lt;/h3&gt;

&lt;p&gt;Bash is very flexible when it comes to writing commands. You can always do something a lot simpler and shorter than you would without different types of &lt;code&gt;expansions&lt;/code&gt; Bash performs on the commands it receives from a user.&lt;/p&gt;

&lt;p&gt;In essence, shell expansions enable users to conveniently manipulate data on the command line, such as regular text or variables, using special syntactic constructs. Think of them as shortcuts.&lt;/p&gt;




&lt;p&gt;Currently,  there are seven types of shell expansions supported in Bash:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;brace expansion&lt;/li&gt;
&lt;li&gt;tilde expansion&lt;/li&gt;
&lt;li&gt;parameter and variable expansion&lt;/li&gt;
&lt;li&gt;command substitution&lt;/li&gt;
&lt;li&gt;arithmetic expansion&lt;/li&gt;
&lt;li&gt;word splitting&lt;/li&gt;
&lt;li&gt;filename expansion &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We won't cover all of them since that would take too much time. Besides, there's no point in duplicating the manual.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Here's a short example of a parameter expansion:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_TEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'sOmE tExT Owo'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Parameter expansion — alongside command substitution and arithmetic expansion — is denoted by the "$" symbol.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The command above produces the text below because the shell performs a &lt;code&gt;parameter expansion&lt;/code&gt; for modifying the case of alphabetic characters to lowercase on the given variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;some text owo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You could also provide a &lt;code&gt;pattern&lt;/code&gt; for operating only on specific characters. One character at a time is matched, though, so no whole words and character sequences can be used.&lt;/p&gt;


&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_TEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'sOmE tExT Owo'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,,O&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;somE tExT owo
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_TEXT&lt;/span&gt;&lt;span class="p"&gt;,,[OT]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;somE tExt owo
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Builtins
&lt;/h3&gt;

&lt;p&gt;You may already know this, but not all commands you type on the command line are actual programs found somewhere on a filesystem. Bash has built-in commands called "builtins." And you use them all the time. Probably the most commonly used are &lt;code&gt;echo&lt;/code&gt; and &lt;code&gt;cd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My personal favorite is the &lt;code&gt;declare&lt;/code&gt; builtin. It enables you to declare a variable of a specific type (e.g., array or integer).&lt;/p&gt;

&lt;h3&gt;
  
  
  Fields
&lt;/h3&gt;

&lt;p&gt;Since the topic is about internal field separators, it's also a good idea to touch a bit on what are &lt;code&gt;fields&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how the manual defines &lt;code&gt;field&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A unit of text that is the result of one of the shell expansions. After expansion, when executing a command, the resulting fields are used as the command name and arguments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pretty self-explanatory.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Word on Internal Field Separators
&lt;/h2&gt;

&lt;p&gt;Internal field separators tell Bash how to split a &lt;code&gt;field&lt;/code&gt; into &lt;code&gt;words&lt;/code&gt; — these are just sequences of characters treated as single units.&lt;/p&gt;

&lt;p&gt;The separators are listed in the &lt;code&gt;IFS&lt;/code&gt; variable that contains three symbols by default: &lt;code&gt;space&lt;/code&gt;, &lt;code&gt;tab&lt;/code&gt;, &lt;code&gt;newline&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;— But what does it mean for me as a user?&lt;br&gt;
— Things will get clear with a couple of simple examples.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Suppose you want to get a list of directories and perform some actions on their names. If you search for directories with the &lt;code&gt;find&lt;/code&gt; command, the output will contain multiple lines (i.e., separated by &lt;code&gt;newline&lt;/code&gt; characters).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'.*'&lt;/span&gt;
&lt;span class="go"&gt;./Pictures
./bin
./Videos
./Public
./Music
./Templates
./git
./nvim
./Downloads
./projects
./NAS
./Desktop
./Documents
./snap
./learn
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you substitute the command above so that its output becomes the value of some variable or a part of another command, the shell splits this field into &lt;code&gt;words&lt;/code&gt;, dividing where it finds the &lt;code&gt;newline&lt;/code&gt; character — or any other character listed in the &lt;code&gt;IFS&lt;/code&gt; variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;declare&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;EXAMPLE_ARR&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'.*'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_ARR&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;./Pictures ./bin ./Videos ./Public ./Music ./Templates ./git ./nvim ./Downloads ./projects ./NAS ./Desktop ./Documents ./snap ./learn
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_ARR&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;./Pictures
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora ~]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;OK, that seems to be pretty reasonable. Now I want you to see the commands below and think about the data stored in a variable. What do the array elements look like? More specifically, the first element. Does it look like "&lt;code&gt;2023-02-26 22-10-40.mp4&lt;/code&gt;?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.mp4
&lt;span class="go"&gt;'2023-02-26 22-10-40.mp4'  '2023-03-14 00-22-19.mp4'  '2023-03-31 21-12-39.mp4'  '2023-04-08 13-45-36.mp4'
'2023-02-26 22-16-21.mp4'  '2023-03-24 11-54-46.mp4'  '2023-04-04 10-05-41.mp4'  '2023-04-11 11-53-24.mp4'
'2023-03-12 18-59-36.mp4'  '2023-03-24 11-54-57.mp4'  '2023-04-04 11-37-05.mp4'  '2023-04-11 12-00-41.mp4'
'2023-03-14 00-10-02.mp4'  '2023-03-24 11-55-14.mp4'  '2023-04-04 12-06-14.mp4'  '2023-04-11 12-00-51.mp4'
'2023-03-14 00-11-13.mp4'  '2023-03-30 16-16-21.mp4'  '2023-04-04 12-19-03.mp4'  '2023-04-11 13-55-54.mp4'
'2023-03-14 00-17-25.mp4'  '2023-03-31 21-07-22.mp4'  '2023-04-04 12-19-23.mp4'  '2023-04-20 19-41-44.mp4'
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_ARR&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;
  The moment of truth... 😰
  &lt;br&gt;
As you may have already noticed, the filenames contain the &lt;code&gt;space&lt;/code&gt; character. Now, recall what is written about &lt;code&gt;IFS&lt;/code&gt; at the start of this section of the article.



&lt;p&gt;Let's list the array elements, each on a separate line, to make the output more readable, and pick just the first ten lines to make it short...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;F &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXAMPLE_ARR&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%b\n'&lt;/span&gt; &lt;span class="nv"&gt;$F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;2023-02-26
22-10-40.mp4
2023-02-26
22-16-21.mp4
2023-03-12
18-59-36.mp4
2023-03-14
00-10-02.mp4
2023-03-14
00-11-13.mp4
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Oh no! My filenames are broken! 😱&lt;/span&gt;
&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bash splits filenames into &lt;code&gt;words&lt;/code&gt; where it finds any of the characters listed in the &lt;code&gt;IFS&lt;/code&gt; variable. Sometimes that's crucial, and you want to change the shell's behavior to split only at the characters you specify.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Congrats if your guess was right! 🎉&lt;/p&gt;
&lt;/blockquote&gt;



&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Time for Tuning | Use IFS to Your Advantage
&lt;/h2&gt;

&lt;p&gt;Since &lt;code&gt;IFS&lt;/code&gt; is a variable, it can be altered by a user. All you need to do is to assign a new value to it. But make sure to use appropriate quoting when you intend to use escape sequences — double quotes (&lt;code&gt;""&lt;/code&gt;) or ANSI-C Quoting (&lt;code&gt;$''&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Changing the &lt;code&gt;IFS&lt;/code&gt; value for the rest of the shell process execution is not recommended since it can cause you more trouble than bring benefits.&lt;/p&gt;

&lt;p&gt;What I prefer is either change the &lt;code&gt;IFS&lt;/code&gt; value, execute the commands where custom field separators are needed, and &lt;code&gt;unset&lt;/code&gt; &lt;code&gt;IFS&lt;/code&gt;, or execute the commands — including &lt;code&gt;IFS&lt;/code&gt; variable assignment — in a subshell to avoid mutating the current shell environment.&lt;/p&gt;

&lt;p&gt;Don't worry. The shell can't be broken by unsetting the &lt;code&gt;IFS&lt;/code&gt; variable. When &lt;code&gt;IFS&lt;/code&gt; is unset, Bash will use the default value identical to the variable's initial value.&lt;/p&gt;

&lt;p&gt;
  Example 1 — Unsetting
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;F &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%b\n'&lt;/span&gt; &lt;span class="nv"&gt;$F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;2023-02-26 22-10-40.mp4
2023-02-26 22-16-21.mp4
2023-03-12 18-59-36.mp4
2023-03-14 00-10-02.mp4
2023-03-14 00-11-13.mp4
2023-03-14 00-17-25.mp4
2023-03-14 00-22-19.mp4
2023-03-24 11-54-46.mp4
2023-03-24 11-54-57.mp4
2023-03-24 11-55-14.mp4
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;unset &lt;/span&gt;IFS
&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  Example 2 — Subshell
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;F &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%b\n'&lt;/span&gt; &lt;span class="nv"&gt;$F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;2023-02-26 22-10-40.mp4
2023-02-26 22-16-21.mp4
2023-03-12 18-59-36.mp4
2023-03-14 00-10-02.mp4
2023-03-14 00-11-13.mp4
2023-03-14 00-17-25.mp4
2023-03-14 00-22-19.mp4
2023-03-24 11-54-46.mp4
2023-03-24 11-54-57.mp4
2023-03-24 11-55-14.mp4
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Since the command was executed in a subshell, the current environment is left untouched.&lt;/span&gt;
&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;F &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%b\n'&lt;/span&gt; &lt;span class="nv"&gt;$F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;2023-02-26
22-10-40.mp4
2023-02-26
22-16-21.mp4
2023-03-12
18-59-36.mp4
2023-03-14
00-10-02.mp4
2023-03-14
00-11-13.mp4
&lt;/span&gt;&lt;span class="gp"&gt;[deathroll@fedora Videos]$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;




&lt;h2&gt;
  
  
  Now that you hopefully know a bit more about Bash, it's time to experiment yourself! Good luck and have fun, see ya in the next article 🤟!
&lt;/h2&gt;

</description>
      <category>beginners</category>
      <category>linux</category>
      <category>devops</category>
      <category>bash</category>
    </item>
    <item>
      <title>Set up a private ClamAV database mirror with a Tor-based local SOCKS5 proxy</title>
      <dc:creator>Timofey Chuchkanov</dc:creator>
      <pubDate>Thu, 09 Feb 2023 11:11:31 +0000</pubDate>
      <link>https://dev.to/crt0r/setup-a-private-clamav-database-mirror-with-a-tor-based-local-socks5-proxy-45em</link>
      <guid>https://dev.to/crt0r/setup-a-private-clamav-database-mirror-with-a-tor-based-local-socks5-proxy-45em</guid>
      <description>&lt;h1&gt;
  
  
  WARNING! NONE OF THE PERFORMED CONFIGURATIONS CONSIDER SPECIFIC PROJECTS' SECURITY REQUIREMENTS. USE THIS ARTICLE AS A SECONDARY
&lt;/h1&gt;

&lt;p&gt;SOURCE OF KNOWLEDGE ONLY AND HARDEN YOUR SYSTEMS AS APPROPRIATE.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*upd 12/26/2024: Dev Community formatting has gone nuts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Why set up a private mirror?&lt;/li&gt;
&lt;li&gt;
Setting up a server

&lt;ul&gt;
&lt;li&gt;What tools are used&lt;/li&gt;
&lt;li&gt;Creating an LXC container on Proxmox&lt;/li&gt;
&lt;li&gt;A local Tor proxy (SOCKS5) with obfs4 bridges&lt;/li&gt;
&lt;li&gt;ClamAV with automatic database updates&lt;/li&gt;
&lt;li&gt;Web server with HTTPS&lt;/li&gt;
&lt;li&gt;Enabling firewall&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Configuring clients&lt;/li&gt;

&lt;li&gt;The result&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;If you are an active computer user, you probably often get a lot of new files from the outside world. The environment is harsh, and it's quite easy to infect a device with malware. To reduce the chances of being infected, it's a good practice to scan everything with special software. For small files that don't contain any confidential info, services like &lt;a href="https://virustotal.com" rel="noopener noreferrer"&gt;VirusTotal&lt;/a&gt; can be utilized. For other data, a standalone or a Live CD-based antivirus application should be preferred.&lt;/p&gt;

&lt;p&gt;One of the most popular Free and Open Source applications of this kind is &lt;a href="https://clamav.net" rel="noopener noreferrer"&gt;ClamAV&lt;/a&gt; by Cisco Talos. The tool is the de facto standard antivirus for UNIX-like systems (both on servers and clients).&lt;/p&gt;

&lt;p&gt;To be able to find threats, antivirus software needs a special database of virus signatures. These signatures are continuously created by large teams of security specialists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why set up a private mirror?
&lt;/h2&gt;

&lt;p&gt;Well, there might be lots of reasons. The most significant one coming straight to mind is to reduce network traffic and to increase the database update speed for clients.&lt;/p&gt;

&lt;p&gt;The decision to host a private virus signature database mirror for ClamAV at my Homelab emerged because I use the tool a lot, and it's not available in my region.&lt;/p&gt;

&lt;p&gt;I encourage you to try out this small project even for your Homelab, because it is really easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What tools are used
&lt;/h3&gt;

&lt;p&gt;Since the aforementioned Homelab is based on the Proxmox VE hypervisor, everything is set up there, in a container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this scenario, &lt;code&gt;freshclam&lt;/code&gt; is used for fetching database files to a mirror server. It's not possible to serve CDIFF files using this method. Consider &lt;a href="https://docs.clamav.net/appendix/CvdPrivateMirror.html#use-cvdupdate-to-serve-whole-databases-and-database-patch-files-from-a-private-mirror" rel="noopener noreferrer"&gt;cvdupdate&lt;/a&gt; if you need to reduce network bandwidth consumption even more during clients' database updates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hypervisor&lt;/td&gt;
&lt;td&gt;Proxmox VE 7.3-4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guest&lt;/td&gt;
&lt;td&gt;Fedora Linux 37 (Container Image)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Antivirus (mirror)&lt;/td&gt;
&lt;td&gt;ClamAV 0.103.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous communication&lt;/td&gt;
&lt;td&gt;Tor 0.4.7.13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Creating an LXC container on Proxmox
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Select your storage pool for VM disks and container templates, go to &lt;code&gt;CT Templates&lt;/code&gt; and click on the &lt;code&gt;Templates&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17g1f5y3k9rnny16ma05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17g1f5y3k9rnny16ma05.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the latest Fedora version in the list and download it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xjettkno5f8lg49xfjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xjettkno5f8lg49xfjj.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the &lt;code&gt;Create CT&lt;/code&gt; button at the top of the Proxmox dashboard and follow the wizard. It's ok to designate 1 CPU core to the container, but it needs sufficient RAM to update databases. Set the limit to something around 4096 MiB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don't forget to set an option to start the container at boot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa01mribgckzpimbwqiux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa01mribgckzpimbwqiux.png" alt=" " width="432" height="214"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start up the container and connect to its console.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  A local Tor proxy (SOCKS5) with obfs4 bridges
&lt;/h3&gt;

&lt;p&gt;In countries where Tor is censored, bridges can be used.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some operating systems' repositories don't provide pre-built packages for obfs4, but the tool can be easily &lt;a href="https://github.com/Yawning/obfs4" rel="noopener noreferrer"&gt;compiled&lt;/a&gt; from source code. If you are interested in a quick guide on how to compile obfs4, let me know!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Install Tor
&lt;/h4&gt;

&lt;p&gt;Thankfully, Fedora has everything we need in its repositories &amp;lt;3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dnf install tor obfs4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure Tor
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Locate the &lt;code&gt;obfs4proxy&lt;/code&gt; binary&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ which obfs4proxy
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;
  Example
  &lt;br&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[deathroll@ClamAV-DB ~]$ which obfs4proxy
/usr/bin/obfs4proxy
[deathroll@ClamAV-DB ~]$
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the &lt;code&gt;/etc/tor/torrc&lt;/code&gt; config file with an editor of choice. Mine is vim.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# vim /etc/tor/torrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, add the following two lines:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy
UseBridges 1
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;👆 Replace the path after &lt;code&gt;exec&lt;/code&gt; with what you got in step 1.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now, get some bridges!&lt;br&gt;
Go to &lt;a href="https://bridges.torproject.org/options/" rel="noopener noreferrer"&gt;https://bridges.torproject.org/options/&lt;/a&gt;, select &lt;code&gt;obfs4&lt;/code&gt;, and click on &lt;code&gt;Get Bridges&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdv0uqq924h5d9mhc2889.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdv0uqq924h5d9mhc2889.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to get a list of bridges, complete a captcha. When the list is displayed, just copy it as-is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frshm8yrydq03bkqw9yv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frshm8yrydq03bkqw9yv7.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste what you've just copied to the &lt;code&gt;torrc&lt;/code&gt; file and prefix each line with &lt;code&gt;Bridge&lt;/code&gt;. Here's how it will look in the file (the output was modified due to privacy concerns):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[deathroll@ClamAV-DB ~]$ grep -i ^bridge /etc/tor/torrc 
Bridge obfs4 &amp;lt;some.ip.address.here:port&amp;gt; &amp;lt;ALONGSTRINGOFSOMEJIBBERISH&amp;gt; cert=&amp;lt;ONEMORELONGSTRINGOFSOMEJIBBERISH&amp;gt; iat-mode=&amp;lt;number&amp;gt;
Bridge obfs4 &amp;lt;some.ip.address.here:port&amp;gt; &amp;lt;ALONGSTRINGOFSOMEJIBBERISH&amp;gt; cert=&amp;lt;ONEMORELONGSTRINGOFSOMEJIBBERISH&amp;gt; iat-mode=&amp;lt;number&amp;gt;
[deathroll@ClamAV-DB ~]$
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After saving and closing the config file, perform a test connection by executing &lt;code&gt;tor&lt;/code&gt; as &lt;code&gt;toranon&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo -u toranon tor
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In case of success, kill the process with &lt;code&gt;Ctrl + C&lt;/code&gt;, and enable and start the service.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl enable --now tor
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the status with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl status tor
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ClamAV with automatic database updates
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Install ClamAV
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dnf install clamav clamd clamav-update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure freshclam
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the &lt;code&gt;/etc/freshclam.conf&lt;/code&gt; file and add the following:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTPProxyServer socks5://127.0.0.1
HTTPProxyPort 9050
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable database compression (optional).&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CompressLocalDatabase yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Also, you can enable logging (optional). For more, read &lt;a href="https://manpages.debian.org/bullseye/clamav-freshclam/freshclam.conf.5.en.html" rel="noopener noreferrer"&gt;freshclam.conf(5)&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add this to the config:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UpdateLogFile /var/log/freshclam.log
LogTime yes
LogSyslog yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the log file.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# touch /var/log/freshclam.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And change its owner to &lt;code&gt;clamupdate&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chown clamupdate: /var/log/freshclam.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;After that, locate the &lt;code&gt;clamav-freshclam&lt;/code&gt; service file.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find /etc/systemd/ -name '*clam*'
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;
  Example
  &lt;br&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[root@ClamAV-DB deathroll]# find /etc/systemd/ -name '*clam*'
/etc/systemd/system/multi-user.target.wants/clamav-freshclam.service
[root@ClamAV-DB deathroll]#
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Open the file you've just found and append &lt;code&gt;tor.service&lt;/code&gt; to the lines that begin with &lt;code&gt;Wants&lt;/code&gt; and &lt;code&gt;After&lt;/code&gt; so they look like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wants=network-online.target tor.service
After=network-online.target tor.service
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Reload the systemd manager configuration.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Enable and start the &lt;code&gt;clamav-freshclam&lt;/code&gt; service.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl enable --now clamav-freshclam
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Check the status with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl status clamav-freshclam
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Web server with HTTPS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In this example, we use NGINX, but you're free to experiment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Prepare a directory for serving
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new directory. For me it is &lt;code&gt;/srv/cvd&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mkdir -p /srv/cvd
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find where the database is located.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# find / -name clamav -xtype d |&amp;amp; grep -iv permission
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;👆 &lt;code&gt;-xtype d&lt;/code&gt; makes &lt;code&gt;find&lt;/code&gt; search for directories only. &lt;code&gt;|&amp;amp;&lt;/code&gt; pipes both &lt;code&gt;stdout&lt;/code&gt; and &lt;code&gt;stderr&lt;/code&gt; to grep. Here we are just filtering the output to remove read errors—don't worry, they are insignificant.&lt;/p&gt;

&lt;p&gt;In my case, the dir is &lt;code&gt;/var/lib/clamav&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;
  Example
  &lt;br&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[deathroll@ClamAV-DB nginx]$ sudo find / -name clamav -xtype d |&amp;amp; grep -iv permission
/usr/share/licenses/clamav
/var/lib/clamav
[deathroll@ClamAV-DB nginx]$
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;

&lt;p&gt;
  Without grep filtering
  &lt;br&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[deathroll@ClamAV-DB nginx]$ sudo find / -name clamav -xtype d
find: ‘/proc/tty/driver’: Permission denied
/usr/share/licenses/clamav
/var/lib/clamav
find: ‘/dev/.lxc/sys/kernel’: Permission denied
find: ‘/dev/.lxc/sys/power’: Permission denied
find: ‘/dev/.lxc/sys/class’: Permission denied
find: ‘/dev/.lxc/sys/devices’: Permission denied
find: ‘/dev/.lxc/sys/dev’: Permission denied
---OUTPUT TRIMMED---
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/fstab&lt;/code&gt; to set a read-only bind mount from the database dir to the newly created one.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/lib/clamav/    /srv/cvd    none    bind,ro    0   0
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reload the systemd manager configuration.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try to mount &lt;code&gt;/srv/cvd&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mount /srv/cvd
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the mountpoint.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ findmnt /srv/cvd
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;
  Example
  &lt;br&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[deathroll@ClamAV-DB nginx]$ findmnt /srv/cvd
TARGET   SOURCE                                                 FSTYPE OPTIONS
/srv/cvd NAS/Virtualization/subvol-1000-disk-0[/var/lib/clamav] zfs    ro,noatime,xattr,posixacl
[deathroll@ClamAV-DB nginx]$
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Install NGINX
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dnf install nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Copy certificates
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;In order to be able to use HTTPS, it's required to have valid certificates. Refer to &lt;a href="https://dev.to/deathroll/trusted-self-signed-tls-certificates-for-dummies-w-thorough-explanations-included-da7"&gt;this article&lt;/a&gt; to learn how to create a trusted self-signed certificate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a directory for certificates in a preferred location.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mkdir /etc/ssl/www
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy a chain of trust file and a key file to the newly created directory.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Configure NGINX
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;In this example, it's assumed that a DNS A record exists for the container's static IP address. The record name is &lt;code&gt;cvd.deathroll.internal&lt;/code&gt;. It's also possible to implement HTTPS without DNS, though not recommended.&lt;/p&gt;

&lt;p&gt;The configuration is split up into multiple files for ease of administration, but this is not required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a file with shared SSL/TLS configuration for server blocks. Choose a filename suitable for you.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# vim /etc/nginx/ssl_params.conf
&lt;/code&gt;&lt;/pre&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add at least two things: locations for the chain of trust and key files.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="s"&gt;"/etc/ssl/www/fullchain.pem"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;"/etc/ssl/www/deathroll-internal-key.pem"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Change any other &lt;a href="https://nginx.org/en/docs/http/ngx_http_ssl_module.html" rel="noopener noreferrer"&gt;options&lt;/a&gt; at your discretion. For example:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Change the default server block in the &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; file to the following:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt;      &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# A catch-all server name for handling requests to any host.&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Redirect to HTTPS.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Create a file for a &amp;lt;domain&amp;gt; HTTPS server block in &lt;code&gt;/etc/nginx/conf.d&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vim /etc/nginx/conf.d/cvd.deathroll.internal.conf
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;And populate it with contents similar to these:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt;      &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;cvd.deathroll.internal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;root&lt;/span&gt;        &lt;span class="n"&gt;/srv/cvd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# This is the bind mount directory you have created earlier.&lt;/span&gt;

    &lt;span class="kn"&gt;include&lt;/span&gt;     &lt;span class="n"&gt;/etc/nginx/ssl_params.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Include shared TLS/SSL parameters.&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;autoindex&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Enable directory listing.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Create a file with a server block for denying requests to any host. For example, &lt;code&gt;/etc/nginx/conf.d/deny-conn-by-ip.conf&lt;/code&gt;. Add something like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt;      &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Make this server default for port 443 to be able to catch unwanted requests.&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;include&lt;/span&gt;     &lt;span class="n"&gt;/etc/nginx/ssl_params.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;blockquote&gt;
&lt;p&gt;👆 From what I could infer from inspecting packets, observing NGINX behavior, and reading its &lt;a href="https://nginx.org/en/docs/http/request_processing.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, when NGINX receives a request, it checks the &lt;code&gt;Host&lt;/code&gt; HTTP header to decide what virtual host should process the request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wc6bzidccy19ys98271.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wc6bzidccy19ys98271.png" alt=" " width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The catch-all (&lt;code&gt;_&lt;/code&gt;) server for port 443 has a &lt;code&gt;default_server&lt;/code&gt; parameter, so HTTPS requests to any &lt;code&gt;Host&lt;/code&gt; will be processed by this virtual host. If this parameter is omitted, any other virtual server for this port may become the default server, and hence requests won't be denied.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Reload NGINX config.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Check NGINX status.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Enabling firewall
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In this example, firewalld is used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Install firewalld
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dnf install firewalld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configure firewalld
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Enable and start the service&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl enable --now firewalld
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add ports. If you use SSH, add it as well.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# firewall-cmd --permanent --add-port={80,443}/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reload firewalld&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# firewall-cmd --complete-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuring clients
&lt;/h2&gt;

&lt;p&gt;Disable database compression (optional) and add a private mirror in the &lt;code&gt;freshclam.conf&lt;/code&gt; file, so these options look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deathroll@fdesk:~ % egrep -i '^(private|compress)' /usr/local/etc/freshclam.conf
CompressLocalDatabase no
PrivateMirror https://cvd.deathroll.internal
deathroll@fdesk:~ % 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The result
&lt;/h1&gt;

&lt;p&gt;After the setup is complete, what we have is a Fedora 37 container with a local Tor-based SOCKS5 proxy that is used for updating the ClamAV database. On top of this, we have an NGINX web server that acts as a private mirror that allows clients to list and download database files for ClamAV. This web server redirects all requests to HTTPS and denies requests to any host different from the server's FQDN.&lt;/p&gt;

&lt;p&gt;
  Deny a request to an IP address
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ld0d8k287sjjap09zt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ld0d8k287sjjap09zt5.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;p&gt;
  Directory listing
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0r01ilvs03fyqk5w35p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0r01ilvs03fyqk5w35p.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;p&gt;
  Update the ClamAV database on a client
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa76in1x522t6eavbhxq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa76in1x522t6eavbhxq3.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

</description>
      <category>goodjob</category>
      <category>community</category>
      <category>gratitude</category>
    </item>
    <item>
      <title>Trusted self-signed TLS certificates for dummies (w/ thorough explanations included)</title>
      <dc:creator>Timofey Chuchkanov</dc:creator>
      <pubDate>Fri, 25 Nov 2022 12:57:30 +0000</pubDate>
      <link>https://dev.to/crt0r/trusted-self-signed-tls-certificates-for-dummies-w-thorough-explanations-included-da7</link>
      <guid>https://dev.to/crt0r/trusted-self-signed-tls-certificates-for-dummies-w-thorough-explanations-included-da7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;*upd 12/26/2024: Dev Community formatting has gone nuts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;upd 1/20/2023: Added FreeBSD steps, CA certificate SAN, and fixed shell commands formatting.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;upd 1/23/2023: Added Android 13 steps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;upd 1/25/2023: Added openssl v1.1.1 fix for the step &lt;code&gt;5.2&lt;/code&gt; in &lt;code&gt;🪄 How to generate a self-signed cert&lt;/code&gt;. Removed FreeBSD instructions for installing openssl v3.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a translation of my article, initially written in a different language. Please, let me know about language-related mistakes if you notice any.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Also, I'm a student and not a professional with a rich knowledge base, though that's my goal. Let me know if I misunderstood something and consequently shared misinformation here.&lt;/em&gt; &lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;h2&gt;
  
  
  WARNING! NONE OF THE PERFORMED CONFIGURATIONS CONSIDER SPECIFIC PROJECTS' SECURITY REQUIREMENTS. USE THIS ARTICLE AS A SECONDARY SOURCE OF KNOWLEDGE ONLY AND HARDEN YOUR SYSTEMS AS APPROPRIATE.
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
🧑‍🎓 A bit of theory

&lt;ul&gt;
&lt;li&gt;🏛️ Certificate authority&lt;/li&gt;
&lt;li&gt;🔏 Digital certificate&lt;/li&gt;
&lt;li&gt;✒️ Digital signature&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;🧐 When you might need this&lt;/li&gt;

&lt;li&gt;🔍 How HTTPS works (abstract)&lt;/li&gt;

&lt;li&gt;📜 What you will need to generate a self-signed cert&lt;/li&gt;

&lt;li&gt;🪄 How to generate a self-signed cert&lt;/li&gt;

&lt;li&gt;

🔫 How to force devices to trust our very own CA

&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;FreeBSD&lt;/li&gt;
&lt;li&gt;Android&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;li&gt;Some additional configuration required by Firefox&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;🖥️ The result&lt;/li&gt;

&lt;li&gt;📖 Additional sources of useful information&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is my slight extension to the &lt;a href="https://youtu.be/VH4gXcvkmOY" rel="noopener noreferrer"&gt;How to create a valid self signed SSL Certificate?&lt;/a&gt; video by Christian Lempa—I love this channel &amp;lt;3—that covers some topics more in-depth. The video turned out to be pretty helpful for me during the integration of HTTPS into my HomeLab services. Now I have trusted TLS certs yay!&lt;/p&gt;

&lt;p&gt;It's barely feasible to do anything without understanding what exactly is happening under the hood for me. I always try to dig deeper to get a grasp of truth—how things work. That's why I wrote this article. The goals are: firstly, to learn more about HTTPS and secondly, to help people that are just like me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧑‍🎓 A bit of theory
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🏛️ Certificate Authority
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Certificate_authority" rel="noopener noreferrer"&gt;&lt;strong&gt;Certificate Authority (CA)&lt;/strong&gt;&lt;/a&gt; is an entity/party responsible for generating, storing, and issuing digital certificates. There are many CAs: governmental, commercial, non-profit, and private.&lt;/p&gt;

&lt;p&gt;Almost every device stores trusted Certificate Authorities' public keys and digital certificates: a smartphone, a PC, or a single-board computer for IoT. If a device supports connecting to a network by design, its operating system has these artifacts. This approach allows us to verify network resources' identities and safely connect to them.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔏 Digital certificate
&lt;/h3&gt;

&lt;p&gt;It is an electronic document used to validate the identity of a public key. It contains info about the key itself, the subject (owner) of this key, and a digital signature of an entity/party that approved the certificate.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✒️ Digital signature
&lt;/h3&gt;

&lt;p&gt;An electronic signature type. It's a mathematical algorithm used for validating data authenticity and integrity. A digital signature creates a unique fingerprint for an entity.&lt;/p&gt;

&lt;p&gt;Abstractly,  digital signature creation and usage process may be performed like so:&lt;/p&gt;

&lt;p&gt;Party &lt;em&gt;A&lt;/em&gt; has a pair of keys—a public and a private one. This party wants to transfer a document &lt;em&gt;DOC&lt;/em&gt; to party &lt;em&gt;B&lt;/em&gt;. It also wants &lt;em&gt;B&lt;/em&gt; to be able to validate the &lt;em&gt;DOC&lt;/em&gt; and check its integrity.&lt;/p&gt;

&lt;p&gt;To achieve this, &lt;em&gt;A&lt;/em&gt; applies a hash function to &lt;em&gt;DOC&lt;/em&gt; to get a digest—an alphanumeric value unique to this document. A digest may be of any fixed size depending on the hash algorithm. When the digest is calculated, the party encrypts it using its private key. That's how a digital signature &lt;em&gt;SIG&lt;/em&gt; is created.&lt;/p&gt;

&lt;p&gt;When &lt;em&gt;B&lt;/em&gt; receives &lt;em&gt;DOC&lt;/em&gt; from &lt;em&gt;A&lt;/em&gt;, it also gets &lt;em&gt;SIG&lt;/em&gt; and &lt;em&gt;A&lt;/em&gt;'s public key &lt;em&gt;PUB&lt;/em&gt;. Just like &lt;em&gt;A,&lt;/em&gt; &lt;em&gt;B&lt;/em&gt; applies a hash function to &lt;em&gt;DOC.&lt;/em&gt; But after that, it uses &lt;em&gt;A&lt;/em&gt;'s public key &lt;em&gt;PUB&lt;/em&gt; to decrypt the digest. The next step is to compare the calculated digest to the decrypted one.&lt;/p&gt;

&lt;p&gt;If digests are equal, the document wasn't altered during transmission.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧐 When you might need this
&lt;/h2&gt;

&lt;p&gt;Before we start doing anything, we always ask ourselves: but why do I need to do this?&lt;/p&gt;

&lt;p&gt;There are many situations when a self-signed certificate might be needed/preferred. For example, it's common for an enterprise network to have a couple or a hundred &lt;strong&gt;internal&lt;/strong&gt; services. I intentionally highlighted the word "internal" since using self-signed certs for public services is a security-violating habit. By doing so, you bring danger to your users, at least. As another example, such certificates may be used for your HomeLab services—just like in my case—or other cases when a non-public domain is utilized.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 How HTTPS works (abstract)
&lt;/h2&gt;

&lt;p&gt;A PKI or &lt;a href="https://en.wikipedia.org/wiki/Public_key_infrastructure" rel="noopener noreferrer"&gt;Public Key Infrastructure&lt;/a&gt; is the foundation of HTTPS.&lt;/p&gt;

&lt;p&gt;Sure, we could encrypt all network traffic with asymmetric algorithms. But it would be too slow and ineffective. Both time and computing resources spent on encryption and decryption increase as the amount of data grows, as you might already know. Symmetric encryption is used to avoid this, and both parties have session keys for it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But here's another problem: &lt;strong&gt;how to transfer session keys in such a way that they could not be accessed by a threat actor? 🤔&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;— Asymmetric encryption! 🎉&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To provide HTTPS traffic encryption, the client and the server must perform a "handshake." During the TLS handshake, parties agree to use a highest TLS version supported by both parties and &lt;a href="https://en.wikipedia.org/wiki/Cipher_suite" rel="noopener noreferrer"&gt;cipher suites&lt;/a&gt; supported alike. A couple more things happen at this stage. The client verifies the identity of the server by a digital certificate belonging to the latter one, and in case of success, session keys are generated. These session keys allow to encrypt traffic symmetrically.&lt;/p&gt;

&lt;p&gt;Both participants of the connection have key pairs consisting of public and private ones. To generate session keys, participants share some data that is encrypted using public keys.&lt;/p&gt;

&lt;p&gt;Data sent after the end of the handshake is symmetrically encrypted with the session keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  📜 What you will need to generate a self-signed cert
&lt;/h2&gt;

&lt;p&gt;To create a certificate, we need several other things first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pair of keys for the Certificate Authority&lt;/li&gt;
&lt;li&gt;A self-signed CA certificate&lt;/li&gt;
&lt;li&gt;A pair of keys for the target resource&lt;/li&gt;
&lt;li&gt;A Certificate Signing Request (CSR) for the resource&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CSR&lt;/strong&gt; is a message for the CA, signed by an entity's private key. It contains info such as an email, an organization's full name, a country code, a city, a key type and its length, etc. An entity sends this message to a CA with the intent to have it signed by the CA's private key.&lt;/p&gt;

&lt;h2&gt;
  
  
  🪄 How to generate a self-signed cert
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;In the following example, we use a 4096-bit long RSA key and an SHA-256 hashing algorithm, but it's just a personal preference. Aside from that, the whole process is explicitly separated into several steps, though some things can be achieved quicker, involving lesser commands.&lt;/p&gt;

&lt;p&gt;And remember that you can choose filenames different from mine. Just make sure they are consistent. :)&lt;/p&gt;

&lt;p&gt;There's a high number of ways you can generate certs. In this scenario, an openssl utility is demonstrated (version 3 and higher).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Certificate Authority key pair
&lt;/h3&gt;

&lt;p&gt;👇 For the sake of security, the key will be encrypted using an AES-256 algorithm. That's why you'll need to come up with a password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl genrsa -aes256 -out CA-key.pem 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Self-signed CA certificate
&lt;/h3&gt;

&lt;p&gt;It's possible to create a certificate valid for one day to &lt;em&gt;several years (👇)&lt;/em&gt;. More commonly, they're valid for several months.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  A certificate that is expired in more than 13 months will be considered invalid by a huge number of modern devices. It was the case with my iOS-powered phone.
&lt;/h4&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's better to add a &lt;code&gt;subjectAltName&lt;/code&gt; extension since the Common Name (CN) is &lt;code&gt;deprecated&lt;/code&gt; and the Subject Alternative Name (SAN) is a more flexible replacement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl req -new -x509 -sha256 -days 365 -key CA-key.pem -out CA.pem -addext 'subjectAltName = DNS:hl-ca.deathroll.internal'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👆 Initially, the &lt;code&gt;openssl req&lt;/code&gt; command with a flag &lt;code&gt;-new&lt;/code&gt; creates a new CSR, but we also specified a flag &lt;code&gt;-x509&lt;/code&gt; and an option &lt;code&gt;-key&lt;/code&gt;. This caused a change in the program's behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-x509&lt;/code&gt; tells the program to generate an X.509 certificate instead&lt;/li&gt;
&lt;li&gt;The certificate is signed with a private key you provide via &lt;code&gt;-key&lt;/code&gt;, and the corresponding public key is attached to the certificate itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;-sha256&lt;/code&gt; flag tells &lt;code&gt;openssl&lt;/code&gt; to use the SHA256 algorithm for digest computation. This algorithm is the default, but in case it changes somewhere in the near future, it's better to be explicit.&lt;/p&gt;

&lt;p&gt;
  OpenSSL 3.0.2 15 Mar 2022 supported digests
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewy3eack0r3xl1cn5s0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewy3eack0r3xl1cn5s0x.png" alt="OpenSSL 3.0.2 15 Mar 2022 supported digests" width="544" height="271"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;p&gt;
  You can check that the certificate is indeed for a CA
  &lt;h2&gt;
  
  
  And a command for this is pretty simple:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl x509 -in CA.pem -text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4csji2tmwl21pkjzzg9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4csji2tmwl21pkjzzg9s.png" alt="X.509 certificate human-readable contents" width="800" height="464"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Resource key pair
&lt;/h3&gt;

&lt;p&gt;👇 This key could also be encrypted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl genrsa -out deathroll-internal-key.pem 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. CSR for a resource/domain
&lt;/h3&gt;

&lt;p&gt;To simplify my life, I generated a CSR for the whole HomeLab domain at once—&lt;code&gt;*.deathroll.internal&lt;/code&gt;. Such a format is called &lt;code&gt;wildcard&lt;/code&gt;. It allows one to use a single certificate no matter what subdomain it is. Sounds convenient, right?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl req -new -sha256 -key deathroll-internal-key.pem -out deathroll-internal.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Resource certificate
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a small config file for X.509 extensions and add the &lt;code&gt;subjectAltName&lt;/code&gt; extension to it. In our case it's a file called &lt;code&gt;certconf.cnf&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subjectAltName="DNS:*.deathroll.internal"
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;
  subjectAlternativeName supported values - x509v3_config (5)
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkplxg66m7j32res1qy2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkplxg66m7j32res1qy2g.png" alt="subjectAlternativeName supported values - x509v3_config" width="800" height="464"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;

&lt;p&gt;
  That's how X.509 extensions look like in a certificate
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbocnnolpolssdi5anub4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbocnnolpolssdi5anub4.png" alt="X.509 certificate human-readable contents" width="800" height="464"&gt;&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the certificate itself&lt;br&gt;

  For openssl &amp;lt;3.0.0
  &lt;br&gt;
Add the &lt;code&gt;-CAcreateserial&lt;/code&gt; flag to create a certificate serial number. If omitted, an error will occur.&lt;br&gt;


&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl x509 -req -sha256 -days 365 -in deathroll-internal.csr -CA CA.pem -CAkey CA-key.pem -out deathroll-internal.pem -extfile certconf.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6. Certificate Chain (Chain of trust)
&lt;/h3&gt;

&lt;p&gt;It's a list of certificates that begins with a resource certificate and ends with a root CA certificate. There can also be intermediate CAs between them. &lt;/p&gt;

&lt;p&gt;Such chains can be found on most web servers—among other places—and are easily generated. To create a chain, copy the contents of a resource cert and then a CA cert into a single file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;cat&lt;/code&gt; command can be applied here. Read the contents and redirect the &lt;code&gt;stdout&lt;/code&gt; to a file.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat deathroll-internal.pem &amp;gt; fullchain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat CA.pem &amp;gt;&amp;gt; fullchain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔫 How to force devices to trust our very own CA
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget to restart any running browsers so they can trust your certificates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open the Microsoft Management Console&lt;br&gt;
For example, &lt;code&gt;Win + R&lt;/code&gt; -&amp;gt; &lt;code&gt;mmc.exe&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;Add/Remove Snap-in&lt;/code&gt; in the &lt;code&gt;File&lt;/code&gt; menu.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;Certificates&lt;/code&gt; among available snap-ins on the left-hand side of the window and click &lt;code&gt;Add&lt;/code&gt; in the middle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, choose for what account you will manage certificates. For me, it's a Computer account, and on the next page—Local computer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;code&gt;OK&lt;/code&gt; in the previously opened window.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expand &lt;code&gt;Certificates&lt;/code&gt; in the console root -&amp;gt; &lt;code&gt;Trusted Root Certification Authorities&lt;/code&gt; -&amp;gt; &lt;code&gt;Certificates&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Right-click on &lt;code&gt;Certificates&lt;/code&gt; -&amp;gt; &lt;code&gt;All tasks&lt;/code&gt; -&amp;gt; &lt;code&gt;Import&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the second page of the opened window, choose a certificate file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the file is not listed, change the file type filter to show all files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Linux
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The methods vary vastly depending on a distribution. I use a Debian-based distro.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Check if the &lt;code&gt;ca-certificates&lt;/code&gt; package is installed&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ apt list --installed | grep ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install it if it's not present on your system.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# apt install ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the certificate file to &lt;code&gt;/usr/share/ca-certificates&lt;/code&gt; and reconfigure the &lt;code&gt;ca-certificates&lt;/code&gt; package.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cp CA.pem /usr/share/ca-certificates/CA.crt
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;👆 Note that the certificate in the target directory must have the &lt;code&gt;.crt&lt;/code&gt; extension. Otherwise, it won't be recognized.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# dpkg-reconfigure ca-certificates
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;👆 In a TUI window choose your certificate by pressing &lt;code&gt;space&lt;/code&gt; on your keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  FreeBSD
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a directory listed in &lt;em&gt;TRUSTPATH&lt;/em&gt; (see &lt;em&gt;ENVIRONMENT&lt;/em&gt; at &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=certctl&amp;amp;sektion=8" rel="noopener noreferrer"&gt;certctl(8)&lt;/a&gt;). For example, &lt;code&gt;/usr/local/etc/ssl/certs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mkdir -p /usr/local/etc/ssl/certs
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the certificate file to the created directory&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cp CA.pem /usr/local/etc/ssl/certs/homelab-ca.crt
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;👆 Note that the certificate in the target directory must have the &lt;code&gt;.crt&lt;/code&gt; extension. Otherwise, it won't be recognized.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rebuild SSL certificates list&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# certctl rehash
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify that the CA certificate was installed (use part of your CN instead of &lt;code&gt;internal&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ certctl list | grep internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify the certificate of your service&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openssl s_client -connect nas.deathroll.internal:443 | head
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Android
&lt;/h3&gt;

&lt;h4&gt;
  
  
  7.1
&lt;/h4&gt;

&lt;p&gt;Surprisingly, it's stupidly simple to install a custom CA cert. Just download the file and open it with a certificate manager. After that, follow the installation wizard.&lt;/p&gt;

&lt;h4&gt;
  
  
  13
&lt;/h4&gt;

&lt;p&gt;Try to search in your settings app. It should be somewhere in the privacy section.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Download the certificate and place it somewhere on your filesystem so it can be located in the &lt;code&gt;Files&lt;/code&gt; app later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open &lt;code&gt;Files&lt;/code&gt; and tap on the certificate file. You will be notified that the profile was downloaded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open &lt;code&gt;Settings&lt;/code&gt; and tap on the &lt;code&gt;Profile Downloaded&lt;/code&gt; item at the top.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tap on the profile and then &lt;code&gt;Install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;While still in the Settings app, go to &lt;code&gt;General&lt;/code&gt; -&amp;gt; &lt;code&gt;About&lt;/code&gt; -&amp;gt; &lt;code&gt;Certificate Trust Settings&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Toggle the switch near your cert.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Some additional configuration required by Firefox
&lt;/h3&gt;

&lt;p&gt;If you are a happy Firefox user, one more additional step is required. You need to permit the browser to use CAs installed on your system.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open &lt;code&gt;about:config&lt;/code&gt; in a new tab—just type it in the search bar—and accept the risk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type &lt;code&gt;security.enterprise_roots.enabled&lt;/code&gt; in the search field on the page and then change the value of this key—it will appear under the input field—from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; by clicking the &lt;code&gt;⇌&lt;/code&gt; button on the right-hand side.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🖥️ The result
&lt;/h2&gt;

&lt;p&gt;Congrats! You have created and installed your own Certificate Authority and a resource certificate. Now, if you load a web page or a web app that uses your certificate, a pleasing lock icon will greet you 🔒. Your devices trust your internal services, and annoying messages about insecurity are gone like a fart in a desert. It's worth it, believe me. :)&lt;/p&gt;

&lt;p&gt;
  Internal HTTPS-secured web services opened in various web browsers
  &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvc6dp5mophlq3w1v8ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvc6dp5mophlq3w1v8ok.png" alt="Firefox - Zabbix" width="800" height="336"&gt;&lt;/a&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flra3k4m0kze97lhmpe6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flra3k4m0kze97lhmpe6l.png" alt="Edge - pfSense" width="550" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fth42ycni3lhsgavr4r1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fth42ycni3lhsgavr4r1o.png" alt="Brave - Homer" width="730" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09960wz2o33xmk1kxq59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09960wz2o33xmk1kxq59.png" alt="Chrome on Android - pfSense" width="720" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5mle9bffop8p7fyql44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5mle9bffop8p7fyql44.png" alt="Brave on iOS - pfSense" width="800" height="1422"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  📖 Additional sources of useful information
&lt;/h2&gt;

&lt;h3&gt;
  
  
  openssl man pages
&lt;/h3&gt;

&lt;p&gt;A helpful one. There are manuals for both config files and openssl subcommands. For example, if you want to learn more about &lt;code&gt;openssl x509&lt;/code&gt;, issue &lt;code&gt;man openssl-x509&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Videos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8ItJ-VqYo_s" rel="noopener noreferrer"&gt;What is a certificate authority?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=s22eJ1eVLTU" rel="noopener noreferrer"&gt;What are Digital Signatures? - Computerphile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=86cQJ0MMses" rel="noopener noreferrer"&gt;TLS Handshake Explained - Computerphile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=T4Df5_cojAs" rel="noopener noreferrer"&gt;How does HTTPS work? What's a CA? What's a self-signed Certificate?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Text
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.digicert.com/blog/what-is-a-certificate-authority" rel="noopener noreferrer"&gt;What is a CA? Certificate Authorities Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/" rel="noopener noreferrer"&gt;What happens in a TLS handshake? | SSL handshake | Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/" rel="noopener noreferrer"&gt;How does public key cryptography work? | Public key encryption and SSL | Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/" rel="noopener noreferrer"&gt;What is an SSL certificate? | How to get a free SSL certificate | Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cisa.gov/uscert/ncas/tips/ST04-018" rel="noopener noreferrer"&gt;Understanding Digital Signatures | CISA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.globalsign.com/en/blog/what-is-a-certificate-signing-request-csr" rel="noopener noreferrer"&gt;What is a Certificate Signing Request (CSR)? Do I need one?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.globalsign.com/ssl/ssl-certificates-installation/certificate-signing-request-csr-overview" rel="noopener noreferrer"&gt;Certificate Signing Request (CSR) - Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.globalsign.com/en/blog/maximum-ssltls-certificate-validity-now-one-year" rel="noopener noreferrer"&gt;Maximum SSL/TLS Certificate Validity One Year&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/man3.0/" rel="noopener noreferrer"&gt;OpenSSL Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ibm.com/docs/en/external-auth-server/2.4.3?topic=securing-x509-extensions" rel="noopener noreferrer"&gt;X.509 Extensions - IBM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.appviewx.com/education-center/what-is-a-certificate-chain/" rel="noopener noreferrer"&gt;What is a Certificate Chain? | SSL Certificate Chain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/system-management-components/what-is-microsoft-management-console" rel="noopener noreferrer"&gt;What is MMC - Windows Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-view-certificates-with-the-mmc-snap-in" rel="noopener noreferrer"&gt;How to: View certificates with the MMC snap-in&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>https</category>
      <category>ssl</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
