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

<channel>
	<title>centos &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/tag/centos/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Mon, 08 Feb 2021 17:04:37 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Ansible: Quick Start Guide for FreeBSD, CentOS and Ubuntu</title>
		<link>https://blog.andreev.it/2021/02/ansible-quick-start-guide-for-freebsd-centos-and-ubuntu/</link>
					<comments>https://blog.andreev.it/2021/02/ansible-quick-start-guide-for-freebsd-centos-and-ubuntu/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 08 Feb 2021 17:04:37 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[FreeBSD]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[Ansible]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[freebsd]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=8784</guid>

					<description><![CDATA[In this post/howto, I&#8217;ll explain how to install Ansible as control and managed node&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post/howto, I&#8217;ll explain how to install Ansible as control and managed node on FreeBSD 12, CentOS 8 and Ubuntu 18. Then, I&#8217;ll explain how to create SSH keys so the nodes can communicate and some basic tasks. Then, I&#8217;ll show you how to create a playbook to install the latest updates and also install an Apache server with the default settings. Finally, I&#8217;ll show an example of how to use variables. </p>
<h1>Control and managed nodes</h1>
<p>A control node is where you execute all of your Ansible commands and eventually keep your playbooks, configs, inventory etc. It&#8217;s pretty much your workstation. A managed node is where the actual playbooks are executed. <a href="https://docs.ansible.com/ansible/latest/network/getting_started/basic_concepts.html" rel="noopener" target="_blank">These </a>are the main concepts and the terminology.<br />
In my case, I have 4 VMs/instances. The main one which is the control node and 3 managed nodes.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2021/01/P153-01.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2021/01/P153-01.png" alt="" width="309" height="113" class="aligncenter size-full wp-image-8786" srcset="https://blog.andreev.it/wp-content/uploads/2021/01/P153-01.png 309w, https://blog.andreev.it/wp-content/uploads/2021/01/P153-01-300x110.png 300w" sizes="(max-width: 309px) 100vw, 309px" /></a></p>
<h2>CentOS 8</h2>
<p>If you want to install Ansible on the control node, you have to install Python 3.x first. </p>
<pre class="brush: bash; title: ; notranslate">
sudo dnf install python3
</pre>
<p>This will also install <strong>pip</strong>. Type <strong>python3 </strong>to test, <strong>CTRL-D</strong> to exit and then install ansible.</p>
<pre class="brush: bash; title: ; notranslate">
sudo pip3 install ansible
</pre>
<p>Type <strong>ansible </strong>to test.</p>
<h2>Ubuntu</h2>
<p>Ubuntu comes with python installed, but not with pip. Install pip with:</p>
<pre class="brush: bash; title: ; notranslate">
sudo apt install python3-pip
</pre>
<p>Then install ansible with:</p>
<pre class="brush: bash; title: ; notranslate">
sudo pip3 install ansible
</pre>
<p>Type <strong>ansible </strong>to test.</p>
<h2>FreeBSD</h2>
<p>FreeBSD comes with python installed but pip is not. Check the version and then install the same pip version. </p>
<pre class="brush: bash; title: ; notranslate">
ls -l /usr/local/bin/python*
</pre>
<p>If your output is for example <strong>python37</strong>, install the same pip version.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install py37-pip
</pre>
<p>Then install ansible.</p>
<pre class="brush: bash; title: ; notranslate">
pip install ansible
</pre>
<p>Type <strong>ansible </strong>to test.</p>
<h1>SSH keys</h1>
<p>While Ansible can use standard *nix username/password authentication, it&#8217;s recommended that you use SSH keys to communicate from control node to the managed nodes. For that, you&#8217;ll have to create your SSH keys. Let&#8217;s say you have an account on your control node and the username is admin. You also want to use the user ansible on the managed nodes. It really doesn&#8217;t matter what usernames you are going to choose. You can always override the keys to use, but in this case, I&#8217;ll create a key on the control node and send it to all managed nodes.<br />
On the control node, regardless of your OS, do:</p>
<pre class="brush: bash; title: ; notranslate">
ssh-keygen -b 4096
</pre>
<p>This will create a subfolder <strong>.ssh</strong> with two files: <strong>id_rsa</strong> and <strong>id_rsa.pub</strong>. The former is your private key and the later is your public key.<br />
Copy the key to your managed nodes.</p>
<pre class="brush: bash; title: ; notranslate">
ssh-copy-id ansible@nodeX.andreev.local
</pre>
<p>This will copy my key for the user admin to the node X under the ansible user. Then test the passwordless connection.</p>
<pre class="brush: bash; title: ; notranslate">
ssh ansible@nodeX.andreev.local
</pre>
<p>Mind that the use of FQDN (nodex.andreev.local) vs. hostname (nodex) is important. For SSH these two are different. Once you log to the managed node, the node will be added to the list of known hosts in the file <strong>.ssh/known_hosts</strong>. </p>
<h1>Inventory and the config file</h1>
<p>Ansible uses the inventory files to execute an action against using the options and parameters specified in the config file. The config file is <strong>/etc/ansible/ansible.cfg</strong> for CentOS and Ubuntu and <strong>/usr/local/etc/ansible/ansible.cfg </strong>for FreeBSD. You can also put the inventory file in the same directory and name it as you wish, but you have to specify the inventory as a parameter on the command line or an entry in the config file. In addition, you can have your config file in your current directory or under the <strong>.ansible</strong> directory in your home folder. <strong>ansible.cfg</strong> in the current directory has a precedence over <strong>.ansible.cfg</strong> in the home directory which has a precedence over <strong>/etc/ansible/ansible.cfg</strong>. Here is how that looks.<br />
Let&#8217;s list all the inventory.</p>
<pre class="brush: bash; title: ; notranslate">
ansible --list-hosts all
</pre>
<p>You&#8217;ll get a message that there is no inventory file. Let&#8217;s create one, we&#8217;ll name it <strong>inventory.txt</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
&#x5B;freebsd]
node1.andreev.local
&#x5B;centos]
node2.andreev.local
&#x5B;ubuntu]
node3.andreev.local
&#x5B;bsd]
node1.andreev.local
&#x5B;linux]
node2.andreev.local
node3.andreev.local
</pre>
<p>If we specify the inventory file, we&#8217;ll get this.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
ansible --list-hosts all -i inventory.txt
  hosts (3):
    node1.andreev.local
    node2.andreev.local
    node3.andreev.local
</pre>
<p>If we create a config file, we can tell ansible where to look for the inventory. Create a file <strong>ansible.cfg</strong> in the same directory.</p>
<pre class="brush: bash; title: ; notranslate">
&#x5B;defaults]
inventory=/home/&lt;somewhere&gt;/inventory.txt
</pre>
<p>If you do <strong>ansible &#8211;list-hosts all</strong> now, you&#8217;ll get the same result as before, but without specifying the inventory file.<br />
Or something like this. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
ansible all -m shell -a &quot;uname -a&quot;
node1.andreev.local | CHANGED | rc=0 &gt;&gt;
FreeBSD node1.andreev.local 12.1-RELEASE FreeBSD 12.1-RELEASE r354233 GENERIC  amd64
node2.andreev.local | CHANGED | rc=0 &gt;&gt;
Linux node2.andreev.local 4.18.0-193.14.2.el8_2.x86_64 #1 SMP Sun Jul 26 03:54:29 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
node3.andreev.local | CHANGED | rc=0 &gt;&gt;
Linux node3.andreev.local 4.15.0-129-generic #132-Ubuntu SMP Thu Dec 10 14:02:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
</pre>
<p>By default, ansible runs on the managed node with the currently logged user that executes the playbook on the control node. If you get an error saying that the previous command cannot connect to the host, you have to specify the same user that you used when you test the connection with <em>ssh -user-@managednode.</em> So, edit <strong>ansible.cfg</strong> and add this line.</p>
<pre class="brush: bash; title: ; notranslate">
remote_user=&lt;user&gt;
</pre>
<p>We can target only the group linux which consists of Linux hosts only in the inventory file. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
ansible linux -m shell -a &quot;date&quot;
node3.andreev.local | CHANGED | rc=0 &gt;&gt;
Fri Jan  8 15:23:40 UTC 2021
node2.andreev.local | CHANGED | rc=0 &gt;&gt;
Fri Jan  8 10:23:40 EST 2021
</pre>
<h1>Playbooks</h1>
<p>The playbooks are the blueprints of the automation tasks. Instead of running the ansible command to execute each task separately, we combine these tasks in a YAML file and execute them sequentially. Here are 3 playbooks that update each of our managed nodes. There is no update module for FreeBSD, so we use the shell command.</p>
<pre class="brush: yaml; title: ; notranslate">
# freebsd-update.yml
---
  - hosts: freebsd
    become: yes
    tasks:
      - name: Fetch all packages
        shell: freebsd-update fetch
      - name: Install FreeBSD updates
        shell: freebsd-update install
      - name: Reboot
        reboot:
</pre>
<p>For CentOS we&#8217;ll use <strong>yum</strong>. </p>
<pre class="brush: yaml; title: ; notranslate">
# centos-update.yml
---
  - hosts: centos
    become: yes
    tasks:
      - name: Update all packages
        yum: name=* state=latest
      - name: Reboot
        reboot:
</pre>
<p>&#8230;and for Ubuntu we&#8217;ll use <strong>apt</strong>.</p>
<pre class="brush: yaml; title: ; notranslate">
# ubuntu-update.yml
---
  - hosts: ubuntu
    become: yes
    tasks:
      - name: Update all packages
        apt: name=* state=latest
      - name: Reboot
        reboot:
</pre>
<p>Save these files with a YAML extension and you can execute them with the following command.</p>
<pre class="brush: bash; title: ; notranslate">
ansible-playbook &lt;filename&gt;
</pre>
<p>All of them will probably fail. That&#8217;s because your ansible user on the managed nodes is required a password when executing a <strong>sudo </strong>command. In order to fix that, you&#8217;ll have to add a line in the sudoers file. Edit this file using the <strong>visudo </strong>command.</p>
<pre class="brush: bash; title: ; notranslate">
visudo
</pre>
<p>&#8230;and then add this line right before the <strong>@includedir <...></strong> which is the last line in the file.</p>
<pre class="brush: bash; title: ; notranslate">
ansible ALL=(ALL) NOPASSWD:ALL
</pre>
<p>Where <strong>ansible </strong>is the user that runs the playbooks on the managed nodes. FreeBSD doesn&#8217;t come with sudo preinstalled, so you&#8217;ll have to install it first on the managed node.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install sudo
</pre>
<p>These playbooks will update the OS and the packages for the Linux. For FreeBSD, it will update only the OS. Here is another example of playbooks that will install Apache server in a default configuration and change the <strong>ServerName </strong>and <strong>ServerAdmin </strong>lines. We&#8217;ll also install PHP and test our server.<br />
If you have a firewall enabled, make sure you open it up first on CentOS. Ubuntu and FreeBSD do not come with the firewall enabled. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --reload
</pre>
<p>For FreeBSD, the playbook looks like this. </p>
<pre class="brush: yaml; title: ; notranslate">
# freebsd-apache.yml
---
  - hosts: freebsd
    become: yes
    tasks:
      - name: Install apache and php
        pkgng:
          name:
            - apache24
            - php74
            - mod_php74
          state:  present
      - name: Start on reboot
        service: name=apache24 enabled=yes
      - name: Copy index.php
        copy:
          src: ../files/index.php
          dest: /usr/local/www/apache24/data
          mode: 0755
      - name: Copy mod_php.conf
        copy:
          src: ../files/mod_php.conf
          dest: /usr/local/etc/apache24/modules.d
          mode: 0755
      - name: Start apache now
        service: name=apache24 state=started
</pre>
<p>For CentOS, it looks like this.</p>
<pre class="brush: yaml; title: ; notranslate">
# centos-apache.yml
---
  - hosts: centos
    become: yes
    tasks:
      - name: Install apache and php
        yum:
          name:
            - httpd
            - php
          state:  present
      - name: Start apache now and on reboot
        service: name=httpd state=started enabled=yes
      - name: Copy index.php
        copy:
          src: ../files/index.php
          dest: /var/www/html
          mode: 0755
</pre>
<p>&#8230;and for Ubuntu it looks like this.</p>
<pre class="brush: yaml; title: ; notranslate">
# ubuntu-apache.yml
---
  - hosts: ubuntu
    become: yes
    tasks:
      - name: Install apache and php
        apt:
          name:
            - apache2
            - php
          state:  present
      - name: Start apache now and on reboot
        service: name=apache2 state=started enabled=yes
      - name: Copy index.php
        copy:
          src: ../files/index.php
          dest: /var/www/html
          mode: 0755
</pre>
<p>You will also need these two files in a directory called <strong>files</strong>. In my case it&#8217;s one level above the directory where I keep my playbooks.<br />
<strong>index.php</strong></p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
  phpinfo();
?&gt;
</pre>
<p><strong>001_mod-php.conf</strong></p>
<pre class="brush: bash; title: ; notranslate">
&lt;IfModule dir_module&gt;
    DirectoryIndex index.php index.html
    &lt;FilesMatch &quot;\.php$&quot;&gt;
        SetHandler application/x-httpd-php
    &lt;/FilesMatch&gt;
    &lt;FilesMatch &quot;\.phps$&quot;&gt;
        SetHandler application/x-httpd-php-source
    &lt;/FilesMatch&gt;
&lt;/IfModule&gt;
</pre>
<p>The <strong>index.php</strong> file is the standard test file to test the PHP distributions and the <strong>001_mod-php.conf</strong> is needed for FreeBSD only. As you can see the playbooks differ quite a bit for these three OSes. Once you deploy the playbooks, you can test the result by going to <strong>http://[nodeX]/index.php</strong>.<br />
Looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2021/01/P153-02.png"><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2021/01/P153-02.png" alt="" width="983" height="227" class="aligncenter size-full wp-image-8802" srcset="https://blog.andreev.it/wp-content/uploads/2021/01/P153-02.png 983w, https://blog.andreev.it/wp-content/uploads/2021/01/P153-02-300x69.png 300w, https://blog.andreev.it/wp-content/uploads/2021/01/P153-02-768x177.png 768w, https://blog.andreev.it/wp-content/uploads/2021/01/P153-02-585x135.png 585w" sizes="(max-width: 983px) 100vw, 983px" /></a></p>
<h1>Service handlers</h1>
<p>Service handlers are used only when a change is made on the managed node. For example, we can restart a service only if the config file was changed. If the file is not changed, then there is no need to restart. Here is an example of a service handler. We&#8217;ll restart postfix service only if the main.cf file was changed.</p>
<pre class="brush: yaml; highlight: [11,14]; title: ; notranslate">
# service-handler.yml
---
- hosts: centos
  become: yes
  tasks:
  - name: Configure main.cf
    lineinfile:
      path: /etc/postfix/main.cf
      regexp: ^#mydomain
      line: 'mydomain = example.com'
    notify: restart postfix

  handlers:
  - name: restart postfix
    service: name=postfix state=restarted
</pre>
<p>Make sure that the name for the handler is the same (lines 11 and 14), so Ansible knows what service handlers is referred. </p>
<h1>Variables</h1>
<p>When ansible runs a playbook on a managed node, the first task is to gather info about the managed node. The info is a bunch of settings that we can use in our playbooks. For example, if you execute the following command, you can see the IP address, the CPU model, python version etc.</p>
<pre class="brush: bash; title: ; notranslate">
ansible -m setup &lt;node&gt;
</pre>
<p>We can use these settings and use them as variables if we need them. For example, this playbook displays the hostname and the IP.</p>
<pre class="brush: yaml; title: ; notranslate">
# showip.yml
---
  - hosts: freebsd
    become: yes
    tasks:
      - name: Show the IP address
        debug:
          msg: &quot;The hostname is {{ inventory_hostname}}  and the IP is {{ansible_default_ipv4.address }}&quot;
</pre>
<p>And if you run the playbook, you&#8217;ll see something like this.</p>
<pre class="brush: plain; title: ; notranslate">
TASK &#x5B;Show the IP address] *********************************************************************************************
ok: &#x5B;node1.andreev.local] =&gt; {
    &quot;msg&quot;: &quot;The hostname is node1.andreev.local  and the IP is 192.168.1.211&quot;
}
</pre>
<p>Here is another example of using variables. In this case, we&#8217;ll specify a file and change the ownership and the mode.</p>
<pre class="brush: yaml; title: ; notranslate">
# owner.yml
---
  - hosts: centos
    become: yes
    vars:
      filename: &quot;/var/www/html/index.php&quot;
    tasks:
      - name: Change the owner of the file
        file:
          path: &quot;{{ filename }}&quot;
          owner: apache
          group: apache
          mode: '0755'
</pre>
<p>In case we want to assign an output to a variable, we&#8217;ll have to use the keyword <strong>register</strong>. Here is an example of how to get the output from a command and print it on the screen with the keyword <strong>debug</strong>.</p>
<pre class="brush: yaml; title: ; notranslate">
# variables.yml
---
  - hosts: freebsd

    tasks:
    - name: Get the uptime manually
      command: uptime
      register: var_uptime

    - name: Print the uptime
      debug:
        msg: The uptime is &quot;{{ var_uptime }}&quot;
</pre>
<h1>Roles</h1>
<p>Roles let you automatically load related vars_files, tasks, handlers, and other Ansible artifacts based on a known file structure. Once you group your content in roles, you can easily reuse them and share them with other users. The idea is to separate the tasks, handlers and vars in different files. Let&#8217;s see this playbook for example. It changes a line in main.cf file, restarts postfix and copies a file under the postfix main directory.</p>
<pre class="brush: yaml; title: ; notranslate">
# roles.yml
---
  - hosts: centos
    become: yes

    vars:
      filevd: &quot;/etc/postfix/virtual_domains&quot;
      cfgpostfix: &quot;/etc/postfix/main.cf&quot;


    tasks:
    - name: Configure main.cf
      lineinfile:
        path: &quot;{{ cfgpostfix }}&quot;
        regexp: ^#mydomain
        line: 'mydomain = example.com'
      notify: restart postfix
    - name: Copy virtual_domains
      copy:
        src: ../files/virtual_domains
        dest: &quot;{{ filevd }}&quot;
        mode: 0755

    handlers:
    - name: restart postfix
      service: name=postfix state=restarted
</pre>
<p>We can rewrite this file by separating the tasks, variables, files and handlers. Run this command.</p>
<pre class="brush: bash; title: ; notranslate">
ansible-galaxy role init postfix
</pre>
<p>If you look at the file/directory structure of the newly created directory postfix, it looks like this.</p>
<pre class="brush: plain; highlight: [1]; title: ; notranslate">
tree postfix
postfix
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml
</pre>
<p>Create a file <strong>virtual_domains</strong> under the <strong>files </strong>directory.</p>
<pre class="brush: bash; title: ; notranslate">
echo &quot;mydomain.com&quot; &gt; postfix/files/virtual_domains
</pre>
<p>Move the config for vars, handlers and tasks in the separate <strong>main.yml</strong> files. For example, this is how my files look like.<br />
<strong>postfix/vars/main.yml</strong></p>
<pre class="brush: yaml; title: ; notranslate">
---
# vars file for postfix

  filevd: &quot;/etc/postfix/virtual_domains&quot;
  cfgpostfix: &quot;/etc/postfix/main.cf&quot;
</pre>
<p>As you can see the keyword <strong>vars:</strong> does not exists. Ansible knows that this file is for <strong>vars </strong>so there is no need to enter the <strong>vars </strong>keyboard.<br />
<strong>postfix/tasks/main.yml</strong></p>
<pre class="brush: yaml; title: ; notranslate">
---
# tasks file for postfix

  - name: Configure main.cf
    lineinfile:
      path: &quot;{{ cfgpostfix }}&quot;
      regexp: ^#mydomain
      line: 'mydomain = example.com'
    notify: restart postfix
  - name: Copy virtual_domains
    copy:
      src: ../files/virtual_domains
      dest: &quot;{{ filevd }}&quot;
      mode: 0755
</pre>
<p><strong>postfix/handlers/main.yml</strong></p>
<pre class="brush: yaml; title: ; notranslate">
---
# handlers file for postfix

  - name: restart postfix
    service: name=postfix state=restarted
</pre>
<p>Finally, create a file called something.yml that will be your main file. This file has to be outside the postfix directory structure.<br />
In my case it looks like this.<br />
<strong>something.yml</strong></p>
<pre class="brush: yaml; title: ; notranslate">
# something.yml
---
  - hosts: centos
    become: yes
    roles:
      - postfix
</pre>
<p>Now, if you execute this playbook, ansible will automatically execute the rest of the dependant playbooks as well.</p>
<pre class="brush: bash; title: ; notranslate">
ansible-playbook something.yml
</pre>
<h1>Error handling</h1>
<p>Sometimes we want certain changes to be ignored. Sometimes, we know the behavior of certain commands and we know that they might return non-zero code and we want that ignored. For example, consider this part of a playbook.</p>
<pre class="brush: yaml; title: ; notranslate">
- hosts: centos
  tasks: 
  - name: Type something that will fail
    command: thiscommanddoesntexist
    ignore_errors: yes

  - name: Run command remotely
    command: /usr/local/bin/somecommand
    register: cmd_result
    changed_when: cmd_result == 2
</pre>
<p>Ansible would report a task as changed as long as the command (or) script gives zero return code.<br />
In the first part, we know that the task will fail, but we decide to ignore it using the keyword <strong>ignore_errors</strong>. No matter what the command returns, <strong>ignore_errors: yes</strong> will never report to ansible that the command failed.<br />
In the second command we can ignore the error based on the output of the command. For example, if the output is 2, the the error will be ignored. If cmd_result is not equal to 2, the task will be marked as changed.<br />
So whenever this condition is true, the task will be marked as changed. </p>
<h1>Tags</h1>
<p>Tags are used when you have a playbook with several tasks and you need to run only specific parts of it instead of running the entire playbook. You use tags to execute or skip selected tasks. Let&#8217;s say we have this playbook that installs Docker on Centos and has multiple tasks. As you can notice in lines 12, 24, 37 and 45 we have a new line with a keyword <strong>tags</strong> that we use to tag certain tasks. The purpose of this is to include or exclude these tasks from the playbook.</p>
<pre class="brush: yaml; highlight: [12,24,37,45]; title: ; notranslate">
# centos-docker.yml
---
- name: Install docker
  hosts: centos
  become: true

  tasks:
    - name: Install yum utils
      yum:
        name: yum-utils
        state: latest
      tags: install

    - name: Install device-mapper-persistent-data
      yum:
        name: device-mapper-persistent-data
        state: latest
      tags: install

    - name: Install lvm2
      yum:
        name: lvm2
        state: latest
      tags: install

    - name: Add Docker repo
      get_url:
        url: https://download.docker.com/linux/centos/docker-ce.repo
        dest: /etc/yum.repos.d/docer-ce.repo
      become: yes

    - name: Install Docker
      package:
        name: docker-ce
        state: latest
      become: yes
      tags: install

    - name: Start Docker service
      service:
        name: docker
        state: started
        enabled: yes
      become: yes
      tags: start
</pre>
<p>Now, with the command below, we can execute the playbook and only the tasks tagged with <strong>install </strong>will be executed.</p>
<pre class="brush: bash; title: ; notranslate">
ansible-playbook centos-docker.yaml --tags install
</pre>
<p>We can also tell ansible to NOT run those tasks tagged with <strong>install</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
ansible-playbook centos-docker.yaml --skip-tags install
</pre>
<p>You can add multiple tags per task, e.g.</p>
<pre class="brush: yaml; title: ; notranslate">
tags:
  - cleanup_app
  - cleanup_web
</pre>
<p>Ansible reserves two tag names for special behavior: <strong>always </strong>and <strong>never</strong>. If you assign the <strong>always </strong>tag to a task or play, Ansible will always run that task or play, unless you specifically skip it (<strong>&#8211;skip-tags always</strong>). If you assign the <strong>never </strong>tag to a task or play, Ansible will skip that task or play unless you specifically request it (<strong>&#8211;tags never</strong>).</p>
<h1>Ansible Vault</h1>
<p>Ansible Vault encrypts variables and files so you can protect sensitive content such as passwords or keys rather than leaving it visible as plaintext in playbooks or roles.<br />
First, you have to create a vaulted file where we&#8217;ll store the passwords. When you run this command it will ask you to create a password and then an empty file will show up.</p>
<pre class="brush: bash; title: ; notranslate">
ansible-vault create secrets.yml
</pre>
<p>Add some passwords there and save the file.</p>
<pre class="brush: bash; title: ; notranslate">
mysql_pwd: &quot;DifficultPassword&quot;
ht_pwd: &quot;PasswordXYZ&quot;
</pre>
<p>If you look at the file now, you&#8217;ll see that it&#8217;s encrypted and you can&#8217;t see the passwords anymore.<br />
If you want to edit the file do <strong>ansible-vault edit secrets.yaml</strong> and enter the vault password.<br />
Create a small playbook that displays the password.</p>
<pre class="brush: yaml; title: ; notranslate">
# centos-vault.yml
---
- hosts: centos
  vars_files:
    - secrets.yml

  tasks:
  - name: Show mysql pwd
    debug:
      msg: &quot;{{ mysql_pwd }}&quot;
</pre>
<p>If you run the playbook now, ansible will throw an error saying ERROR! Attempting to decrypt but no vault secrets found. You have to specify the parameter <strong>&#8211;ask-vault-pass</strong> and enter the vault password when prompted.</p>
<h1>Prompts</h1>
<p>In case you need to pause the playbook execution and ask the user for some input such as confirmation or password, use prompts.<br />
If you want the output to echo, use <strong>private: no</strong>, otherwise what you type won&#8217;t show up on the screen. Here is an example of a playbook that asks you to confirm if a file needs to be copied to the node. If you type <strong>yes</strong> and hit enter, the file will be copied, otherwise it won&#8217;t.</p>
<pre class="brush: yaml; title: ; notranslate">
# centos-prompt.yml
---
- hosts: centos
  become: yes

  vars_prompt:
    name: upload
    private: no
    prompt: &quot;Do you want to upload xyz.txt?&quot;

  tasks:
  - name: Upload xyz.txt
    copy:
      src: xyz.txt
      dest: /var/log
    when: upload == &quot;yes&quot;
</pre>
<h1>Useful options</h1>
<pre class="brush: bash; title: ; notranslate">
ansible-playbook &lt;name&gt; --syntax-check # checks the syntax of the playbook
ansible-playbook &lt;name&gt; --check # does a dry run, reports the will-be changes, but the playbook is not executed
ansible-playbook &lt;name&gt; --step # ask to confirm each-step
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2021/02/ansible-quick-start-guide-for-freebsd-centos-and-ubuntu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS, FreeBSD: Secure SSH with fail2ban and blacklistd with MFA (Google Authenticator)</title>
		<link>https://blog.andreev.it/2020/03/centos-freebsd-secure-ssh-with-fail2ban-and-blacklistd-with-mfa-google-authenticator/</link>
					<comments>https://blog.andreev.it/2020/03/centos-freebsd-secure-ssh-with-fail2ban-and-blacklistd-with-mfa-google-authenticator/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Fri, 27 Mar 2020 18:57:52 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[FreeBSD]]></category>
		<category><![CDATA[blacklistd]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[fail2ban]]></category>
		<category><![CDATA[freebsd]]></category>
		<category><![CDATA[Google Authenticator]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MFA]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=6612</guid>

					<description><![CDATA[I have some servers that are exposed to the Internet on port 22 and&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>I have some servers that are exposed to the Internet on port 22 and I see a lot of brute force attacks coming. The easiest and best way to protect is to block port 22 for everyone except a handful of IPs or safe subnets. But sometimes, these servers need to be available for some other people who work from who knows where, so you can&#8217;t just restrict IPs anymore. In this case, the best practice is to enforce MFA (multi factor authentication). But, this won&#8217;t work if you have some service accounts that need to log to the server. You can&#8217;t MFA a service account.To balance between security and availability, you can also install some tools that will block the repeated offenders, IPs that crawl the network and try to login to your network over SSH. In this post I&#8217;ll describe how to use fail2ban for CentOS and blacklistd for FreeBSD. fail2ban for FreeBSD is too complex to setup while blacklistd is a native tool and does the same job. </p>
<h1>CentOS</h1>
<p>I have a CentOS box with both <strong>SELinux </strong>and <strong>firewalld </strong>enabled. I am not using keys to log to the server, I have a generic user that uses password and the root is NOT allowed to log to the server using ssh (<strong>PermitRootLogin no in /etc/ssh/sshd_config</strong>).</p>
<h2>Log with SSH keys (optional)</h2>
<p>If you want to have some users that need to log with SSH keys, you have to do the following. Log as that user and run <strong>ssh-keygen</strong> and hit enter for the defaults.</p>
<pre class="brush: bash; highlight: [1,3,5,6]; title: ; notranslate">
ssh-keygen -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/home/test/.ssh/id_rsa):
Created directory '/home/test/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_rsa.
Your public key has been saved in /home/test/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:CmYzEZq7u4IilphSEC/cdTFKQY2+fjQgkw8acCXZjIo test@localhost.localdomain
The key's randomart image is:
+---&#x5B;RSA 4096]----+
|  .*+++o.        |
|o ++++.o.        |
|+=+ =..          |
|Eo.B +           |
| o+ @ o S        |
| ..+ * +         |
|ooo . o .        |
|Oo . . .         |
|=.o.  .          |
+----&#x5B;SHA256]-----+
</pre>
<p>You have two files created for the <strong>test </strong>user, a private (<strong>id_rsa</strong>) and a public (<strong>id_rsa.pub</strong>) file.<br />
You need the private file in order to use it with putty. But putty uses it&#8217;s own PPK file, so you have to convert that with <a href="https://www.puttygen.com/convert-pem-to-ppk">puttygen</a>. You can use the Windows version or you can install putty and convert it. <strong>NOTE</strong>: putty is a GUI program so don&#8217;t install it on a server because it will install a bunch of GUI libraries.<br />
Install epel-release repo.</p>
<pre class="brush: bash; title: ; notranslate">
yum install -y epel-release
</pre>
<p>&#8230;and then install putty.</p>
<pre class="brush: bash; title: ; notranslate">
yum install -y putty
</pre>
<p>Log as the user and convert the file from PEM to PPK. </p>
<pre class="brush: bash; title: ; notranslate">
puttygen .ssh/id_rsa -o private.ppk -O private
</pre>
<p>Your key will be in the home directory as <strong>private.ppk</strong>. Use this key to log to your server. But, you have to authorize your public key first. Logged as test user do:</p>
<pre class="brush: bash; title: ; notranslate">
cat .ssh/id_rsa.pub &gt;&gt; .ssh/authorized_keys
chmod 0600 .ssh/authorized_keys
</pre>
<p>Now, you can log as test user with a key using putty. This will affect only the <strong>test </strong>user to log with a key. The other users will continue logging as they used to do before. Mind that at this point you can log as a test user using a key and the password. If you want to completely disable password logins make sure that these settings are configured under <strong>/etc/ssh/sshd_config</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
</pre>
<p>Restart sshd (<strong>systemctl restart sshd</strong>) anytime you make a change in that file.</p>
<h2>Google Authenticator</h2>
<p>In order to enable MFA, we&#8217;ll have to install Google Authenticator on the server and a client on a phone or a desktop. I use <a href="https://authy.com/" rel="noopener noreferrer" target="_blank">authy </a>for that. You can use any MFA client you want on your phone. I will also use key only logins and no passwords so go ahead and change those 3 lines above in <strong>sshd_config</strong>.  Also, make sure that you can login with at least one user that can sudo or you&#8217;ll be locked. The only way to get in will be thru the console which does not use ssh so it&#8217;s not affected.<br />
To install Google Authenticator, you need the epel repo. Skip this step if you already completed any of the previous steps.</p>
<pre class="brush: bash; title: ; notranslate">
yum install -y epel-release
</pre>
<p>Then install Google Authenticator.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install google-authenticator
</pre>
<p>Now, you can go two ways. Mandate that all accounts use MFA or only certain ones. It depends on your server usage case. If you have some automated scripts that use SSH to do some work, the first option is not viable. For each account that you want to use MFA, you have to configure the authenticator. I&#8217;ll use my <strong>test </strong>account. Logged as the test user, type <strong>google-authenticator</strong>. I answered <strong>y</strong> on all of the questions. </p>
<pre class="brush: bash; highlight: [1,2,15,19,30,35]; title: ; notranslate">
google-authenticator
Do you want authentication tokens to be time-based (y/n) y
Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/test@localhost.l....
                                                                                
Your new secret key is: HF52123FWAGRKLGX5EJ4567CLE
Your verification code is 880933
Your emergency scratch codes are:
  94310385
  95472197
  98736692
  31351434
  83561212

Do you want me to update your &quot;/home/test/.google_authenticator&quot; file? (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

By default, a new token is generated every 30 seconds by the mobile app.
In order to compensate for possible time-skew between the client and the server,
we allow an extra token before and after the current time. This allows for a
time skew of up to 30 seconds between authentication server and client. If you
experience problems with poor time synchronization, you can increase the window
from its default size of 3 permitted codes (one previous code, the current
code, the next code) to 17 permitted codes (the 8 previous codes, the current
code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
between client and server.
Do you want to do so? (y/n) y

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting? (y/n) y
</pre>
<p>In a console session you&#8217;ll see a barcode but it&#8217;s garbled. Instead scroll up and you&#8217;ll see a URL (line 4). Go to that URL and scan the barcode with your MFA app.<br />
Open <strong>/etc/ssh/sshd_config</strong>, scroll all the way down and add this line as the last line.</p>
<pre class="brush: bash; title: ; notranslate">
AuthenticationMethods publickey,password publickey,keyboard-interactive
</pre>
<p>Then change this line to <strong>yes</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
ChallengeResponseAuthentication yes
</pre>
<p>Edit <strong>/etc/pam.d/sshd</strong> and after these two lines, add the <strong>auth </strong>line.</p>
<pre class="brush: bash; highlight: [3]; title: ; notranslate">
# Used with polkit to reauthorize users in remote sessions
-session   optional     pam_reauthorize.so prepare
auth required pam_google_authenticator.so nullok
</pre>
<p><strong>nullok </strong>means that you are allowing some accounts to log as usual, without MFA. If you remove <strong>nullok</strong>, all accounts will be required to use MFA.<br />
Also, comment this line like this.</p>
<pre class="brush: bash; title: ; notranslate">
#auth       substack     password-auth
</pre>
<p>Restart the <strong>sshd </strong>daemon now.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart sshd
</pre>
<p>At this point, you&#8217;ll be able to login only with a public key and an MFA for the users that have google-authenticator and a public key configured. The password users won&#8217;t be able to log in. The console logins are not affected. So, if you have some users without a public key and google-authneticator, they&#8217;ll still be able to login with a password from a console.</p>
<h2>fail2ban</h2>
<p>In order to install <a href="https://www.fail2ban.org/wiki/index.php/Main_Page" rel="noopener noreferrer" target="_blank">fail2ban </a>you have to install the <strong>epel-release</strong> first. Skip this step if you followed the previous step because you already installed epel repo.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install epel-release
</pre>
<p>Then install fail2ban.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install fail2ban
</pre>
<p>Make sure it starts on boot.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable fail2ban
</pre>
<p>The config files for fail2ban are under <strong>/etc/fail2ban</strong>, but you shouldn&#8217;t change any of the existing files. Instead create a new file called <strong>sshd.conf</strong> under <strong>/etc/fail2ban/jail.d</strong> directory. Here is mine.</p>
<pre class="brush: bash; title: ; notranslate">
&#x5B;sshd]
enabled = true

# &quot;ignoreip&quot; can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban
# will not ban a host which matches an address in this list. Several addresses
# can be defined using space (and/or comma) separator.
ignoreip = 127.0.0.1/8

# &quot;bantime&quot; is the number of seconds that a host is banned.
# add m for minutes or hr for hours
bantime  = 10m

# A host is banned if it has generated &quot;maxretry&quot; during the last &quot;findtime&quot;
# seconds.
findtime = 300

# &quot;maxretry&quot; is the number of failures before a host get banned.
maxretry = 3
</pre>
<p>It is very self explanatory with the included comments. In my example, I&#8217;ll ban an IP for 10 minutes if I see that IP try and fail 3 times to login in 300 seconds (5 mins). You can start/restart the service now.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start fail2ban
</pre>
<p>The fail2ban log file is under <strong>/var/log/fail2ban.log</strong> and the SSH logs are under <strong>/var/log/secure</strong>.<br />
Most of the hits that you&#8217;ll see in the <strong>/var/log/secure</strong> are IPs that will try to log as admin or root, but only once in a hour or two. So, they won&#8217;t be caught with these settings. You should add all of your subnets under <strong>ignoreip </strong>setting and change the <strong>maxretry </strong>to 1 and <strong>bantime </strong>to 8760h (1 year). It will ultimately block the IP for a year for anyone that makes any unauthorized attempt. It&#8217;s up to you what settings you use.<br />
Here is an example in the <strong>/var/log/secure</strong> of an IP (3.92.82.165) trying to log as root.</p>
<pre class="brush: bash; title: ; notranslate">
Mar 21 10:26:13 localhost sshd&#x5B;13207]: pam_succeed_if(sshd:auth): requirement &quot;uid &gt;= 1000&quot; not met by user &quot;root&quot;
Mar 21 10:26:15 localhost sshd&#x5B;13207]: Failed password for root from 3.92.82.165 port 60362 ssh2
Mar 21 10:26:16 localhost unix_chkpwd&#x5B;13211]: password check failed for user (root)
Mar 21 10:26:16 localhost sshd&#x5B;13207]: pam_succeed_if(sshd:auth): requirement &quot;uid &gt;= 1000&quot; not met by user &quot;root&quot;
Mar 21 10:26:18 localhost sshd&#x5B;13207]: Failed password for root from 3.92.82.165 port 60362 ssh2
Mar 21 10:26:18 localhost sshd&#x5B;13207]: Connection closed by 3.92.82.165 port 60362 &#x5B;preauth]
Mar 21 10:26:18 localhost sshd&#x5B;13207]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=ec2-3-92-82-165.compute-1.amazonaws.com  user=root
</pre>
<p>&#8230;and the IP got banned. You can see that from /var/log/fail2ban.</p>
<pre class="brush: bash; title: ; notranslate">
2020-03-21 10:26:12,259 fail2ban.filter         &#x5B;13131]: INFO    &#x5B;sshd] Found 3.92.82.165 - 2020-03-21 10:26:12
2020-03-21 10:26:15,382 fail2ban.filter         &#x5B;13131]: INFO    &#x5B;sshd] Found 3.92.82.165 - 2020-03-21 10:26:15
2020-03-21 10:26:18,887 fail2ban.filter         &#x5B;13131]: INFO    &#x5B;sshd] Found 3.92.82.165 - 2020-03-21 10:26:18
2020-03-21 10:26:19,286 fail2ban.actions        &#x5B;13131]: NOTICE  &#x5B;sshd] Ban 3.92.82.165
</pre>
<p>You can also see the banned IPs using this command. <strong>sshd </strong>is the name of the jail. If you scroll up, you&#8217;ll see that I named my jail like that <strong>[sshd]</strong> in the conf file above.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     8
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 2
   |- Total banned:     2
   `- Banned IP list:   3.81.85.240 3.92.82.165
</pre>
<p>But mistakes happen. If someone gets banned, you can unban the IP with this command, where sshd is the jail name and the IP you want unbanned.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
fail2ban-client set sshd unbanip 3.92.82.165
1
</pre>
<p>You should get 1 as an output. Means OK, True. If you get 0, it means the IP wasn&#8217;t there. If you can&#8217;t see all banned IPs, do:</p>
<pre class="brush: bash; title: ; notranslate">
zgrep 'Ban' /var/log/fail2ban.log*
</pre>
<h1>FreeBSD</h1>
<p>I have a FreeBSD box with no firewall enabled for now. For the <strong>blacklistd </strong>part, I&#8217;ll use <strong>pf</strong>. If you use <strong>ipfirewall </strong>or <strong>ipfilter</strong>, then you have to find another guide. I am not using keys to log to the server, I have a generic user that uses password and the root is NOT allowed to log to the server using ssh (<strong>PermitRootLogin no</strong> in <strong>/etc/ssh/sshd_config</strong>).</p>
<h2>Log with SSH keys (optional)</h2>
<p>If you want to have some users that need to log with SSH keys, you have to do the following. Log as that user and run <strong>ssh-keygen</strong> and hit enter for the defaults.</p>
<pre class="brush: bash; highlight: [1,3,5]; title: ; notranslate">
ssh-keygen -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/home/test/.ssh/id_rsa):
Created directory '/home/test/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_rsa.
Your public key has been saved in /home/test/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:rT2m3a/KrC/jk1lxHUPMwuXM5g/5LpjkurrC29EO1z8 test@bsd.andreev.local
The key's randomart image is:
+---&#x5B;RSA 4096]----+
|                 |
|                 |
|           .     |
|        ..o o    |
|     . =S..+     |
|.   . + B+.      |
|o. o + @+.+o     |
|..o * o.OE+o     |
| ..o.+o.+@@.oo.  |
+----&#x5B;SHA256]-----+
</pre>
<p>You have two files created for the test user, a private (<strong>id_rsa</strong>) and a public (<strong>id_rsa.pub</strong>) file.<br />
You need the private file in order to use it with putty. But putty uses it&#8217;s own PPK file, so you have to convert that with <a href="https://www.puttygen.com/convert-pem-to-ppk">puttygen</a>. You can use the Windows version or you can install putty and convert it. </p>
<pre class="brush: bash; title: ; notranslate">
pkg install putty-nogtk
</pre>
<p>Log as the user and convert the file from PEM to PPK. </p>
<pre class="brush: bash; title: ; notranslate">
puttygen .ssh/id_rsa -o private.ppk -O private
</pre>
<p>Your key will be in the home directory as <strong>private.ppk</strong>. Use this key to log to your server. But, you have to authorize your public key first. Logged as test user do:</p>
<pre class="brush: bash; title: ; notranslate">
cat .ssh/id_rsa.pub &gt;&gt; .ssh/authorized_keys
chmod 0600 .ssh/authorized_keys
</pre>
<p>Now, you can log as test user with a key using putty. This will affect only the <strong>test </strong>user to log with a key. The other users will continue logging as they used to do before. Mind that at this point you can log as a test user using a key and the password. If you want to completely disable password logins make sure that these settings are configured under <strong>/etc/ssh/sshd_config</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
</pre>
<p>Restart sshd <strong>(service sshd restart)</strong>  anytime you make a change in that file.</p>
<h2>Google Authenticator</h2>
<p>In order to enable MFA, we&#8217;ll have to install Google Authenticator on the server and a client on a phone or a desktop. I use <a href="https://authy.com/" rel="noopener noreferrer" target="_blank">authy </a>for that. You can use any MFA client you want on your phone. I will also use key only logins and no passwords so go ahead and change those 3 lines above in <strong>sshd_config</strong>. Also, make sure that you can login with at least one user that can sudo or you’ll be locked. The only way to get in will be thru the console which does not use ssh so it’s not affected. To install Google Authenticator type:</p>
<pre class="brush: bash; title: ; notranslate">
pkg install pam_google_authenticator
</pre>
<p>Now, you can go two ways. Mandate that all accounts use MFA or only certain ones. It depends on your server usage case. If you have some automated scripts that use SSH to do some work, the first option is not viable. For each account that you want to use MFA, you have to configure the authenticator. I&#8217;ll use my <strong>test </strong>account. Logged as the test user, type <strong>google-authenticator</strong>. I answered <strong>y</strong> on all of the questions and <strong>-1</strong> to skip. </p>
<pre class="brush: bash; highlight: [1,2,6,15,19,30,35]; title: ; notranslate">
$ google-authenticator
Do you want authentication tokens to be time-based (y/n) y
Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/test@bsd.andreev.local%3Fsecret%3D24345SOFKCTVX44YXYZVYOQKCA%26issuer%3Dbsd.andreev.local                                                                                                                                                         
Your new secret key is: 24HJ3SOFKCTVZ33YVBKVYOKEDB
Enter code from app (-1 to skip): -1
Code confirmation skipped
Your emergency scratch codes are:
  34344229
  32681543
  80840666
  32593123
  29994070

Do you want me to update your &quot;/home/test/.google_authenticator&quot; file? (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

By default, a new token is generated every 30 seconds by the mobile app.
In order to compensate for possible time-skew between the client and the server,
we allow an extra token before and after the current time. This allows for a
time skew of up to 30 seconds between authentication server and client. If you
experience problems with poor time synchronization, you can increase the window
from its default size of 3 permitted codes (one previous code, the current
code, the next code) to 17 permitted codes (the 8 previous codes, the current
code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
between client and server.
Do you want to do so? (y/n) y

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting? (y/n) y
</pre>
<p>In a console session you&#8217;ll see a barcode but it&#8217;s garbled. Instead scroll up and you&#8217;ll see a URL (line 4). Go to that URL and scan the barcode with your MFA app.<br />
Now, log as root or sudo to open <strong>/etc/ssh/sshd_config</strong> file. Change this line to <strong>yes</strong>. </p>
<pre class="brush: bash; title: ; notranslate">
ChallengeResponseAuthentication yes
</pre>
<p>Scroll all the way down and add this line as the last line.</p>
<pre class="brush: bash; title: ; notranslate">
AuthenticationMethods publickey,password publickey,keyboard-interactive
</pre>
<p>Edit <strong>/etc/pam.d/sshd</strong> and after these two lines, add the last <strong>auth </strong>line but commend the line above so it looks like this.</p>
<pre class="brush: bash; highlight: [3]; title: ; notranslate">
#auth           sufficient      pam_ssh.so              no_warn try_first_pass
#auth            required        pam_unix.so             no_warn try_first_pass
auth            required        pam_google_authenticator.so nullok
</pre>
<p><strong>nullok </strong>means that you are allowing some accounts to log as usual, without MFA. If you remove <strong>nullok</strong>, all accounts will be required to use MFA. Restart the <strong>sshd </strong>daemon now.</p>
<pre class="brush: bash; title: ; notranslate">
service sshd restart
</pre>
<p>At this point, you’ll be able to login only with a public key and an MFA for the users that have google-authenticator and a public key configured. The password users won’t be able to log in. The console logins are not affected. So, if you have some users without a public key and google-authneticator, they’ll still be able to login with a password from a console.<br />
If you don&#8217;t want to use public key and prefer passwords and MFA, then remove the last line from <strong>sshd_config AuthenticationMethod</strong>s and uncomment <strong>pam_unix.so</strong> in <strong>/etc/pam.d/sshd</strong> above <strong>google_authenticator</strong> line. Restart sshd.</p>
<h2>blacklistd</h2>
<p>fail2ban is available for FreeBSD, but it takes some time to configure it right with the proper firewalls. Instead, I use blacklistd which comes preinstalled. The only prerequisite is a firewall and I&#8217;ll be using pf for that. So, first let&#8217;s enable pf and blacklistd by adding these lines in <strong>/etc/rc.conf</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
pf_enable=&quot;YES&quot;
pflog_enable=&quot;YES&quot;
blacklistd_enable=&quot;YES&quot;
blacklistd_flags=&quot;-r&quot;
</pre>
<p>Let&#8217;s create a very simple pf config file <strong>/etc/pf.conf</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
ext_if = &quot;vmx0&quot;
set skip on lo0
anchor &quot;blacklistd/*&quot; in on $ext_if
table &lt;blocked_subnets&gt; persist file &quot;/etc/blocked_subnets&quot;
block all
block in log quick on $ext_if from &lt;blocked_subnets&gt; to any
block out log quick on $ext_if from any to &lt;blocked_subnets&gt;
pass in on $ext_if proto tcp from any to any port {ssh} keep state
pass out quick on $ext_if keep state
</pre>
<p>Make sure you replace the name of your network card under line #1. You can get the name from <strong>ifconfig </strong>output. Also, if you want to block certain subnets by default, you can list them under <strong>/etc/blocked_subnets </strong>file. The subnet(s) should be in CIDR format. For example, if you want to block certain countries, you can look at this <a href="https://www.countryipblocks.net/acl.php" rel="noopener noreferrer" target="_blank">website</a>. BTW, the subnets blocking is part of pf not blacklistd. You can reboot now to make sure everything comes back OK and you are able to login. But before doing that, edit the <strong>sshd </strong>daemon and let it know that we&#8217;ll use <strong>blacklistd</strong>. Find <strong>UseBlacklist </strong>in <strong>/etc/ssh/sshd_conf</strong> and change it to:</p>
<pre class="brush: bash; title: ; notranslate">
UseBlacklist yes
</pre>
<p>The reboot will start pf and blacklistd. The blacklistd conf file is <strong>/etc/blacklistd.conf</strong>. You can read more about blacklistd <a href="https://www.freebsd.org/doc/handbook/firewalls-blacklistd.html" rel="noopener noreferrer" target="_blank">here</a>. For example, if I want to whitelist certain subnets from being blacklisted, I&#8217;ll add them under the <strong>[remote]</strong> section. In my case, I am whitelisting my home subnet and an external subnet.</p>
<pre class="brush: bash; title: ; notranslate">
&#x5B;remote]
172.16.1.0/24:ssh      *       *       *               =       *       *
154.21.3.0/24:ssh      *       *       *               =       *       *
</pre>
<p>You can see your SSH activity in <strong>/var/log/auth.log.</strong> To see who is blocked, run:</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
blacklistctl dump -br
        address/ma:port id      nfail   remaining time
  213.74.176.36/32:22   OK      8/3     23h58m57s
</pre>
<p>This IP will be blocked for 24hrs by default. You can change that under the <strong>disable </strong>column in <strong>/etc/blacklistd.conf</strong>.<br />
You can also see the blocked IPs by looking at the pf <strong>blacklistd </strong>table.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
pfctl -a blacklistd/22 -t port22 -T show
   213.74.176.36
</pre>
<p>And if you want to remove an IP or a subnet do:</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
pfctl -a blacklistd/22 -t port22 -T delete 213.74.176.36/32
1/1 addresses deleted.
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2020/03/centos-freebsd-secure-ssh-with-fail2ban-and-blacklistd-with-mfa-google-authenticator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Create disposable e-mail addresses using postfix, dovecot and squirrelmail</title>
		<link>https://blog.andreev.it/2020/01/centos-create-disposable-e-mail-addresses-using-postfix-dovecot-and-squirrelmail/</link>
					<comments>https://blog.andreev.it/2020/01/centos-create-disposable-e-mail-addresses-using-postfix-dovecot-and-squirrelmail/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 14 Jan 2020 19:51:37 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[disposable e-mail]]></category>
		<category><![CDATA[dovecot]]></category>
		<category><![CDATA[postfix]]></category>
		<category><![CDATA[squirrelmail]]></category>
		<category><![CDATA[temp e-mail]]></category>
		<category><![CDATA[temporary e-maill]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=6425</guid>

					<description><![CDATA[There are many sites that offer this functionality (temp-mail.org, guerrillamail.org, throwawaymail.com), but if you&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>There are many sites that offer this functionality (temp-mail.org, guerrillamail.org, throwawaymail.com), but if you want to build your own solution from scratch, I&#8217;ll show how I managed to accomplish this. I have a CentOS 7 server running in a cloud with a public IP and a valid domain name. You won&#8217;t need any SSL certificates, but you can add them if you want. This is not a complete guide on how to create a valid postfix and dovecot e-mail solution. If you are looking for that, stop and look further. The solution described here uses just the basic functionalities to send and receive e-mails. I&#8217;ll create some small scripts to manage the temporary e-mail addresses and you can use <strong>squirrelmail </strong>to check your e-mails. Actually, if you want you can skip that part and use some other e-mail POP3/IMAP e-mail client to check your e-mails.</p>
<h1>Valid domain and MX records</h1>
<p>You will need a valid domain for this. The scripts cover multiple virtual domains, so if you have many domains, you can use them as well. In the DNS for your domain make sure you have a valid MX record that points to your server. I use Route 53 and this is how my records look like.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2020/01/P144-01.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2020/01/P144-01.png" alt="" width="443" height="76" class="aligncenter size-full wp-image-6428" srcset="https://blog.andreev.it/wp-content/uploads/2020/01/P144-01.png 443w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-01-300x51.png 300w" sizes="(max-width: 443px) 100vw, 443px" /></a><br />
Make sure you have the MX record set properly. Go to this <a href="https://mxtoolbox.com/" rel="noopener noreferrer" target="_blank">site </a>and type your domain name. I&#8217;ll use my domain called <strong>cloudranger.live</strong>. This is how it looks like.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2020/01/P144-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2020/01/P144-02.png" alt="" width="1205" height="288" class="aligncenter size-full wp-image-6429" srcset="https://blog.andreev.it/wp-content/uploads/2020/01/P144-02.png 1205w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-02-300x72.png 300w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-02-1024x245.png 1024w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-02-768x184.png 768w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-02-1170x280.png 1170w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-02-585x140.png 585w" sizes="(max-width: 1205px) 100vw, 1205px" /></a><br />
Don&#8217;t worry about the DMARC error, we won&#8217;t use that. As you can see from both screenshots above, the IPs from the DNS and the tool match, which means your e-mail server will be properly configured on the Internet to receive e-mails. </p>
<h1>Firewall and SELinux</h1>
<p>If you use firewall, make sure you have these ports opened: 25 for SMTP, 143 for IMAP or 110 for POP3 and 80 for squirrelmail/Apache.<br />
SELinux can stay as is, but if you use squirrelmail, you&#8217;ll have to disable it in <strong>/etc/selinux/config</strong>. Or temporarily, do:</p>
<pre class="brush: bash; title: ; notranslate">
setenforce 0
</pre>
<h1>postfix Part I</h1>
<p>We&#8217;ll use postfix as MTA. It comes preinstalled on CentOS, but it runs on the localhost interface only, so we have to make some changes. The postfix daemon is running under the postfix user, but later we&#8217;ll see that because this users UID is lower than 1000, we can&#8217;t use it with dovecot. Actually, we can, but it&#8217;s not recommended. We&#8217;ll create a user called <strong>vpostfix</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
groupadd vpostfix &amp;&amp; useradd vpostfix -g vpostfix -s /sbin/nologin -c &quot;Virtual postfix user&quot; -d /var/empty
</pre>
<p>This will create a user and a group called vpostfix. Let&#8217;s get the <strong>UID </strong>and <strong>GID</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
grep vpostfix /etc/passwd &amp;&amp; grep vpostfix /etc/group
vpostfix:x:1002:1002:Virtual postfix user:/var/empty:/sbin/nologin
vpostfix:x:1002:
</pre>
<p>The <strong>UID </strong>and the <strong>GID </strong>are 1002. Write down this number. Now, edit the main configuration file for postfix which is <strong>/etc/postfix/main.cf</strong>. Make a copy first for the config file in case something goes wrong.</p>
<pre class="brush: bash; title: ; notranslate">
cd /etc/postfix
cp main.cf main.cf.ORIG
</pre>
<p>Then edit <strong>main.cf</strong> and search for <strong>inet_interfaces</strong>. <strong>IMPORTANT</strong>! Uncomment <strong>inet_interfaces = all</strong> and add a comment for i<strong>net_interfaces = localhost</strong>. If you don&#8217;t put comment in front of &#8230;localhost it will override the &#8230;all.</p>
<pre class="brush: bash; title: ; notranslate">
inet_interfaces = all
#inet_interfaces = $myhostname
#inet_interfaces = $myhostname, localhost
#inet_interfaces = localhost
</pre>
<p>Find and uncomment this line.</p>
<pre class="brush: bash; title: ; notranslate">
#home_mailbox = Maildir/
</pre>
<p>Then add these lines at the end of the file.</p>
<pre class="brush: bash; title: ; notranslate">
# Virtual domains, users, and aliases
virtual_mailbox_domains = mysql:/etc/postfix/virtual_domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/virtual_users.cf
virtual_mailbox_base = /var/mail/virtual_domains
# Make sure you replace these UID:GID numbers
virtual_minimum_uid = 1002
virtual_uid_maps = static:1002
virtual_gid_maps = static:1002
virtual_transport = lmtp:unix:private/dovecot
</pre>
<p>So, we&#8217;ll use virtual domains that will be defined in a MySQL database, our temp/disposable e-mail addresses will be also stored in a MySQL database and we&#8217;ll have a directory <strong>/var/mail/virtual_domains</strong> where we&#8217;ll get the e-mails in a <strong>Maildir </strong>format. Make sure you replace those numbers that you got for the postfix user. The last line is used to override the actual postfix delivery and use dovecot for that.<br />
Let&#8217;s create the directory where e-mails will be stored.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /var/mail/virtual_domains
chown -R vpostfix:vpostfix /var/mail/virtual_domains
</pre>
<p>Leave postfix as is for now and let&#8217;s proceed with MySQL database.</p>
<h1>MySQL</h1>
<p>I prefer to use MariaDB, so we&#8217;ll install that first.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install mariadb-server
</pre>
<p>Enable the daemon on boot and start it.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable mariadb
systemctl start mariadb
</pre>
<p>Do an initial config.</p>
<pre class="brush: bash; title: ; notranslate">
mysql_secure_installation
</pre>
<p>Pretty much, accept the defaults with ENTER and just enter the new <strong>root </strong>database password.<br />
Now, we have to create the database and two tables for the virtual domains and the virtual users as specified in lines 2 and 3 in the <strong>main.cf</strong> file above. Copy and paste these lines but change the password in line 3. That is your password for the postfix database user. This DB user has nothing to do with postfix account in <strong>/etc/passwd </strong>that runs postfix. You can change the name if you want, but you have to remember it later when we do the scripts.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; /tmp/temp.sql
CREATE DATABASE db_postfix;
GRANT ALL PRIVILEGES ON db_postfix.* TO &quot;postfix&quot;@&quot;localhost&quot; IDENTIFIED BY &quot;your_password&quot;;

use db_postfix;

CREATE TABLE virtual_domains (
  id INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE(name)
);

CREATE TABLE virtual_users (
  id int(11) NOT NULL AUTO_INCREMENT,
  domain_id INT(11) NOT NULL,
  password VARCHAR(106) NOT NULL,
  email VARCHAR(100) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY email (email),
  FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) DEFAULT CHARSET=utf8;
EOF
</pre>
<p>Run the script.</p>
<pre class="brush: bash; title: ; notranslate">
mysql -u root -p &lt; /tmp/temp.sql
</pre>
<p>You can check the tables. Log to the database as the postfix with the password you specified in line 3.</p>
<pre class="brush: bash; title: ; notranslate">
mysql -u postfix -p
</pre>
<p>Then switch to our new database <strong>db_postfix</strong> defined in line 2.</p>
<pre class="brush: bash; title: ; notranslate">
use db_postfix;
</pre>
<p>Show the tables.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
show tables;
+----------------------+
| Tables_in_db_postfix |
+----------------------+
| virtual_domains      |
| virtual_users        |
+----------------------+
2 rows in set (0.00 sec)
</pre>
<p>At this point, we are good with the database. Now we have to create the scripts that will manage the domains and users. You can (of course) use mysql commands to create domains and users, but I&#8217;ve made some scripts that will make this easier.</p>
<h1>Scripts</h1>
<p>These scripts are the backbone of how you&#8217;ll interact when creating domains and users. It simplifies everything for you. So, we&#8217;ll have a couple of scripts/commands and this is their syntax. I&#8217;ll put them under <strong>/usr/local/bin</strong> so if you run these commands as root, this directory is not in the path. But under CentOS, if you have any non-root user, <strong>/usr/local/bin</strong> is already in the <strong>$PATH</strong>. So, do <strong>echo $PATH</strong> and see if you can execute them without specifying the full path.<br />
There are 8 scripts in total. If you run them without any parameters, they&#8217;ll give you the syntax. This is a brief description.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2020/01/P144-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2020/01/P144-03.png" alt="" width="719" height="181" class="aligncenter size-full wp-image-6438" srcset="https://blog.andreev.it/wp-content/uploads/2020/01/P144-03.png 719w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-03-300x76.png 300w, https://blog.andreev.it/wp-content/uploads/2020/01/P144-03-585x147.png 585w" sizes="(max-width: 719px) 100vw, 719px" /></a><br />
Here is the kick. These scripts call the mysql utility and you have to provide a password for every interaction with the database. You can easily modify them and specify <strong>-p</strong> parameter in the script and type the password there, but that&#8217;s not recommended. So, you have to either specify the postfix DB user password all the time&#8230; or, my suggestion is to export the postfix password in a variable that only lasts for the duration of your session.<br />
So, before running any of the scripts, I recommend that you export your password like this. This is the password for the postfix DB user, not the root DB user.</p>
<pre class="brush: bash; title: ; notranslate">
export MYSQL_PWD=your_password
</pre>
<p>Once you do this, you can run any of the scripts without any prompts. Copy and paste the following scripts.<br />
<strong>IMPORTANT! </strong>If you modified the DB name or any table name from my script (<strong>/tmp/temp.sql</strong>), you have to modify these scripts as well.<br />
==================================================<br />
<strong>add_domain</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/add_domain
#!/bin/bash
usage () {
    echo &quot;Usage: add_domain &lt;domain_name&gt;&quot;
}

if &#x5B; $# -ne 1 ] ; then
    usage
    exit 1
fi

if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi

/usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;INSERT INTO db_postfix.virtual_domains (name) VALUES ('$1');&quot;
EOF
</pre>
<p><strong>add_user</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/add_user
#!/bin/bash
if &#x5B; $# -ne 2 ] ; then
    echo &quot;Usage: add_user &lt;e-mail&gt; &lt;password&gt;&quot;
    exit 1
fi

if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi
regex=&quot;^&#x5B;a-z0-9!#\$%&amp;'*+/=?^_\`{|}~-]+(\.&#x5B;a-z0-9!#$%&amp;'*+/=?^_\`{|}~-]+)*@(&#x5B;a-z0-9](&#x5B;a-z0-9-]*&#x5B;a-z0-9])?\.)+&#x5B;a-z0-9](&#x5B;a-z0-9-]*&#x5B;a-z0-9])?\$&quot;

if &#x5B;&#x5B; $1 =~ $regex ]] ; then
    DOMAIN=`echo $1 | cut -d'@' -f2`
else
    echo &quot;Not a valid e-mail address.&quot;
    exit 1
fi
ID=$(/usr/bin/mysql -u postfix db_postfix -s $OPTION -N -e &quot;SELECT id from db_postfix.virtual_domains WHERE NAME='$DOMAIN';&quot;)
if &#x5B; -z &quot;$ID&quot; ] ; then
    echo &quot;The domain $DOMAIN is missing. Use add_domain $DOMAIN first.&quot;
else
   /usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;INSERT INTO db_postfix.virtual_users (domain_id, email, password) VALUES ($ID, '$1', ENCRYPT('$2', CONCAT('\$6\$', SUBSTRING(SHA(RAND()), -16))));&quot;
fi
EOF
</pre>
<p><strong>ls_domains</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/ls_domains
#!/bin/bash
if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi
/usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;SELECT * FROM virtual_domains;&quot;
EOF
</pre>
<p><strong>ls_users</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/ls_users
#!/bin/bash

if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi

/usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;SELECT * FROM virtual_users;&quot;

EOF
</pre>
<p><strong>rmall_domains</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/rmall_domains
#!/bin/bash
if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi

while true; do
    read -p &quot;All domain records will be deleted. Please confirm &#x5B;yn] &quot; yn
    case $yn in
        &#x5B;Yy]* ) /usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;DELETE FROM db_postfix.virtual_domains&quot;; break;;
        &#x5B;Nn]* ) exit;;
        * ) echo &quot;Answer yes(y) or no(n).&quot;;;
    esac
done
EOF
</pre>
<p><strong>rmall_users</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/rmall_users
#!/bin/bash
if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi

while true; do
    read -p &quot;All user records will be deleted. Please confirm &#x5B;yn] &quot; yn
    case $yn in
        &#x5B;Yy]* ) /usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;DELETE FROM db_postfix.virtual_users&quot;; break;;
        &#x5B;Nn]* ) exit;;
        * ) echo &quot;Answer yes(y) or no(n).&quot;;;
    esac
done

EOF
</pre>
<p><strong>rm_domain</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/rm_domain
#!/bin/bash
if &#x5B; $# -ne 1 ]
  then
    echo &quot;Usage: rm_domain &lt;domain_name&gt;&quot;
    exit
fi
if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi

/usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;DELETE FROM db_postfix.virtual_domains WHERE name = '$1';&quot;

EOF
</pre>
<p><strong>rm_user</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/local/bin/rm_user
#!/bin/bash
if &#x5B; $# -ne 1 ]
  then
    echo &quot;Usage: rm_user &lt;e-mail&gt;&quot;
    exit
fi
if &#x5B; -z &quot;$MYSQL_PWD&quot; ] ; then
    OPTION=&quot;-p&quot;
else
    OPTION=&quot;&quot;
fi

/usr/bin/mysql -u postfix db_postfix $OPTION -e &quot;DELETE FROM db_postfix.virtual_users WHERE email = '$1';&quot;
EOF
</pre>
<p>==================================================<br />
Make the scripts executable.</p>
<pre class="brush: bash; title: ; notranslate">
cd /usr/local/bin
chmod +x add_domain ls_domains rmall_domains rm_domain add_user ls_users rmall_users rm_user
</pre>
<p>Now that we have the scripts let&#8217;s create our first domain and first disposable e-mail address. It&#8217;s very easy.<br />
Log as regular user or add <strong>/usr/local/bin</strong> in the PATH if you run these as root.</p>
<pre class="brush: bash; title: ; notranslate">
export MYSQL_PWD=pwd_for_postfix_user
add_domain cloudranger.live
add_user klimenta@cloudranger.live some_pwd
</pre>
<p>Check what you did.</p>
<pre class="brush: bash; highlight: [1,7]; title: ; notranslate">
ls_domains
+----+------------------+
| id | name             |
+----+------------------+
|  1 | cloudranger.live |
+----+------------------+
ls_users
+----+-----------+----------------------------------------------------------+---------------------------+
| id | domain_id | password                                                 | email                     |
+----+-----------+----------------------------------------------------------+---------------------------+
|  1 |         1 | $6$4asdfasdfadsfasdfasdfadsfasdfadsfasdfasdfadsfa3qw8sF/ | klimenta@cloudranger.live |
+----+-----------+----------------------------------------------------------+---------------------------+
</pre>
<p>The password is hashed. Use the other commands if you want to remove a domain, user, all domains or all users. </p>
<h1>postfix Part II</h1>
<p>Now that we have MySQL ready, you should create two files that will tell postfix how to tie with MySQL. If you look at the postfix <strong>main.cf</strong> file, lines 2 and 3 specify exactly that info. Here are the files. Just copy and paste these, but make sure you change the password in line 2. Log as root or sudo.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; /etc/postfix/virtual_domains.cf
user = postfix
password = password_for_db_user_postfix
hosts = 127.0.0.1
dbname = db_postfix
query = SELECT 1 from virtual_domains WHERE name='%s'
EOF
</pre>
<p>And the same file for the e-mails.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; /etc/postfix/virtual_users.cf
user = postfix
password = password_for_db_user_postfix
hosts = 127.0.0.1
dbname = db_postfix
query = SELECT 1 from virtual_users WHERE email='%s'
EOF
</pre>
<h1>dovecot</h1>
<p>We&#8217;ll use dovecot so we can get the received e-mails as POP3 or IMAP. I&#8217;ll enable both protocols but I&#8217;ll use IMAP later with the squirrelmail. We need to install it first and enable it to boot.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install dovecot dovecot-mysql
systemctl enable dovecot
</pre>
<p>Now, we&#8217;ll configure it. dovecot has one main config file which <strong>/etc/dovecot/dovecot.conf</strong> and separate config files for each functionality under <strong>/etc/dovecot/conf.d</strong> directory. Make a copy of both.</p>
<pre class="brush: bash; title: ; notranslate">
cd /etc/dovecot
cp dovecot.conf dovecot.conf.ORIG
cp -R conf.d conf.d.ORIG
</pre>
<p>Edit <strong>/etc/dovecot/dovecot.conf</strong> and uncomment the following line.</p>
<pre class="brush: bash; title: ; notranslate">
protocols = imap pop3 lmtp
</pre>
<p>Then, copy the template dovecot-sql file. You might have to check the directory where this file is, because the dovecot version number is part of the name.</p>
<pre class="brush: bash; title: ; notranslate">
cd /etc/dovecot
cp /usr/share/doc/dovecot-2.2.36/example-config/dovecot-sql.conf.ext .
</pre>
<p>Edit this file and search for these directives, uncomment them and configure them properly.</p>
<pre class="brush: bash; title: ; notranslate">
#driver =
</pre>
<p>&#8230;should become</p>
<pre class="brush: bash; title: ; notranslate">
driver = mysql
</pre>
<p>Then&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
#connect =
</pre>
<p>&#8230;should become</p>
<pre class="brush: bash; title: ; notranslate">
connect = host=127.0.0.1 dbname=db_postfix user=postfix password=pwd_for_db_postfix_user
</pre>
<p>Again&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
#default_pass_scheme = MD5
</pre>
<p>&#8230;should become</p>
<pre class="brush: bash; title: ; notranslate">
default_pass_scheme = SHA512-CRYPT
</pre>
<p>Scroll down and finally change this.</p>
<pre class="brush: bash; title: ; notranslate">
#password_query = \
#  SELECT username, domain, password \
#  FROM users WHERE username = '%n' AND domain = '%d'
</pre>
<p>&#8230;to this.</p>
<pre class="brush: bash; title: ; notranslate">
password_query = \
  SELECT email as user, password \
  FROM virtual_users WHERE email = '%u'
</pre>
<p>But it&#8217;s not over. We have to make some changes in the other files as well.<br />
Edit each of these files and make these changes under <strong>/etc/dovecot/conf.d</strong>.<br />
====================================================<br />
<strong>10-master.conf</strong><br />
Look for this snippet and make sure&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
service lmtp {
  unix_listener lmtp {
    #mode = 0666
  }
</pre>
<p>&#8230;it&#8217;s like this. The first occurrence for user/group is postfix, the rest are vpostfix. It&#8217;s not a typo.</p>
<pre class="brush: bash; title: ; notranslate">
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot {
    #mode = 0666
    mode = 0600
    user = postfix
    group = postfix
  }
</pre>
<p>Same further down. </p>
<pre class="brush: bash; title: ; notranslate">
  unix_listener auth-userdb {
    #mode = 0666
    #user =
    #group =
  }

  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}
</pre>
<p>Should look like this.</p>
<pre class="brush: bash; title: ; notranslate">
  unix_listener auth-userdb {
    mode = 0600
    user = vpostfix
    group =  vpostfix
  }

  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = vpostfix
    group = vpostfix
  }
</pre>
<p><strong>10-auth.conf</strong><br />
Uncomment these 3 lines.</p>
<pre class="brush: bash; title: ; notranslate">
disable_plaintext_auth = yes
auth_mechanisms = plain
!include auth-sql.conf.ext
</pre>
<p>Then modify <strong>auth_mechanisms = plain</strong> and add <strong>login </strong>so it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
auth_mechanisms = plain login
</pre>
<p><strong>auth-sql.conf.ext</strong><br />
Comment these lines.</p>
<pre class="brush: bash; title: ; notranslate">
userdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
</pre>
<p>So it&#8217;s like this.</p>
<pre class="brush: bash; title: ; notranslate">
#userdb {
#  driver = sql
#  args = /etc/dovecot/dovecot-sql.conf.ext
#}
</pre>
<p>&#8230;and all the way at the bottom.</p>
<pre class="brush: bash; title: ; notranslate">
#userdb {
  #driver = static
  #args = uid=vmail gid=vmail home=/var/vmail/%u
#}
</pre>
<p>should be&#8230;Don&#8217;t just uncomment. Change the <strong>args </strong>line.</p>
<pre class="brush: bash; title: ; notranslate">
userdb {
  driver = static
  args = uid=vpostfix gid=vpostfix home=/var/mail/virtual_domains/%d/%n
}
</pre>
<p><strong>10-logging.conf</strong><br />
Uncomment the <strong>log_path</strong> line and specify a log file for dovecot.</p>
<pre class="brush: bash; title: ; notranslate">
log_path = /var/log/dovecot.log
</pre>
<p><strong>10-mail.conf</strong><br />
Uncomment the <strong>mail_location</strong> and specify where the e-mails will be stored. <strong>%d/%n</strong> means the domain name and the login name, so the path will be something like <strong>/var/mail/virtual_domains/cloudranger.live/klimenta</strong>. dovecot will take care of the directory creation, so don&#8217;t worry about that.</p>
<pre class="brush: bash; title: ; notranslate">
mail_location = maildir:/var/mail/virtual_domains/%d/%n
</pre>
<p>====================================================</p>
<h1>Test e-mail delivery</h1>
<p>At this point we should be able to test our solution. Restart both daemons, so all the changes take effect.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart mariadb
systemctl restart postfix
systemctl restart dovecot
</pre>
<p>Check the logs for both postfix and dovecot. They should look similar to this.</p>
<pre class="brush: bash; title: ; notranslate">
tail /var/log/maillog
Jan 13 16:21:08 ip-172-31-72-148 postfix/postfix-script&#x5B;27817]: stopping the Postfix mail system
Jan 13 16:21:08 ip-172-31-72-148 postfix/master&#x5B;2774]: terminating on signal 15
Jan 13 16:21:08 ip-172-31-72-148 postfix/postfix-script&#x5B;27897]: starting the Postfix mail system
Jan 13 16:21:08 ip-172-31-72-148 postfix/master&#x5B;27899]: daemon started -- version 2.10.1, configuration /etc/postfix
</pre>
<p>&#8230;and</p>
<pre class="brush: bash; title: ; notranslate">
tail /var/log/dovecot.log
Jan 13 16:21:14 master: Info: Dovecot v2.2.36 (1f10bfa63) starting up for imap, pop3, lmtp (core dumps disabled)
</pre>
<p>From another computer send an e-mail to your disposable e-mail account. You&#8217;ll see that it&#8217;s recorded in the postfix log.<br />
This is my <strong>/var/log/maillog</strong>.</p>
<pre class="brush: bash; highlight: [6]; title: ; notranslate">
Jan 13 23:34:26 ip-172-31-86-23 postfix/smtpd&#x5B;23108]: connect from something.domain.com&#x5B;114.102.113.201]
Jan 13 23:34:26 ip-172-31-86-23 postfix/smtpd&#x5B;23108]: ACB334065F8: client=something.domain.com&#x5B;114.102.113.201]
Jan 13 23:34:26 ip-172-31-86-23 postfix/cleanup&#x5B;23116]: ACB334065F8: message-id=&lt;000701d5ca69$fe8e8eb0$fbabac10$@heythere.com&gt;
Jan 13 23:34:26 ip-172-31-86-23 postfix/smtpd&#x5B;23108]: disconnect from something.domain.com&#x5B;114.102.113.201]
Jan 13 23:34:26 ip-172-31-86-23 postfix/qmgr&#x5B;23052]: ACB334065F8: from=&lt;user@heythere.com&gt;, size=2814, nrcpt=1 (queue active)
Jan 13 23:34:26 ip-172-31-86-23 postfix/lmtp&#x5B;23117]: ACB334065F8: to=&lt;klimenta@cloudranger.live&gt;, relay=ip-172-31-86-23.ec2.internal&#x5B;private/dovecot], delay=0.09, delays=0.02/0.01/0.01/0.04, dsn=2.0.0, status=sent (250 2.0.0 &lt;klimenta@cloudranger.live&gt; gDpGLIL+HF5OWgAAbmziKA Saved)
Jan 13 23:34:26 ip-172-31-86-23 postfix/qmgr&#x5B;23052]: ACB334065F8: removed
</pre>
<p>Look at line 6 (scroll to the right). SMTP code 250 means all went well.<br />
And if you go to <strong>/var/mail/virtual_domains/your_domain/your_email</strong>, you&#8217;ll see a bunch of folders created. The e-mail that just arrived is under <strong>new </strong>directory. You can <strong>cat </strong>the file and see your e-mail.</p>
<pre class="brush: bash; highlight: [1,3]; title: ; notranslate">
pwd
/var/mail/virtual_domains/cloudranger.live/klimenta/new
ls -l
total 4
-rw-------. 1 vpostfix vpostfix 2993 Jan 13 23:34 1578958466.M778127P23118.ip-172-31-86-23.ec2.internal,S=2993,W=3076
</pre>
<p>At this point, you can use some IMAP/POP3 client to get the e-mail (see notes at the end). Specify the hostname of the server as your mail server, if asked for port, put 110 for POP and 143 for IMAP and your disposable e-mail/password as username and password. In my case, I don&#8217;t want to deal with constant e-mail client configuration for these temp e-mails, so I installed <strong>squirrelmail</strong>. You can use <strong>Roundcube Mail</strong> if you like sleeker interface, but there is more configuration involved.</p>
<h1>squirrelmail (optional)</h1>
<p>In order to install squirrelmail, you have to install epel-release repo. It also installs Apache and PHP for you. Bear this in mind and if you are not comfortable, stop.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install epel-release
</pre>
<p>Then install squirrelmail.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install squirrelmail
</pre>
<p>Enable Apache on boot.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable httpd
</pre>
<p>Go to <strong>/etc/httpd/conf.d</strong> and replace the <strong>squirrelmail.conf</strong> file. Keep the original file. Just copy and paste.</p>
<pre class="brush: bash; title: ; notranslate">
cd /etc/httpd/conf.d
mv squirrelmail.conf squirrelmail.conf.ORIG
cat &lt;&lt; 'EOF' &gt; /etc/httpd/conf.d/squirrelmail.conf
#
# SquirrelMail is a webmail package written in PHP.
#

Alias /webmail /usr/share/squirrelmail

&lt;Directory &quot;/usr/share/squirrelmail&quot;&gt;
        Options None
        AllowOverride none
        Require all granted
&lt;/Directory&gt;
EOF
</pre>
<p>Let&#8217;s configure it.</p>
<pre class="brush: bash; title: ; notranslate">
cd /usr/share/squirrelmail/config/
./conf.pl
</pre>
<p>Go to #2, then #3. Switch to #2 (SMTP). Go back with <strong>R</strong> and then go to option <strong>D</strong> and type <strong>dovecot</strong>. Finally type <strong>S</strong> to Save data. <strong>Q</strong> to quit.<br />
Start Apache.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start httpd
</pre>
<p>&#8230;and go to your server&#8217;s IP or hostname if you have it in DNS as <em>http://ip_or_hostname/webmail</em>. Log with the disposable e-mail and you&#8217;ll see your mailbox. </p>
<h1>Note about POP3/IMAP clients</h1>
<p>If you want to access your mailbox from a GUI client from another computer, you won&#8217;t be able to do so because of the dovecot configuration. That&#8217;s because dovecot considers localhost connections secure, but any other subnet rather than that is considered unsecure.<br />
In order to make this work, you&#8217;ll have to make changes. But mind that if you make these changes, your password will be sent as a plain text across the network. If you are fine with this, proceed.<br />
Change this line to  <strong>= no</strong> in <strong>/etc/dovecot/conf.d/10-auth.conf</strong></p>
<pre class="brush: bash; title: ; notranslate">
disable_plaintext_auth = yes
</pre>
<p>Change <strong>ssl = required</strong> in <strong>/etc/dovecot/conf.d/10-ssl.conf</strong> to <strong>ssl = no</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
ssl = no
</pre>
<p>Restart dovecot and you should be able to access your mailbox remotely.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2020/01/centos-create-disposable-e-mail-addresses-using-postfix-dovecot-and-squirrelmail/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: squid transparent proxy for DNS filtering/whitelisting</title>
		<link>https://blog.andreev.it/2019/11/aws-squid-transparent-proxy-for-dns-filtering-whitelisting/</link>
					<comments>https://blog.andreev.it/2019/11/aws-squid-transparent-proxy-for-dns-filtering-whitelisting/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 17 Nov 2019 22:38:01 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[intercept]]></category>
		<category><![CDATA[splice]]></category>
		<category><![CDATA[squid]]></category>
		<category><![CDATA[ssl bump]]></category>
		<category><![CDATA[transparent proxy]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=6035</guid>

					<description><![CDATA[If you have some instances running in AWS, most likely they are in the&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>If you have some instances running in AWS, most likely they are in the private subnet and to access Internet they use the Internet Gateway. By default, they can access everything and that&#8217;s a big no-no. You don&#8217;t want to be an open SMTP relay or a Bitcoin node if your instance is compromised. But the Internet Gateway can&#8217;t do the filtering for you. You are able to access everything. Granted, you can modify the rules in the Network ACL, but you can only deny access to IPs, not URLs. The problem with this scenario is that many update sites have IPs that change from time to time or they use CDN networks with a tons of IPs behind. If you whitelist these IPs, then technically you are whitelisting every single site that&#8217;s behind the CDN network such as akamai.<br />
One of the solutions is to use <a href="http://www.squid-cache.org/" rel="noopener noreferrer" target="_blank">squid</a>. squid can act as a cache server and transparent proxy. Transparent proxy means that the instances that are accessing the Internet are not aware that they are behind a proxy, you don&#8217;t have to specify any settings on the instance that&#8217;s accessing the Internet. A transparent proxy is much more easier for the end user/client, they don&#8217;t have to configure anything on their end.<br />
I&#8217;ll show you how to configure a squid transparent proxy that sits in a public subnet in AWS and the instances that are in the private subnets will use squid to access Internet. I&#8217;ll implement whitelisting ACL which means that only allowed sites will be reachable. The rest is blocked. This solution is not for your home/lab network where you want to access everything. With this solution most sites will not show properly unless you whitelist all the dependencies. For example, some sites have their CSS scripts in a separate domain, so if you whitelist amazon.com, you&#8217;ll be able to access it, but it will look garbled because their CSS is on awsstatic.com. So, you have to whitelist this site. I&#8217;ll explain how to find that as well if you encounter these problems.<br />
Before I begin, these are the pre-requisites and the expectations. We&#8217;ll use CentOS 7 (with SELinux disabled) as a squid server that runs in a public subnet and the instances that want to access the Internet are in a private subnet. squid constantly evolves, so whatever works now might not work tomorrow, because of the nature of how we access SSL sites (related to TLS 1.3). I&#8217;ll use CentOS 7 and squid 4.9. If you use a different version, this might not work. Also, subsequent updates might break this solution. If you want an Enterprise solution, you might want to look somewhere else.<br />
Here is my architectural diagram. I&#8217;ll explain every detail. If you have your VPCs, subnets, routes ready, you might want to skip this, but pay attention on the routing.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-14.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-14.jpg" alt="" width="1792" height="1063" class="aligncenter size-full wp-image-6060" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-14.jpg 1792w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-14-300x178.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-14-1024x607.jpg 1024w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-14-768x456.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-14-1536x911.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-14-1170x694.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-14-585x347.jpg 585w" sizes="(max-width: 1792px) 100vw, 1792px" /></a></p>
<h1>VPC, subnets, public route and Internet Gateway</h1>
<p>I&#8217;ll create a new VPC called vpcSquid and I&#8217;ll carve that VPC with two subnets, one private and one public. Each will accommodate 256 hosts.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-01.png" alt="" width="690" height="326" class="aligncenter size-full wp-image-6039" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-01.png 690w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-01-300x142.png 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-01-585x276.png 585w" sizes="(max-width: 690px) 100vw, 690px" /></a><br />
Let&#8217;s create the two subnets. Go to the <strong>VPC </strong>menu and from the <strong>Subnets </strong>submenu click on <strong>Create subnet</strong>.<br />
The first one is the public subnet where squid server will reside.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-02.png" alt="" width="696" height="401" class="aligncenter size-full wp-image-6040" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-02.png 696w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-02-300x173.png 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-02-585x337.png 585w" sizes="(max-width: 696px) 100vw, 696px" /></a><br />
Create the private subnet.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-03.png" alt="" width="696" height="406" class="aligncenter size-full wp-image-6041" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-03.png 696w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-03-300x175.png 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-03-585x341.png 585w" sizes="(max-width: 696px) 100vw, 696px" /></a><br />
Now go to the <strong>Route tables</strong> menu and you&#8217;ll see that you have a route there already. This route was created when you created the VPC. It refers to the public subnet, but it&#8217;s not associated yet.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-04.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-04.jpg" alt="" width="583" height="283" class="aligncenter size-full wp-image-6044" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-04.jpg 583w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-04-300x146.jpg 300w" sizes="(max-width: 583px) 100vw, 583px" /></a><br />
Click one the <strong>Subnet associations</strong> tab and then click on <strong>Edit subnet assications</strong> button. Select the public subnet and click <strong>Save</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-05.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-05.jpg" alt="" width="490" height="197" class="aligncenter size-full wp-image-6045" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-05.jpg 490w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-05-300x121.jpg 300w" sizes="(max-width: 490px) 100vw, 490px" /></a><br />
Before we create a route to the Internet for the public subnet, we need an Internet Gateway. Click on the <strong>Internet Gateways</strong> menu on the left and click <strong>Create Internet Gateway</strong>. I named mine gwSquid. Select the newly created <strong>Internet Gateway</strong> and then click on the <strong>Actions </strong>button and click on <strong>Attach to VPC</strong>. Assign the VPC that we created.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-06.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-06.jpg" alt="" width="408" height="242" class="aligncenter size-full wp-image-6046" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-06.jpg 408w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-06-300x178.jpg 300w" sizes="(max-width: 408px) 100vw, 408px" /></a><br />
Now, go back to the <strong>Route tables</strong>, then select the public route again and click on the <strong>Routes </strong>tab. Click on <strong>Edit routes</strong> button, then click on <strong>Add route</strong>.<br />
Add a route to the Internet (0.0.0.0/0) over the Internet gateway that we just created. Click on <strong>Save routes</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-07.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-07.jpg" alt="" width="849" height="253" class="aligncenter size-full wp-image-6047" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-07.jpg 849w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-07-300x89.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-07-768x229.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-07-585x174.jpg 585w" sizes="(max-width: 849px) 100vw, 849px" /></a></p>
<h1>squid instance</h1>
<p>Let&#8217;s create the instance now. Choose CentOS 7 image, deploy it in the VPC that we just created. Make sure it&#8217;s in the public subnet and that you have a public IP.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-08.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-08.jpg" alt="" width="653" height="147" class="aligncenter size-full wp-image-6048" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-08.jpg 653w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-08-300x68.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-08-585x132.jpg 585w" sizes="(max-width: 653px) 100vw, 653px" /></a><br />
For storage, choose whatever you like. If you want to use squid as a cache server, then choose more than the default 8GB. For example, if you choose 20GB disk, you can also restrict squid to keep only 12GB of cache for example. Your choice.<br />
Assign a security group that allows port 22 (SSH). In my case I allowed access from everywhere (0.0.0.0/0), but it&#8217;s wisely to choose only certain IPs or subnets. This is a test instance, so I&#8217;ll proceed with 0.0.0.0/0.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-09.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-09.jpg" alt="" width="865" height="264" class="aligncenter size-full wp-image-6049" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-09.jpg 865w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-09-300x92.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-09-768x234.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-09-585x179.jpg 585w" sizes="(max-width: 865px) 100vw, 865px" /></a><br />
Finally choose your key and launch the instance. While the instance spins up, go back to the <strong>VPC | Route tables</strong> menu.</p>
<h1>Private route</h1>
<p>Click on <strong>Create Route table</strong> button and enter the name of the private route and associate it the the VPC. Click the <strong>Create </strong>button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-10.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-10.jpg" alt="" width="718" height="211" class="aligncenter size-full wp-image-6050" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-10.jpg 718w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-10-300x88.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-10-585x172.jpg 585w" sizes="(max-width: 718px) 100vw, 718px" /></a><br />
Select the private route and click on the <strong>Subnet Associations</strong> tab. Click <strong>Edit subnet associations</strong> button and select the private subnet.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-11.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-11.jpg" alt="" width="839" height="198" class="aligncenter size-full wp-image-6051" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-11.jpg 839w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-11-300x71.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-11-768x181.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-11-585x138.jpg 585w" sizes="(max-width: 839px) 100vw, 839px" /></a><br />
Then click on the <strong>Routes </strong>tab and click on <strong>Edit routes</strong>. Click <strong>Add route</strong>. For <strong>Destination </strong>type 0.0.0.0/0, for the target select <strong>Instance </strong>and select the instance that we just created. Click <strong>Save routes</strong> button. What we just did is we told AWS to route the Internet request in the private subnet through our squid instance. So, the Internet request will go to the instance first and then through the Internet gateway.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-12.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-12.jpg" alt="" width="858" height="270" class="aligncenter size-full wp-image-6052" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-12.jpg 858w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-12-300x94.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-12-768x242.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-12-585x184.jpg 585w" sizes="(max-width: 858px) 100vw, 858px" /></a></p>
<h1>Source/Destination Check</h1>
<p>At this point, the only thing remaining is to go to the instance and change the <strong>Source/Destination Check</strong> setting. Select the instance, click the <strong>Actions </strong>button, then select <strong>Networking </strong>and then <strong>Select Source/Dest. Check</strong>. Click on <strong>Yes, Disable</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-13.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-13.jpg" alt="" width="593" height="196" class="aligncenter size-full wp-image-6053" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-13.jpg 593w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-13-300x99.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-13-585x193.jpg 585w" sizes="(max-width: 593px) 100vw, 593px" /></a></p>
<h1>Install squid from source</h1>
<p>SSH to the instance using <strong>centos </strong>as user and your key as password. Type <strong>sudo su</strong> to get the root prompt. CentOS comes with squid 3.5, but we&#8217;ll install squid 4.9 from the source.<br />
First, install the pre-requisites.</p>
<pre class="brush: bash; title: ; notranslate">
yum install -y perl gcc-c++ autoconf automake make wget
yum install -y libxml2-devel libecap-devel openssl-devel openldap-devel pam-devel libdb-devel
</pre>
<p>Then, create a user that will run the squid daemon.</p>
<pre class="brush: bash; title: ; notranslate">
adduser squid -r -s /sbin/nologin -m
</pre>
<p>Go to the /tmp folder, download the squid tarball, extract it and go to that directory.</p>
<pre class="brush: bash; title: ; notranslate">
cd /tmp
wget http://www.squid-cache.org/Versions/v4/squid-4.9.tar.gz
tar xzvf squid-4.9.tar.gz
cd squid-4.9
</pre>
<p>Configure the squid with all helpers. Click the plus sign to expand the source.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
./configure --build=x86_64-redhat-linux-gnu --host=x86_64-redhat-linux-gnu \
--program-prefix= --prefix=/usr --exec-prefix=/usr \
--bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc \
--datadir=/usr/share --includedir=/usr/include \
--libdir=/usr/lib64 --libexecdir=/usr/libexec \
--sharedstatedir=/var/lib --mandir=/usr/share/man \
--infodir=/usr/share/info \
--exec_prefix=/usr --libexecdir=/usr/lib64/squid \
--localstatedir=/var --datadir=/usr/share/squid \
--sysconfdir=/etc/squid --with-logdir=/var/log/squid \
--with-pidfile=/var/run/squid.pid --enable-auth \
--enable-auth-basic=DB,LDAP,NCSA,NIS,PAM,POP3,RADIUS,SASL,SMB,getpwnam,fake \
--enable-auth-ntlm=fake --enable-auth-digest=file,LDAP,eDirectory \
--enable-auth-negotiate=kerberos,wrapper \
--enable-external-acl-helpers=file_userip,LDAP_group,time_quota,delayer,SQL_session,session,unix_group,wbinfo_group,kerberos_ldap_group \
--enable-cache-digests --enable-cachemgr-hostname=localhost \
--enable-delay-pools --enable-epoll --enable-icap-client --enable-ident-lookups \
--enable-linux-netfilter --enable-removal-policies=heap,lru \
--enable-snmp --enable-storeio=aufs,diskd,rock,ufs --enable-wccpv2 \
--enable-esi --enable-ecap --with-aio \
--enable-security-cert-generators --enable-security-cert-validators \
--enable-icmp --with-filedescriptors=16384 --disable-arch-native --without-nettle \
--disable-dependency-tracking --enable-eui --enable-follow-x-forwarded-for \
--with-default-user=squid --enable-ssl-crtd --with-included-ltdl \
--with-dl --with-openssl --with-pthreads --disable-arch-native 
</pre>
<p>Compile and install. It takes about 20 mins on 2 CPU, 2GB RAM t3 instance.</p>
<pre class="brush: bash; title: ; notranslate">
make &amp;&amp; make install
</pre>
<p>While the installation runs, open up a new session and <strong>sudo su</strong> again as root. We have to create the systemd startup scripts. Just copy and paste these three commands. It will create three files.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /usr/lib/systemd/system/squid.service
&#x5B;Unit]
Description=Squid caching proxy
After=syslog.target network.target nss-lookup.target

&#x5B;Service]
Type=forking
LimitNOFILE=16384
EnvironmentFile=/etc/sysconfig/squid
ExecStartPre=/usr/libexec/squid/cache_swap.sh
ExecStart=/usr/sbin/squid $SQUID_OPTS -f $SQUID_CONF
ExecReload=/usr/sbin/squid $SQUID_OPTS -k reconfigure -f $SQUID_CONF
ExecStop=/usr/sbin/squid -k shutdown -f $SQUID_CONF
TimeoutSec=0

&#x5B;Install]
WantedBy=multi-user.target
EOF
</pre>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
cat &lt;&lt; 'EOF' &gt; /etc/sysconfig/squid
# default squid options
SQUID_OPTS=&quot;&quot;

# Time to wait for Squid to shut down when asked. Should not be necessary
# most of the time.
SQUID_SHUTDOWN_TIMEOUT=100

# default squid conf file
SQUID_CONF=&quot;/etc/squid/squid.conf&quot;
EOF
</pre>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
mkdir /usr/libexec/squid
cat &lt;&lt; 'EOF' &gt; /usr/libexec/squid/cache_swap.sh
#!/bin/bash
if &#x5B; -f /etc/sysconfig/squid ]; then
        . /etc/sysconfig/squid
fi

SQUID_CONF=${SQUID_CONF:-&quot;/etc/squid/squid.conf&quot;}

CACHE_SWAP=`sed -e 's/#.*//g' $SQUID_CONF | \
        grep cache_dir | awk '{ print $3 }'`

for adir in $CACHE_SWAP; do
        if &#x5B; ! -d $adir/00 ]; then
                echo -n &quot;init_cache_dir $adir... &quot;
                squid -N -z -F -f $SQUID_CONF &gt;&gt; /var/log/squid/squid.out 2&gt;&amp;1
        fi
done
EOF
chmod +x /usr/libexec/squid/cache_swap.sh
</pre>
<p>Once the compilation and installation ends, execute these lines to allow the squid user to access the log directory and enable the auto-start on boot. Also, make sure SELinux is disabled and change <strong>/etc/selinux/config</strong> file to <strong>SELINUX=permissive</strong> or <strong>SELINUX=disabled</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
chown -R squid:squid /var/log/squid
systemctl enable squid
setenforce 0
</pre>
<p>Feel free to start squid, but it will run with the default config and in non-transparent proxy mode.</p>
<pre class="brush: bash; title: ; notranslate">
# Optional
systemctl start squid
systemctl status squid -l
tail /var/log/squid/cache.log
</pre>
<p>If you like you can test. Log to an instance in a private subnet and set the proxy. Change the IP below to the IP of your squid server and make sure the squid server has a security group that allows port 3128 from 10.10.51.0/24 subnet.</p>
<pre class="brush: bash; title: ; notranslate">
# Optional, do this on the client instance
export http_proxy=10.10.50.235:3128
export https_proxy=10.10.50.235:3128
</pre>
<p>If you do <strong>curl -v https://wordpress.org</strong> you&#8217;ll get their page back in HTML/CSS.</p>
<pre class="brush: bash; title: ; notranslate">
unset http_proxy
unset https_proxy
</pre>
<p>If you reset the proxy settings to nothing and if you do culr again, you&#8217;ll see that it will time-out. Even with the proxy set, you can&#8217;t use yum because yum uses proxy settings specified in <strong>/etc/yum.conf</strong>. That&#8217;s why a transparent proxy is much more superior. You don&#8217;t have to worry about configuring the proxy on the instances.<br />
<strong>NOTE</strong>: Make sure you unset the proxy settings if you already exported them.<br />
Now that we have pretty-much everything ready, let&#8217;s create the config file. It&#8217;s using SSLBump peek and splice config. For more info click <a href="https://wiki.squid-cache.org/Features/SslPeekAndSplice" rel="noopener noreferrer" target="_blank">here</a>.<br />
Just copy and paste the snippet below. It will create the config for you.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; /etc/squid/squid.conf
cache deny all
# HTTP
http_port 3128
http_port 3129 intercept
acl http-whitelist dstdomain &quot;/etc/squid/whitelist.txt&quot;
http_access allow http-whitelist

# HTTPS
https_port 3130 cert=/etc/squid/ssl/squid.pem ssl-bump intercept
acl sslport port 443
http_access allow sslport
acl https-whitelist ssl::server_name &quot;/etc/squid/whitelist.txt&quot;
acl step1 at_step SslBump1
acl step2 at_step SslBump2
acl step3 at_step SslBump3
ssl_bump peek step1 all
ssl_bump peek step2 https-whitelist
ssl_bump splice step3 https-whitelist
ssl_bump terminate step2 all
http_access deny all
EOF
</pre>
<p>And for the whitelist file, create a file with your allowed domains. Everything else will be blocked. You can put IPs here as well.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; 'EOF' &gt; /etc/squid/whitelist.txt
.amazon.com
.amazonaws.com
.wordpress.org
.api.google.com
EOF
</pre>
<p>If squid is running, stop it with <strong>systemctl stop squid</strong> and execute these lines to create an internal self-signed certificate. More info on why this is needed, in the link that I&#8217;ve presented above.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /etc/squid/ssl
cd /etc/squid/ssl
openssl genrsa -out squid.key 4096
openssl req -new -key squid.key -out squid.csr -subj &quot;/C=US/ST=NJ/L=squid/O=squid/CN=squid&quot;
openssl x509 -req -days 3650 -in squid.csr -signkey squid.key -out squid.crt
cat squid.key squid.crt &gt;&gt; squid.pem  
</pre>
<p>Initialize the SSL database.</p>
<pre class="brush: bash; title: ; notranslate">
/usr/lib64/squid/security_file_certgen -c -s /var/cache/squid/ssl_db -M 4MB
</pre>
<p>&#8230;otherwise you&#8217;ll receive these errors in <strong>/var/log/squid/cache.log</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
2019/11/17 15:44:19 kid1| WARNING: /usr/lib64/squid/security_file_certgen -s /var/cache/squid/ssl_db -M 4MB #Hlpr1 exited
2019/11/17 15:44:19 kid1| Too few /usr/lib64/squid/security_file_certgen -s /var/cache/squid/ssl_db -M 4MB processes are running (need 1/32)
</pre>
<p>Change the permission of this file&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
chmod 4755 /usr/lib64/squid/pinger 
</pre>
<p>&#8230;otherwise you&#8217;ll receive these errors in <strong>/var/log/squid/cache.log</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
2019/11/17 15:38:47| pinger: Initialising ICMP pinger ...
2019/11/17 15:38:47| Open  icmp_sock: (1) Operation not permitted
2019/11/17 15:38:47| pinger: Unable to start ICMP pinger.
</pre>
<p>If you start squid now, you&#8217;ll get something like this in <strong>/var/log/squid/cache.log</strong>. If you still get errors like the ones above (pinger: Unable to start ICMP pinger), reboot one more time.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
2019/11/17 15:49:17| Created PID file (/var/run/squid.pid)
2019/11/17 15:49:17 kid1| Current Directory is /
2019/11/17 15:49:17 kid1| Starting Squid Cache version 4.9 for x86_64-redhat-linux-gnu...
2019/11/17 15:49:17 kid1| Service Name: squid
2019/11/17 15:49:17 kid1| Process ID 3705
2019/11/17 15:49:17 kid1| Process Roles: worker
2019/11/17 15:49:17 kid1| With 16384 file descriptors available
2019/11/17 15:49:17 kid1| Initializing IP Cache...
2019/11/17 15:49:17 kid1| DNS Socket created at &#x5B;::], FD 5
2019/11/17 15:49:17 kid1| DNS Socket created at 0.0.0.0, FD 9
2019/11/17 15:49:17 kid1| Adding domain ec2.internal from /etc/resolv.conf
2019/11/17 15:49:17 kid1| Adding nameserver 10.10.50.2 from /etc/resolv.conf
2019/11/17 15:49:17 kid1| helperOpenServers: Starting 5/32 'security_file_certgen' processes
2019/11/17 15:49:17 kid1| Logfile: opening log daemon:/var/log/squid/access.log
2019/11/17 15:49:17 kid1| Logfile Daemon: opening log /var/log/squid/access.log
2019/11/17 15:49:17 kid1| Local cache digest enabled; rebuild/rewrite every 3600/3600 sec
2019/11/17 15:49:17 kid1| Store logging disabled
2019/11/17 15:49:17 kid1| Swap maxSize 0 + 262144 KB, estimated 20164 objects
2019/11/17 15:49:17 kid1| Target number of buckets: 1008
2019/11/17 15:49:17 kid1| Using 8192 Store buckets
2019/11/17 15:49:17 kid1| Max Mem  size: 262144 KB
2019/11/17 15:49:17 kid1| Max Swap size: 0 KB
2019/11/17 15:49:17 kid1| Using Least Load store dir selection
2019/11/17 15:49:17 kid1| Current Directory is /
2019/11/17 15:49:17 kid1| Finished loading MIME types and icons.
2019/11/17 15:49:17 kid1| HTCP Disabled.
2019/11/17 15:49:17 kid1| Pinger socket opened on FD 26
2019/11/17 15:49:17 kid1| Squid plugin modules loaded: 0
2019/11/17 15:49:17 kid1| Adaptation support is off.
2019/11/17 15:49:17 kid1| Accepting HTTP Socket connections at local=&#x5B;::]:3128 remote=&#x5B;::] FD 22 flags=9
2019/11/17 15:49:17 kid1| Accepting NAT intercepted HTTP Socket connections at local=&#x5B;::]:3129 remote=&#x5B;::] FD 23 flags=41
2019/11/17 15:49:17 kid1| Accepting NAT intercepted SSL bumped HTTPS Socket connections at local=&#x5B;::]:3130 remote=&#x5B;::] FD 24 flags=41
2019/11/17 15:49:17| pinger: Initialising ICMP pinger ...
2019/11/17 15:49:17| pinger: ICMP socket opened.
2019/11/17 15:49:17| pinger: ICMPv6 socket opened
2019/11/17 15:49:18 kid1| storeLateRelease: released 0 objects
</pre>
<p>If you see my <strong>squid.conf</strong>, you&#8217;ll see that I am not using squid as cache server (<strong>cache deny all</strong>). If you want to cache your responses, replace that line with this one. 100 means 100MB. See <a href="http://www.squid-cache.org/Doc/config/cache_dir/" rel="noopener noreferrer" target="_blank">here </a>for more explanation.</p>
<pre class="brush: bash; title: ; notranslate">
cache_dir ufs /var/cache/squid 100 16 256
</pre>
<p>In order to run in transparent proxy mode, we have to make some firewall changes and redirect the traffic on ports 80 and 443 to squid which will listen on 3129 and 3130 (for https). Do this on the squid server. You don&#8217;t have to do anything on the clients.</p>
<pre class="brush: bash; title: ; notranslate">
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3129
iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3130
</pre>
<p>If we reboot the server, the iptables changes will be lost, so we have to save them.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install iptables-services
systemctl enable iptables
systemctl start iptables
service iptables save
</pre>
<p>Reboot to make sure everything comes back. Check the redirects. Lines 4 and 5 should be as below.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
iptables --table nat --list
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             anywhere             tcp dpt:http redir ports 3129
REDIRECT   tcp  --  anywhere             anywhere             tcp dpt:https redir ports 3130

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  anywhere             anywhere
</pre>
<h1>Test</h1>
<p>In order to test, we&#8217;ll build a small Linux instance in the private subnet and test from there. Do not assign a public IP to this instance. If you don&#8217;t have a VPN or Direct Connect to your private subnets, you&#8217;ll have to use the squid server to access the instance. Copy the key (PEM) file to the squid instance and SSH to the private instance using <strong>ssh -i key.pem internal_IP_of_test_instance</strong>. You have to <strong>chmod 600 key.pem</strong> first. The key file is the one that you use to connect to the squid instance. That&#8217;s in my case. If you have multiple key files, then copy the key file that you used when you created the instance in the private subnet. Assign a security group that allows SSH from the squid server instance, or use the same one that I used when created the squid server which allows access from everywhere on port 22. You have to rectify this later. Don&#8217;t allow access from everywhere in any circumstances. This is just for test.<br />
Make sure that you have a new security group created for the squid server instance. This security group should allow ports 80 and 443 from the private subnet only (10.10.51.0/24). Attach this security group to the squid server so it allows HTTP/HTTPS ports from the client instances.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/11/P139-15.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/11/P139-15.jpg" alt="" width="793" height="133" class="aligncenter size-full wp-image-6073" srcset="https://blog.andreev.it/wp-content/uploads/2019/11/P139-15.jpg 793w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-15-300x50.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-15-768x129.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/11/P139-15-585x98.jpg 585w" sizes="(max-width: 793px) 100vw, 793px" /></a><br />
On the client instance, make sure you have <strong>unset http_proxy</strong> and <strong>https_proxy</strong> variables. If you try to access a site that&#8217;s not listed in the whitelist.txt, you&#8217;ll get an Access Denied page from squid or sometimes you&#8217;ll get some SSL errors. For the sites that are listed, you might get them to show properly, but sometimes they&#8217;ll be garbled. If you look at the source of the page, you&#8217;ll see what URLs is the main site trying to access. Most likely there are some JavaScripts hosted on different domains. You&#8217;ll have to whitelist these as well if you want proper rendering. But sometimes, you might want to connect to an update server and you won&#8217;t be able to see the HTML rendering, e.g. CentOS update sites. In those cases <a href="https://www.telerik.com/fiddler" rel="noopener noreferrer" target="_blank">Fiddler </a>might be able to help.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/11/aws-squid-transparent-proxy-for-dns-filtering-whitelisting/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS, CentOS: Create your own radio station and deploy it on Alexa (optional)</title>
		<link>https://blog.andreev.it/2019/10/aws-centos-create-your-own-radio-station-and-deploy-it-on-alexa-optional/</link>
					<comments>https://blog.andreev.it/2019/10/aws-centos-create-your-own-radio-station-and-deploy-it-on-alexa-optional/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 06 Oct 2019 12:55:24 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Editor's Pick]]></category>
		<category><![CDATA[Alexa]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[icecast]]></category>
		<category><![CDATA[radio station]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5735</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to run your own Internet radio station. On&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to run your own Internet radio station. On top of that, I&#8217;ll show you how to build an Alexa skill to deploy your station in the cloud, so anyone with Alexa can say: &#8220;<strong>Alexa, play</strong> &lt;<strong>whatever_station</strong>&gt;&#8221; and hear your Internet radio station. There are many tutorials that describe this, but most of them require that you pay for some radio station hosting. If that&#8217;s easier for you, then you can skip this tutorial. If you want to have your own radio on the Internet (build from scratch) and deploy it or not on Alexa, than this guide is for you. There are some prerequisites though. You&#8217;ll need a CentOS 7 instance with a public IP and if you decide to publish it on Alexa, then you&#8217;ll need valid SSL certificate.<br />
<strong>IMPORTANT</strong>: If your want your station to be public on Alexa, then you also need license for the music. But no worries, if you just want your music played over Alexa at home (and not public), you can still do it.<br />
Also, the ices2 client can&#8217;t stream MP3 files, so you have to convert them to OGG. If you want to stream MP3s directly, check the earlier version of ices. The configuration file is almost identical. Ogg-Vorbis is perfectly fine and I&#8217;ll show you how to convert your library from MP3 to OGG.<br />
So, there are two parts of this tutorial. Build your own station first and then the optional Alexa part. Let&#8217;s start.</p>
<h1>CentOS 7</h1>
<p>Make sure you have a CentOS 7 instance ready in the cloud with a public IP. NATed instances are OK as long as you can control the ports. In addition, make sure SELinux is disabled. Disabling the OS firewall is optional. I assume that the firewall on the instance is on.</p>
<h2>icecast</h2>
<p><a href="http://icecast.org/" rel="noopener noreferrer" target="_blank">icecast </a>is a streaming <strong>server </strong>similar to shoutcast. It broadcasts a stream that&#8217;s provided by a source. The source can be a microphone, sound card or a file. In order to stream a file, we need a <strong>client </strong>such as ices, LiquidSoap etc. You are not the client, you are the <strong>listener</strong>. Now that we have this straighten up, let&#8217;s install icecast from the source. Just copy and paste everything below. If you get an error, execute line by line and see where it fails. Fix any issues, if any.</p>
<pre class="brush: bash; highlight: [7]; title: ; notranslate">
yum -y groupinstall &quot;Development Tools&quot;
yum install -y wget curl-devel libvorbis-devel libxslt-devel libxslt-devel openssl-devel
cd /tmp
wget http://downloads.xiph.org/releases/icecast/icecast-2.4.4.tar.gz
tar xzvf icecast-2.4.4.tar.gz
cd icecast-2.4.4
./configure --prefix=/opt/icecast --with-curl --with-openssl
make
make install
</pre>
<p>We specified <strong>/opt/icecast</strong> where <strong>icecast </strong>will be installed (line 7). You can change that in the <strong>configure </strong>statement, but you have to adjust everything after. <strong>icecast </strong>creates a sample configuration file named <strong>icecast.xml</strong> under <strong>/opt/icecast/etc</strong>. If you look at the XML file, you&#8217;ll see a bunch of settings and some of them have to be changed.<br />
We&#8217;ll change the following parameters:</p>
<ul>
&lt;<strong>location</strong>&gt; &#8211; Specifies where the radio station is located.<br />
&lt;<strong>admin</strong>&gt; &#8211; An e-mail address of the radio station admin.<br />
&lt;<strong>hostname</strong>&gt; &#8211; A hostname for the radio station. Important only if you are publishing this in YP directory of broadcast streams. Irrelevant for us, but we&#8217;ll change it anyway.<br />
&lt;<strong>source-password</strong>&gt; &#8211; A password so the source client can attach to the icecast server and provide the OGG stream.<br />
&lt;<strong>relay-password</strong>&gt; &#8211; Used only if the icecast is used as a relay.<br />
&lt;<strong>admin-user</strong>&gt; &#8211; Username for the admin web page.<br />
&lt;<strong>admin-password</strong>&gt; &#8211; Password for the admin user.<br />
&lt;<strong>logdir</strong>&gt; &#8211; A directory where the log file is stored.<br />
&lt;<strong>user</strong>&gt; &#8211; A local OS user that will run icecast. Don&#8217;t use root, it won&#8217;t work. You can create a separate user for this. The built-in nobody user is perfectly fine.<br />
&lt;<strong>group</strong>&gt; &#8211; A local OS group that will run icecast. </ul>
<p>So, let&#8217;s change these manually or with this code. Adjust to suit your needs.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/icecast/etc
location=&quot;New Jersey&quot;
admin=klimenta@iandreev.com
hostname=radio.iandreev.com
sourcepwd=Sup3rStr0ngP455w0rd
relaypwd=Sup3rStr0ngP455w0rd
admin=myusername
password=Sup3rStr0ngP455w0rd
logdir=/opt/icecast/log
user=nobody
group=nobody
sed -i &quot;s/&lt;location&gt;Earth/&lt;location&gt;$location/&quot; icecast.xml
sed -i &quot;s/&lt;admin&gt;icemaster@localhost/&lt;admin&gt;$admin/&quot; icecast.xml
sed -i &quot;s/&lt;hostname&gt;localhost/&lt;hostname&gt;$hostname/&quot; icecast.xml
sed -i &quot;s/&lt;source-password&gt;hackme/&lt;source-password&gt;$sourcepwd/&quot; icecast.xml
sed -i &quot;s/&lt;relay-password&gt;hackme/&lt;relay-password&gt;$relaypwd/&quot; icecast.xml
sed -i &quot;s/&lt;admin-user&gt;admin/&lt;admin-user&gt;$admin/&quot; icecast.xml
sed -i &quot;s/&lt;admin-password&gt;hackme/&lt;admin-password&gt;$password/&quot; icecast.xml
sed -i &quot;s|&lt;logdir&gt;/opt/icecast/var/log/icecast|&lt;logdir&gt;$logdir|&quot; icecast.xml
sed -i &quot;s/&lt;user&gt;nobody/&lt;user&gt;$user/&quot; icecast.xml
sed -i &quot;s/&lt;group&gt;nogroup/&lt;group&gt;$group/&quot; icecast.xml
</pre>
<p>Let&#8217;s create the log directory and give the icecast daemon permission to write.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/icecast
mkdir log
chown -R nobody:nobody log
</pre>
<p>icecast doesn&#8217;t come with a startup systemd script, so we&#8217;ll have to take care of that.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; /etc/systemd/system/icecast.service
&#x5B;Unit]
Description=Icecast Network Audio Streaming Server
After=network.target

&#x5B;Service]
Type=simple
User=nobody
Group=nobody
ExecStart=/opt/icecast/bin/icecast -c /opt/icecast/etc/icecast.xml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill $MAINPID

&#x5B;Install]
WantedBy=multi-user.target
EOF
</pre>
<p>Now you can enable icecast on boot and start it.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable icecast
systemctl start icecast
</pre>
<p>If you run <strong>systemctl status icecast</strong>, you should see that the daemon is running. Also, if you check the error log (<strong>tail /opt/icecast/log/error.log</strong>), you&#8217;ll see that the daemon is running too. Ignore the SSL warning for now.<br />
<strong>icecast </strong>runs on port <strong>8000</strong>, so if you have a firewall, you&#8217;ll have to poke a hole.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=8000/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
<p>From a browser, go to your server&#8217;s IP on port 8000, e.g. <strong>http://201.202.203.24:8000</strong> and you&#8217;ll see something like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-01.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-01.jpg" alt="" width="929" height="248" class="aligncenter size-full wp-image-8683" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-01.jpg 929w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-01-300x80.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-01-768x205.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-01-585x156.jpg 585w" sizes="(max-width: 929px) 100vw, 929px" /></a><br />
If you click on <strong>Administration</strong>, you&#8217;ll be prompted to log in. Use the <strong>admin-user</strong> and <strong>admin-password</strong> that you&#8217;ve changed in <strong>icecast.xml</strong>.</p>
<h2>ices</h2>
<p>Now that we have the streaming server ready, let&#8217;s install the source client. The source client will provide the stream to the server. We&#8217;ll use <strong>ices </strong>for this.</p>
<pre class="brush: bash; title: ; notranslate">
cd /tmp
wget http://downloads.us.xiph.org/releases/ices/ices-2.0.2.tar.gz
tar xzvf ices-2.0.2.tar.gz
cd ices-2.0.2
yum -y install libshout-devel
./configure --prefix=/opt/ices
make &amp;&amp; make install
</pre>
<p><strong>ices </strong>creates some configuration files under <strong>/opt/ices/share/ices</strong>. We&#8217;ll use the <strong>ices-playlist.xml</strong> template to generate a radio station for us.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/ices/share/ices
cp ices-playlist.xml myradio.xml
</pre>
<p>Now edit <strong>myradio.xml</strong> and change the following with a script or manually. Make sure you adjust for your needs.</p>
<ul>
&lt;<strong>logpath</strong>&gt; &#8211; A directory where the log file is stored.<br />
&lt;<strong>logfile</strong>&gt; &#8211; The name of the log file.<br />
&lt;<strong>name</strong>&gt; &#8211; The name of your radio.<br />
&lt;<strong>genre</strong>&gt; &#8211; Self explanatory.<br />
&lt;<strong>description</strong>&gt; &#8211; Enter a description for your station.<br />
&lt;<strong>password</strong>&gt; &#8211; The password to connect to icecast. It should match the <strong>source-password in icecast.xml</strong><br />
&lt;<strong>mount</strong>&gt; &#8211; The name of the mount. This is like the URL for your station. It has to end with .ogg<br />
&lt;<strong>param name</strong>&gt; &#8211; A file where the playlist for this station will be stored.
</ul>
<pre class="brush: bash; title: ; notranslate">
cd /opt/ices/share/ices
logpath=/opt/ices/log
logfile=myradio.log
name=&quot;Radio MyRadio&quot;
genre=&quot;Some genre&quot;
descr=&quot;Best Radio in the Universe and beyond&quot;
pwd=Sup3rStr0ngP455w0rd
mount=/myradio.ogg
playlist=/opt/ices/playlists/myradio.txt
sed -i &quot;s|&lt;logpath&gt;/var/log/ices|&lt;logpath&gt;$logpath|&quot; myradio.xml
sed -i &quot;s/&lt;logfile&gt;ices.log/&lt;logfile&gt;$logfile/&quot; myradio.xml
sed -i &quot;s/&lt;name&gt;Example stream name/&lt;name&gt;$name/&quot; myradio.xml
sed -i &quot;s/&lt;genre&gt;Example genre/&lt;genre&gt;$genre/&quot; myradio.xml
sed -i &quot;s/&lt;description&gt;A short description of your stream/&lt;description&gt;$descr/&quot; myradio.xml
sed -i &quot;s/&lt;password&gt;hackme/&lt;password&gt;$pwd/&quot; myradio.xml
sed -i &quot;s|&lt;mount&gt;/example1.ogg|&lt;mount&gt;$mount|&quot; myradio.xml
sed -i &quot;s|&lt;param name=\&quot;file\&quot;&gt;playlist.txt|&lt;param name=\&quot;file\&quot;&gt;$playlist|&quot; myradio.xml
</pre>
<p>Let&#8217;s create two directories. One for the logs and the other for the files where we&#8217;ll store playlists. Not the music files  &#8211; the playlists. The playlists tells ices where to look for your music.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/ices
mkdir log playlists
chown -R nobody:nobody log
</pre>
<p>Create a startup script for ices as well. Look at line 10, that&#8217;s where my config file for this radio station is. Can you have multiple radio stations? Absolutely, one <strong>icecast </strong>server can handle multiple <strong>ices </strong>clients. Just invoke another <strong>ices </strong>client with a different config file and create a similar startup script with a different URL, e.g &#8230;/myradio.ogg and &#8230;/mystation.ogg. </p>
<pre class="brush: bash; highlight: [10]; title: ; notranslate">
cat &lt;&lt; EOF &gt; /etc/systemd/system/ices-myradio.service
&#x5B;Unit]
Description=Ices Network Audio Streaming Client for MyRadio
After=icecast.service

&#x5B;Service]
Type=simple
User=nobody
Group=nobody
ExecStart=/opt/ices/bin/ices /opt/ices/share/ices/myradio.xml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill $MAINPID
Restart=always
RestartSec=3

&#x5B;Install]
WantedBy=multi-user.target
EOF
</pre>
<p>At this point you can tell ices where your music is stored. As I mentioned earlier, we&#8217;ll have to use Ogg-Vorbis files (.ogg).<br />
So, create a folder where your music files will be stored. I&#8217;ll use <strong>/music/myradio</strong> and put some public royalty free music there.<br />
Courtesy of <strong>Patrick de Arteaga</strong>. Please give him some credit and visit his <a href="https://patrickdearteaga.com/" rel="noopener noreferrer" target="_blank">site</a>.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p /music/myradio
cd /music/myradio
wget -U 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' https://patrickdearteaga.com/audio/Battleship.ogg
wget -U 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' https://patrickdearteaga.com/audio/Chiptronical.ogg
wget -U 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' https://patrickdearteaga.com/audio/Friends.ogg
</pre>
<p>OK, so we have our music there. When the stream ends, it will start from the first song. Now we have to create the playlist and put it under the <strong>/opt/ices/conf</strong> directory.<br />
If you remember we specified <strong><param name></strong> directive in the <strong>myradio.xml</strong> file referencing where the playlist is stored. Let&#8217;s create the file.</p>
<pre class="brush: bash; title: ; notranslate">
ls -d /music/myradio/*.ogg &gt; /opt/ices/playlists/myradio.txt
</pre>
<p>Now that we have everything ready, let&#8217;s enable ices on boot and start it.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable ices-myradio
systemctl start ices-myradio
</pre>
<p>Check if everything is OK. </p>
<pre class="brush: bash; title: ; notranslate">
systemctl status ices-myradio
tail /opt/ices/log/myradio.log
</pre>
<p>If everything looks good, you can listen to your stream at <strong>http://whateverIP:8000/myradio.ogg</strong>.<br />
If you are satisfied with this, you can stop reading. You&#8217;ve accomplished enough. If you want to see how to convert MP3 to OGG and how to use a reverse proxy so you can stream over HTTP, then read further.</p>
<h2>MP3 to OGG conversion (sox)</h2>
<p>If you have a bunch of files that you want to convert to OGG, you can use sox. It&#8217;s a very powerful tool for conversion between formats. Let&#8217;s install it first.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install libmad-devel libid3tag-devel lame lame-devel flac flac-devel
cd /tmp
wget https://sourceforge.net/projects/sox/files/sox/14.4.2/sox-14.4.2.tar.gz
tar xvfz sox-14.4.2.tar.gz
cd sox-14.4.2
./configure
make -s &amp;&amp; make install
</pre>
<p>Now you can convert the files from MP3 to OGG and vice versa. Conversion is very simple. Just specify the source and the destination file, e.g. <strong>sox file1.mp3 file1.ogg</strong> and that&#8217;s it. If you want to see the details of the file such as bit-rate, duration, sample etc, use <strong>soxi</strong>, e.g. <strong>soxi file.ogg</strong>. If you want more details on sox and it&#8217;s usage, please check their <a href="http://sox.sourceforge.net/" rel="noopener noreferrer" target="_blank">website</a>. For <strong>soxi</strong>, check this <a href="http://sox.sourceforge.net/soxi.html" rel="noopener noreferrer" target="_blank">link</a>.<br />
If you want to convert your files in bulk, you can use this command that will search for MP3 files in your current directory and subdirectories and convert them to OGG.</p>
<pre class="brush: bash; title: ; notranslate">
find . -type f -name &quot;*.mp3&quot; -exec sh -c 'sox &quot;$1&quot; &quot;${1%.mp3}.ogg&quot;' _ {} \;
</pre>
<h2>nginx as reverse proxy</h2>
<p>If you recall, we are using <strong>http://somenameorip:8000/myradio.ogg</strong> to access our station. This is fine for regular users from their home, but in any corporate environment, anything than port 80 and 443 are blocked. So, we have to stream our music over port 80. I&#8217;ll show later how to stream over 443 because it&#8217;s a requirement for Alexa. First thing first, let&#8217;s install <strong>nginx</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install nginx
systemctl enable nginx
</pre>
<p><strong>nginx </strong>creates a configuration file under <strong>/etc/nginx</strong> called <strong>nginx.conf</strong>. Look for this snippet in the configuration file.</p>
<pre class="brush: plain; title: ; notranslate">
        location / {
        }
</pre>
<p>&#8230;and change it to this.</p>
<pre class="brush: plain; title: ; notranslate">
        location /myradio.ogg {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }
</pre>
<p>You can start the reverse proxy now and verify that it&#8217;s working.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start nginx
systemctl status nginx
</pre>
<p>Because we are proxying over HTTP, we have to open the firewall too and if you want you can close port 8000. It&#8217;s not needed anymore.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=80/tcp --zone=public --permanent
firewall-cmd --remove-port=8000/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
<p>Now you can access your station as <strong>http://serveriporname/myradio.ogg</strong>, so there is no need to specify port 8000.</p>
<h2>SSL certificate</h2>
<p>If you decide to publish your station on Alexa, you&#8217;ll need a valid (not self-signed) certificate. You will also need to reconfigure both nginx and icecast to use SSL. You can&#8217;t access your server by IP because you&#8217;ll get an invalid certificate warning, so make sure you have your DNS ready to resolve your IP to a valid domain name. Get the certificates ready. You&#8217;ll need the private key, the certificate, the intermediate certificate and the root CA certificate. When you purchase a certificate all of these will be available for you (except for the private key).<br />
Let&#8217;s create the chain. In my case, I have each certificate in a separate file and I&#8217;ll create one certificate for icecast.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /opt/icecast/ssl
cat star.iandreev.com.key star.iandreev.com.crt star.iandreev.com.inter star.iandreev.com.CA &gt; /opt/icecast/ssl/icecast.pem
chown -R nobody:nobody /opt/icecast/ssl
</pre>
<p>Then, edit <strong>/opt/icecast/etc/icecast.xml</strong> and find the following snippet.</p>
<pre class="brush: plain; title: ; notranslate">
    &lt;listen-socket&gt;
        &lt;port&gt;8000&lt;/port&gt;
    &lt;/listen-socket&gt;
</pre>
<p>Copy these three lines after, so the config looks like this now. We are telling <strong>icecast </strong>to spawn itself and listen on port 8443 too.</p>
<pre class="brush: bash; title: ; notranslate">
    &lt;listen-socket&gt;
        &lt;port&gt;8000&lt;/port&gt;
    &lt;/listen-socket&gt;
    &lt;listen-socket&gt;
        &lt;port&gt;8443&lt;/port&gt;
        &lt;ssl&gt;1&lt;/ssl&gt;
    &lt;/listen-socket&gt;
</pre>
<p>We also have to specify the certificate chain. Find this line in the config and <strong>uncomment it</strong>. Make sure you uncomment the line, it&#8217;s commented by default.</p>
<pre class="brush: bash; title: ; notranslate">
&lt;ssl-certificate&gt;/opt/icecast/ssl/icecast.pem&lt;/ssl-certificate&gt;
</pre>
<p>Now, you have to restart <strong>icecast</strong> and <strong>ices </strong>too.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart icecast
systemctl restart ices-myradio
</pre>
<p>And if you check the error.log (yes, error log), you&#8217;ll see that icecast is using SSL now.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
tail /opt/icecast/log/error.log
&#x5B;2019-10-03  22:29:23] INFO connection/get_ssl_certificate SSL certificate found at /opt/icecast/ssl/icecast.pem
&#x5B;2019-10-03  22:29:23] INFO connection/get_ssl_certificate SSL using ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES
</pre>
<p>Now the <strong>nginx </strong>part. Edit <strong>/etc/nginx/nginx.conf</strong>.</p>
<ul>
&#8211; uncomment the section for &#8220;a TLS enabled server&#8221;. Remove the # from all the lines below # Settings for a TLS enabled server. Don&#8217;t uncomment this line.<br />
&#8211; Replace location / {} with this snippet. Not the first occurrence for port 80. This is for port 443.</p>
<pre class="brush: plain; title: ; notranslate">
        location /myradio.ogg {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }
</pre>
<p>&#8211; Copy your private key to <strong>/etc/pki/nginx/private/server.key</strong> and your public certificate to <strong>/etc/pki/nginx/server.crt</strong>. If the directories do not exists, create them. (<strong>cd /etc/pki &#038;&#038; mkdir -p nginx/private</strong>). Also, make sure that the certificate (<strong>server.crt</strong>) is a chain of certificates, not just a simple server certificate. If you do <strong>curl -iv https://yoursite.com</strong>, the result for the SSL should be clear without any errors or warning. You can reuse the icecast.pem certificate for icecast and copy that file as <strong>server.crt</strong> under <strong>/etc/pki/nginx/</strong>. You can even reference the same file in the same directory, but then you have to change the config files and permissions.<br />
&#8211; Reload nginx (<strong>systemctl reload nginx</strong>)<br />
&#8211; Open the firewall</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=443/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
</ul>
<p>Now you can access your server with https. If you want to be even more precise, you can redirect the http to https, so you don&#8217;t have to specify the protocol in the browser. Edit <strong>nginx.conf</strong> and add line 5. Reload nginx after (<strong>systemctl reload nginx</strong>).</p>
<pre class="brush: bash; highlight: [5]; title: ; notranslate">
server {
    listen       80 default_server;
    listen       &#x5B;::]:80 default_server;
    server_name  _;
    return 301 https://$host$request_uri;
    root         /usr/share/nginx/html;
</pre>
<p>Here is my <strong>/etc/nginx/nginx.conf</strong> file.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user &#x5B;$time_local] &quot;$request&quot; '
                      '$status $body_bytes_sent &quot;$http_referer&quot; '
                      '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       &#x5B;::]:80 default_server;
        server_name  _;
        return 301 https://$host$request_uri;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

# Settings for a TLS enabled server.

    server {
        listen       443 ssl http2 default_server;
        listen       &#x5B;::]:443 ssl http2 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        ssl_certificate &quot;/etc/pki/nginx/server.crt&quot;;
        ssl_certificate_key &quot;/etc/pki/nginx/private/server.key&quot;;
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location /myradio.ogg {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

}
</pre>
<h1>Alexa</h1>
<p>Now comes the fun part. It&#8217;s much easier than you think thanks to <a href="https://andymoore.info/" rel="noopener noreferrer" target="_blank">Andy Moore</a>. He has a great step-by-step <a href="https://andymoore.info/stream-your-radio-station-on-alexa-for-free" rel="noopener noreferrer" target="_blank">tutorial </a>for this. I&#8217;ll quickly go over what&#8217;s needed.<br />
You&#8217;ll need your:</p>
<ul>
&#8211; Radio station name<br />
&#8211; Name of your website<br />
&#8211; URL of your stream (it has to be https)</ul>
<p>Fun fact. I named my station Radio Andreev and whatever I said, Alexa couldn&#8217;t understand. It was looking for Radio Andrea all the time. So, do yourself a favor and name your station something that Alexa can understand. I named my station Radio Tincup, my website is https://radio.iandreev.com and the stream is at https://radio.iandreev.com/myradio.ogg. Generate the JSON from the site above or get this code and change your values. Click the (+) sign to expand. </p>
<pre class="brush: xml; collapse: true; highlight: [4,27,28,29,30]; light: false; title: ; toolbar: true; notranslate">
{
  &quot;interactionModel&quot;: {
    &quot;languageModel&quot;: {
      &quot;invocationName&quot;: &quot;radio tincup&quot;,
      &quot;intents&quot;: &#x5B;
        {
          &quot;name&quot;: &quot;AMAZON.CancelIntent&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Cancel&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;AMAZON.HelpIntent&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Help&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;AMAZON.StopIntent&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Stop&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;Play&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Ask radio tincup to play radio tincup&quot;,
            &quot;Start radio tincup&quot;,
            &quot;Ask radio tincup to play&quot;,
            &quot;Play radio tincup&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;AMAZON.NavigateHomeIntent&quot;,
          &quot;samples&quot;: &#x5B;]
        },
        {
          &quot;name&quot;: &quot;AMAZON.PauseIntent&quot;,
          &quot;samples&quot;: &#x5B;]
        },
        {
          &quot;name&quot;: &quot;AMAZON.ResumeIntent&quot;,
          &quot;samples&quot;: &#x5B;]
        }
      ],
      &quot;types&quot;: &#x5B;]
    }
  }
}
</pre>
<p>And this is your JavaScript. </p>
<pre class="brush: jscript; collapse: true; highlight: [28,71]; light: false; title: ; toolbar: true; notranslate">
const Alexa = require('ask-sdk-core');
var https = require('https');

const PlayHandler = {
	canHandle(handlerInput)
	{
		return (
			handlerInput.requestEnvelope.request.type === 'LaunchRequest' ||
			(
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				handlerInput.requestEnvelope.request.intent.name === 'Play'
			) ||
			(
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				handlerInput.requestEnvelope.request.intent.name === 'AMAZON.ResumeIntent'
			)
		);
	},
	handle(handlerInput)
	{
		return handlerInput.responseBuilder
			.addDirective({
				type: 'AudioPlayer.Play',
				playBehavior: 'REPLACE_ALL',
				audioItem:{
					stream:{
						token: '0',
						url: 'https://radio.iandreev.com/myradio.ogg',
						offsetInMilliseconds: 0
					}
				}
			})
			.getResponse();
	}
};

const PauseStopHandler = {
	canHandle(handlerInput)
	{
		return (
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				(
					handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' ||
					handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'
				)
			) ||
			(
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				handlerInput.requestEnvelope.request.intent.name === 'AMAZON.PauseIntent'
			);
	},
	handle(handlerInput)
	{
		return handlerInput.responseBuilder
			.addDirective({
				type: 'AudioPlayer.ClearQueue',
				clearBehavior: 'CLEAR_ALL'
			})
			.getResponse();
	}
};

const HelpIntentHandler = {
	canHandle(handlerInput)
	{
		return handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
			handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
	},
	handle(handlerInput)
	{
		const speechText = 'You can say Play, Stop or Resume. For more information please visit radio dot iandreev dot com';
		return handlerInput.responseBuilder
			.speak(speechText)
			.getResponse();
	}
};

const SessionEndedRequestHandler = {
	canHandle(handlerInput)
	{
		return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
	},
	handle(handlerInput)
	{
		return handlerInput.responseBuilder.getResponse();
	}
};

const IntentReflectorHandler = {
	canHandle(handlerInput)
	{
		return handlerInput.requestEnvelope.request.type === 'IntentRequest';
	},
	handle(handlerInput)
	{
		const intentName = handlerInput.requestEnvelope.request.intent.name;
		const speechText = 'NO INTENT HELP TEXT';
		return handlerInput.responseBuilder
			.speak(speechText)
			.getResponse();
	}
};

const ErrorHandler = {
	canHandle()
	{
		return true;
	},
	handle(handlerInput, error)
	{
		const speechText = `Sorry, I could not understand what you said. Please try again.`;
		return handlerInput.responseBuilder
			.speak(speechText)
			.reprompt(speechText)
			.getResponse();
	}
};

exports.handler = Alexa.SkillBuilders.custom()
	.addRequestHandlers(
		PlayHandler,
		PauseStopHandler,
		HelpIntentHandler,
		SessionEndedRequestHandler,
		IntentReflectorHandler)
	.addErrorHandlers(
		ErrorHandler)
	.lambda();
</pre>
<p>Go to <a href="https://developer.amazon.com" rel="noopener noreferrer" target="_blank">https://developer.amazon.com</a> and log in (or register). Make sure you use the same account in the developer console with the same account that you have your Alexa configured. Once logged in, click on <strong>Alexa </strong>from the menu and then <strong>Alexa Skills Kit</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-02.png" alt="" width="503" height="180" class="aligncenter size-full wp-image-8684" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-02.png 503w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-02-300x107.png 300w" sizes="(max-width: 503px) 100vw, 503px" /></a><br />
Click <strong>Create Skill</strong>. Enter the name of your station and the language.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-03.png" alt="" width="496" height="299" class="aligncenter size-full wp-image-8685" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-03.png 496w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-03-300x181.png 300w" sizes="(max-width: 496px) 100vw, 496px" /></a><br />
Choose a <strong>Custom </strong>model.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-04.png" alt="" width="416" height="341" class="aligncenter size-full wp-image-8686" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-04.png 416w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-04-300x246.png 300w" sizes="(max-width: 416px) 100vw, 416px" /></a><br />
Choose <strong>Alexa-Hosted</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-05.png" alt="" width="317" height="275" class="aligncenter size-full wp-image-8687" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-05.png 317w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-05-300x260.png 300w" sizes="(max-width: 317px) 100vw, 317px" /></a><br />
Scroll up and in the top right corner click <strong>Create skill</strong>. After 30-40 seconds, you&#8217;ll be presented with a different screen. On the left side find the <strong>Interfaces </strong>menu, then click on it. Then enable the <strong>Audio Player</strong> on the right.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-06.png" alt="" width="1061" height="442" class="aligncenter size-full wp-image-8688" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-06.png 1061w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-300x125.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-1024x427.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-768x320.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-585x244.png 585w" sizes="(max-width: 1061px) 100vw, 1061px" /></a><br />
Scroll up and click on <strong>Save Interfaces</strong> first, then <strong>Build Model</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-07.png" alt="" width="634" height="182" class="aligncenter size-full wp-image-8689" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-07.png 634w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-07-300x86.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-07-585x168.png 585w" sizes="(max-width: 634px) 100vw, 634px" /></a><br />
Click on the <strong>JSON Editor</strong> on the left, right above <strong>Interfaces</strong>. Once the editor shows up, delete everything there and paste the JSON file.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-08.png" alt="" width="771" height="204" class="aligncenter size-full wp-image-8690" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-08.png 771w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-08-300x79.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-08-768x203.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-08-585x155.png 585w" sizes="(max-width: 771px) 100vw, 771px" /></a><br />
Click <strong>Save Model</strong> then <strong>Build Model</strong> from the top.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-09.png" alt="" width="597" height="156" class="aligncenter size-full wp-image-8691" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-09.png 597w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-09-300x78.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-09-585x153.png 585w" sizes="(max-width: 597px) 100vw, 597px" /></a><br />
Click on <strong>Code </strong>from the top and paste the <strong>JavaScript</strong> in the editor.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-10.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-10.png" alt="" width="345" height="285" class="aligncenter size-full wp-image-8692" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-10.png 345w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-10-300x248.png 300w" sizes="(max-width: 345px) 100vw, 345px" /></a><br />
Click <strong>Save </strong>then <strong>Deploy </strong>from the upper right.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-11.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-11.png" alt="" width="404" height="103" class="aligncenter size-full wp-image-8693" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-11.png 404w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-11-300x76.png 300w" sizes="(max-width: 404px) 100vw, 404px" /></a><br />
Click on <strong>Test</strong>, allow access to the microphone and change <strong>Skill testing</strong> from <strong>Off </strong>to <strong>Development</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-12.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-12.png" alt="" width="527" height="220" class="aligncenter size-full wp-image-8694" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-12.png 527w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-12-300x125.png 300w" sizes="(max-width: 527px) 100vw, 527px" /></a><br />
Now, with the mouse hit the microphone icon, hold it there, say <strong>Alexa Play Radio Tincup</strong> and release the button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-13.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-13.png" alt="" width="503" height="273" class="aligncenter size-full wp-image-8695" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-13.png 503w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-13-300x163.png 300w" sizes="(max-width: 503px) 100vw, 503px" /></a><br />
Here is what I was talking to. Alexa can&#8217;t understand me&#8230;or maybe my mic is bad. If Alexa doesn&#8217;t understand, just type &#8220;<strong>Alexa play Radio Tincup</strong>&#8221; in the text field.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-14.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-14.png" alt="" width="414" height="778" class="aligncenter size-full wp-image-8696" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-14.png 414w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-14-160x300.png 160w" sizes="(max-width: 414px) 100vw, 414px" /></a><br />
You won&#8217;t hear Alexa playing and you won&#8217;t hear any music from the computer, but you&#8217;ll see a JSON response with the valid URL of your OGG stream.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-15.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-15.png" alt="" width="667" height="385" class="aligncenter size-full wp-image-8697" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-15.png 667w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-15-300x173.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-15-585x338.png 585w" sizes="(max-width: 667px) 100vw, 667px" /></a><br />
At this point you are all set. You can play your music using Alexa. For most people this would be the end. But if you really like to publish your station to be public, then proceed. In the console, click on <strong>Distribution</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-16.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-16.jpg" alt="" width="508" height="102" class="aligncenter size-full wp-image-8698" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-16.jpg 508w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-16-300x60.jpg 300w" sizes="(max-width: 508px) 100vw, 508px" /></a><br />
You&#8217;ll see a bunch of questions regarding the nature of your station, are you collecting customer information, are you advertising and so on. Just follow the process and hopefully, you&#8217;ll get your station published.</p>
<h1>Cleanup</h1>
<p>You might want to delete the tarballs for the icecast, ices and sox that you&#8217;ve downloaded.</p>
<pre class="brush: bash; title: ; notranslate">
rm -rf /tmp/ice*
rm -rf /tmp/sox*
</pre>
<p>You&#8217;ll also want to make sure that the firewall allows only port 80 and 443. Ports 8000 and 8443 can be, but they don&#8217;t have to be exposed to the Internet.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --remove-port=8000/tcp --zone=public --permanent
firewall-cmd --remove-port=8443/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/10/aws-centos-create-your-own-radio-station-and-deploy-it-on-alexa-optional/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		<enclosure url="https://patrickdearteaga.com/audio/Battleship.ogg" length="1320786" type="audio/ogg" />
<enclosure url="https://patrickdearteaga.com/audio/Chiptronical.ogg" length="4175911" type="audio/ogg" />
<enclosure url="https://patrickdearteaga.com/audio/Friends.ogg" length="1721081" type="audio/ogg" />
<enclosure url="https://radio.iandreev.com/myradio.ogg" length="0" type="audio/ogg" />

			</item>
		<item>
		<title>FreeBSD, CentOS: Install Jenkins behind a nginx reverse proxy using SSL certificates</title>
		<link>https://blog.andreev.it/2019/07/freebsd-centos-install-jenkins-behind-a-nginx-reverse-proxy-using-ssl-certificates/</link>
					<comments>https://blog.andreev.it/2019/07/freebsd-centos-install-jenkins-behind-a-nginx-reverse-proxy-using-ssl-certificates/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sat, 20 Jul 2019 13:06:24 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[FreeBSD]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[freebsd]]></category>
		<category><![CDATA[install]]></category>
		<category><![CDATA[Jenkins]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5482</guid>

					<description><![CDATA[In this post, I&#8217;ll explain how to install Jenkins on FreeBSD and CentOS. Jenkins&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post, I&#8217;ll explain how to install <strong>Jenkins </strong>on FreeBSD and CentOS. <strong>Jenkins </strong>runs on port 8080 (8180 in FreeBSD), so sometimes it&#8217;s not possible to access these ports because of corporate firewalls. We&#8217;ll put <strong>Jenkins </strong>behind <strong>nginx </strong>that will act as a reverse proxy. We&#8217;ll use port 80 (HTTP) and 443 (HTTPS) if you want to have SSL certificates. The installation is very simple.</p>
<h1>CentOS</h1>
<p>The <strong>Jenkins </strong>package is not part of the default CentOS repository, so we have to download that one first, install <strong>Jenkins</strong>, enable it to start on boot and then start it. You can skip the first line if you have <strong>wget </strong>installed. By default, it is not installed.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install wget
wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
yum -y install jenkins java
systemctl enable jenkins
systemctl start jenkins
</pre>
<p>If you have a firewall enabled, you have to open the port for 8080.</p>
<pre class="brush: plain; title: ; notranslate">
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --reload
</pre>
<p>Now, go to <strong>http://[ip]:8080</strong> and replace the <ip> with the IP of your server and you should see this greeting page.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/07/P134-01.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/07/P134-01.jpg" alt="" width="869" height="355" class="aligncenter size-full wp-image-8637" srcset="https://blog.andreev.it/wp-content/uploads/2019/07/P134-01.jpg 869w, https://blog.andreev.it/wp-content/uploads/2019/07/P134-01-300x123.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/07/P134-01-768x314.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/07/P134-01-585x239.jpg 585w" sizes="(max-width: 869px) 100vw, 869px" /></a><br />
Cat out that file, copy &#038; paste the value in the browser and click <strong>Continue</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
cat /var/lib/jenkins/secrets/initialAdminPassword
7f09cb85a4b843bdab66c768020a7c6e
</pre>
<p>And that&#8217;s it. Follow the prompts to install plugins, provide admin password etc&#8230;</p>
<h2>nginx as reverse proxy</h2>
<p>In order to access the server with out the need to specify the port 8080 and use a DNS name, we&#8217;ll have to use <strong>nginx </strong>that will listen on port 80 or 443 and redirect the traffic to 8080. Let&#8217;s install and configure it. In my case, I&#8217;ll access the server as <strong>jenkins.domain.com</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install epel-release
yum -y install nginx
systemctl enable nginx
</pre>
<p>Edit <strong>/etc/nginx/nginx.conf</strong> and delete everything after this line around line 36. The <strong>include </strong>line should stay and the last line <strong>&#8220;}&#8221;</strong> should stay as well. So, the last lines should look like this.</p>
<pre class="brush: bash; title: ; notranslate">
       include /etc/nginx/conf.d/*.conf;
}
</pre>
<p>Go to <strong>/etc/nginx/conf.d</strong> folder and create a new file called <strong>jenkins.conf</strong>.<br />
This is how my <strong>jenkins.conf</strong> file looks like. Change the domain in the highlighted line.</p>
<pre class="brush: bash; highlight: [3]; title: ; notranslate">
server {
    listen 80;
    server_name jenkins.domain.com;

    location / {
        proxy_set_header   X-Forwarded-For $remote_addr;
        proxy_set_header   Host $http_host;
        proxy_pass         &quot;http://127.0.0.1:8080&quot;;
    }
}
</pre>
<p>Don&#8217;t forget to open port 80 in your firewall.</p>
<pre class="brush: plain; title: ; notranslate">
firewall-cmd --permanent --zone=public --add-port=80/tcp
firewall-cmd --reload
</pre>
<p>If you have SELinux, you&#8217;ll have to allow HTTP traffic.</p>
<pre class="brush: bash; title: ; notranslate">
setsebool -P httpd_can_network_connect 1
</pre>
<p>And restart nginx for changes to take effect.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart nginx
</pre>
<p>Now, you can access the server as <strong>jenkins.domain.com</strong>.<br />
For HTTPS traffic, the configuration is different. You&#8217;ll need certificates specified in lines 15 and 16 (under <strong>/etc/nginx</strong>) and the domain changed in 7,13 and 31. Here is the config (<strong>/etc/nginx/conf.d/jenkins.conf</strong>) in order to access jenkins over SSL. </p>
<pre class="brush: bash; highlight: [7,13,15,16,31]; title: ; notranslate">
upstream jenkins {
  server 127.0.0.1:8080 fail_timeout=0;
}

server {
  listen 80;
  server_name jenkins.domain.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  server_name jenkins.domain.com;

  ssl_certificate public_cert.pem;
  ssl_certificate_key private_cert.key;
  client_max_body_size 4M;

  location / {
    proxy_set_header        Host $host:$server_port;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_redirect http:// https://;
    proxy_pass              http://jenkins;
    # Required for new HTTP-based CLI
    proxy_http_version 1.1;
    proxy_request_buffering off;
    proxy_buffering off; # Required for HTTP-based CLI to work over SSL
    # workaround for https://issues.jenkins-ci.org/browse/JENKINS-45651
    add_header 'X-SSH-Endpoint' 'jenkins.domain.com:50022' always;
  }
}
</pre>
<p>Make sure you open port 443 on the firewall as we did for port 80 and change SELinux with <strong>setsebool</strong>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --permanent --zone=public --add-port=443/tcp
firewall-cmd --reload
</pre>
<p>SELinux change.</p>
<pre class="brush: bash; title: ; notranslate">
setsebool -P httpd_can_network_connect 1
</pre>
<p>You should also restart <strong>nginx </strong>for the changes to take effect.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart nginx
</pre>
<p>That&#8217;s it. You should be able to access <strong>Jenkins </strong>as <strong>jenkins.domain.com</strong> over https.</p>
<h1>FreeBSD</h1>
<p>The installation on FreeBSD is a little bit different. You can install <strong>Jenkins </strong>from the packages. It will install JDK as well.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install jenkins
</pre>
<p>The OpenJDK installation depends on these &#8220;memory filesystems&#8221;. Mount them now.</p>
<pre class="brush: bash; title: ; notranslate">
mount -t fdescfs fdesc /dev/fd
mount -t procfs proc /proc
</pre>
<p>&#8230;and make sure they are mounted on boot.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt;&gt; /etc/fstab
fdesc   /dev/fd         fdescfs         rw      0       0
proc    /proc           procfs          rw      0       0
EOF
</pre>
<p>We need to make sure <strong>jenkins </strong>starts on boot.</p>
<pre class="brush: bash; title: ; notranslate">
sysrc jenkins_enable=YES
</pre>
<p>Now, we can start <strong>jenkins</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
service jenkins start
</pre>
<p>Go to <strong>http://[IP]:8180/jenkins</strong> and you should see the welcome screen. Replace the <strong>[IP]</strong> with the IP address of your server. You should see this screen.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/07/P134-02.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/07/P134-02.jpg" alt="" width="861" height="355" class="aligncenter size-full wp-image-8638" srcset="https://blog.andreev.it/wp-content/uploads/2019/07/P134-02.jpg 861w, https://blog.andreev.it/wp-content/uploads/2019/07/P134-02-300x124.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/07/P134-02-768x317.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/07/P134-02-585x241.jpg 585w" sizes="(max-width: 861px) 100vw, 861px" /></a><br />
Cat out that file, copy &#038; paste the value in the browser and click <strong>Continue</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
cat /usr/local/jenkins/secrets/initialAdminPassword
c33a584df234433d9a88e19d8e14c289
</pre>
<p>And that’s it. Follow the prompts to install plugins, provide admin password etc…The jenkins installation on FreeBSD is accessed with a suffix <strong>/jenkins</strong>. If you try to access it without the suffix, you&#8217;ll get this error.</p>
<pre class="brush: bash; title: ; notranslate">
HTTP ERROR 404
Problem accessing /. Reason:
    Not Found
</pre>
<p>It&#8217;s much easier to do the redirects with nginx without the suffix, so we&#8217;ll change it.<br />
Edit this file <strong>/usr/local/etc/rc.d/jenkins</strong> and around line 54 find this.</p>
<pre class="brush: bash; title: ; notranslate">
: ${jenkins_args=&quot;--webroot=${jenkins_home}/war --httpPort=8180 --prefix=/jenkins&quot;}
</pre>
<p>Change it so it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
: ${jenkins_args=&quot;--webroot=${jenkins_home}/war --httpPort=8180 --prefix=/&quot;}
</pre>
<p>Restart <strong>Jenkins </strong>and you&#8217;ll be able to access it as <strong>http://[IP]:8180</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
service jenkins restart
</pre>
<h2>nginx as reverse proxy</h2>
<p>In order to access the server with out the need to specify the port 8180 and use a DNS name, we&#8217;ll have to use nginx that will listen on port 80 or 443 and redirect the traffic to 8180. Let&#8217;s install and configure <strong>nginx</strong>. In my case, I&#8217;ll access the server as <strong>jenkins.domain.com</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install nginx
</pre>
<p>Run on boot.</p>
<pre class="brush: bash; title: ; notranslate">
sysrc nginx_enable=YES
</pre>
<p>Edit <strong>/usr/local/etc/nginx/nginx.conf</strong> and delete everything after this line around line 39 after <strong>#gzip on</strong> directive. Add the include line, so the last three lines look like this.</p>
<pre class="brush: bash; title: ; notranslate">
    #gzip  on;
    include /usr/local/etc/nginx/conf.d/*.conf;
}
</pre>
<p>Create a new <strong>conf.d</strong> folder <strong>mkdir /usr/local/etc/nginx/conf.d</strong> and create a new file called <strong>jenkins.conf</strong>.<br />
This is how my <strong>jenkins.conf</strong> file looks like. Change the domain in the highlighted line.</p>
<pre class="brush: bash; highlight: [3]; title: ; notranslate">
server {
    listen 80;
    server_name jenkins.domain.com;

    location / {
        proxy_set_header   X-Forwarded-For $remote_addr;
        proxy_set_header   Host $http_host;
        proxy_pass         &quot;http://127.0.0.1:8180&quot;;
    }
}
</pre>
<p>Restart <strong>Jenkins </strong>and you should be able to access the site now with http://jenkins.domain.com.<br />
For HTTPS traffic, the configuration is different. You&#8217;ll need certificates specified in lines 15 and 16 (under <strong>/usr/local/etc/nginx</strong>) and the domain changed in 7,13 and 31. Here is the config (<strong>/usr/local/etc/nginx/conf.d/jenkins.conf</strong>) in order to access jenkins over SSL. </p>
<pre class="brush: bash; highlight: [7,13,15,16,31]; title: ; notranslate">
upstream jenkins {
  server 127.0.0.1:8180 fail_timeout=0;
}

server {
  listen 80;
  server_name jenkins.domain.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  server_name jenkins.domain.com;

  ssl_certificate public_cert.pem;
  ssl_certificate_key private_cert.key;
  client_max_body_size 4M;

  location / {
    proxy_set_header        Host $host:$server_port;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_redirect http:// https://;
    proxy_pass              http://jenkins;
    # Required for new HTTP-based CLI
    proxy_http_version 1.1;
    proxy_request_buffering off;
    proxy_buffering off; # Required for HTTP-based CLI to work over SSL
    # workaround for https://issues.jenkins-ci.org/browse/JENKINS-45651
    add_header 'X-SSH-Endpoint' 'jenkins.domain.com:50022' always;
  }
}
</pre>
<p>You should also restart <strong>nginx </strong>for the changes to take effect.</p>
<pre class="brush: bash; title: ; notranslate">
service nginx restart
</pre>
<p>That&#8217;s it. You should be able to access <strong>Jenkins </strong>as <strong>jenkins.domain.com</strong> over https.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/07/freebsd-centos-install-jenkins-behind-a-nginx-reverse-proxy-using-ssl-certificates/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Kubernetes, Flannel and Calico in a single master configuration</title>
		<link>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/</link>
					<comments>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 09 Apr 2019 15:46:01 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Calico]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Flannel]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5127</guid>

					<description><![CDATA[Kubernetes (k8s) is getting a lot of attention and it&#8217;s becoming more and more&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Kubernetes (k8s) is getting a lot of attention and it&#8217;s becoming more and more popular even in enterprises that are quite IT conservative. In this post I&#8217;ll explain how to install Kubernetes on a single master and one node on CentOS. Then, we&#8217;ll install the networking plugins (CNI), Flannel or Calico. At the end I&#8217;ll show an example of how to deploy a simple Node.js app and do rollout update and undoing the rollout. </p>
<div style="border:1px solid red; padding:16px;">
<p style="text-align:center;"><strong><span style="color:#800000;">NOTE ABOUT VERSIONS</span> </strong></p>
<p><center>Kubernetes and the surrounding components are changed on a daily basis. What works today, might not work tomorrow.</center></p>
<p><center>This tutorial assumes that you use CentOS 7, Docker 18.x and Kubernetes 1.14.</center></p>
</div>
<p>For this post, there are some pre-requisites. You will need 2 servers with 2 CPUs and at least 2GB RAM. It is also recommended to have a working DNS. If you don&#8217;t have DNS in your lab, make sure you use <strong>/etc/hosts</strong> for hostname resolution, but you can get away if you use IPs only (not recommended). </p>
<h1>Pre-requisites</h1>
<p>On a fresh installed CentOS 7, do these pre-req commands on both the master and the node at the same time. You need to be logged as root.<br />
Make sure SELinux is disabled.</p>
<pre class="brush: bash; title: ; notranslate">
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
</pre>
<p>Kubernetes doesn&#8217;t like swap, so if you have it in <strong>/etc/fstab</strong>, disable the swap.</p>
<pre class="brush: bash; title: ; notranslate">
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
</pre>
<p>Enable the bridge network module.</p>
<pre class="brush: bash; title: ; notranslate">
modprobe br_netfilter
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-iptables
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-ip6tables
</pre>
<p>Install Docker and change the cgroup from cfsgroup to systemd.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum -y install docker-ce
mkdir /etc/docker
cat &lt;&lt;EOF &gt; /etc/docker/daemon.json
{
  &quot;exec-opts&quot;: &#x5B;&quot;native.cgroupdriver=systemd&quot;],
  &quot;log-driver&quot;: &quot;json-file&quot;,
  &quot;log-opts&quot;: {
    &quot;max-size&quot;: &quot;100m&quot;
  },
  &quot;storage-driver&quot;: &quot;overlay2&quot;,
  &quot;storage-opts&quot;: &#x5B;
    &quot;overlay2.override_kernel_check=true&quot;
  ]
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl enable docker &amp;&amp; systemctl start docker
</pre>
<p>Try this line and make sure the output says <strong>systemd</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker info | grep -i cgroup
Cgroup Driver: systemd
</pre>
<p>Add the Kubernetes repo. </p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/yum.repos.d/kubernetes.repo
&#x5B;kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
</pre>
<h1>Master Node</h1>
<p>Open the firewall <a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports" rel="noopener noreferrer" target="_blank">ports</a>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=6443/tcp --permanent
firewall-cmd --add-port=2379-2380/tcp --permanent
firewall-cmd --add-port=10250-10252/tcp --permanent
firewall-cmd --reload
</pre>
<p>From the repo install kubelet, kubectl and kubeadm and make sure Kubernetes starts on boot.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install kubelet kubectl kubeadm
systemctl enable kubelet
</pre>
<p>Don&#8217;t start Kubernetes yet. It will fail with a message that it can&#8217;t find a config yaml file. Just initialize the cluster. This will also start the kubelet service. Pick one choice (Flannel or Calico).<br />
<strong>NOTE: This line initializes the cluster to be used for Flannel.</strong> </p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=10.244.0.0/16
</pre>
<p><strong>NOTE: This line initializes the cluster to be used for Calico.</strong> </p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=192.168.0.0/16
</pre>
<p>Look at the bottom of the output. You should see something like this. Lines 5,6,7 and 15 and 16 are important. </p>
<pre class="brush: plain; highlight: [5,6,7,15,16]; title: ; notranslate">
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run &quot;kubectl apply -f &#x5B;podnetwork].yaml&quot; with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.175:6443 --token 0z3iov.28rz29hxw9ft4jmg \
    --discovery-token-ca-cert-hash sha256:782d37b5c870ebebd2f17cc3ba1424f305e6a3e293afc04fc2030edfab6bf4b0
</pre>
<p>This means that the cluster initialized OK.<br />
Check the status of both Docker and Kubernetes.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl status docker | grep Active
systemctl status kubelet | grep Active
</pre>
<p>Make sure they are both running. Check <strong>/var/log/messages</strong> if you have any issues.</p>
<h1>Nodes (workers)</h1>
<p>On the worker nodes, make sure you do the same as you did on the master (swap, SELinux, Docker) except that you don&#8217;t have to install kubectl. </p>
<pre class="brush: bash; title: ; notranslate">
yum -y install kubelet kubeadm
systemctl enable kubelet
</pre>
<p>Open the firewall <a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports" rel="noopener noreferrer" target="_blank">ports</a>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=10250/tcp --permanent
firewall-cmd --add-port=30000-32767/tcp --permanent
firewall-cmd --reload
</pre>
<p>Now, you can join the cluster. Use the command that was the output from the <strong>kubeadm init</strong> on the master (see above &#8211; lines 15 and 16).</p>
<pre class="brush: bash; title: ; notranslate">
kubeadm join 192.168.1.175:6443 --token 0z3iov.28rz29hxw9ft4jmg \
    --discovery-token-ca-cert-hash sha256:782d37b5c870ebebd2f17cc3ba1424f305e6a3e293afc04fc2030edfab6bf4b0
</pre>
<p>That&#8217;s how you join nodes to the master. Replace <strong>192.168.1.175</strong> with the IP or hostname of your master node. If everything is OK, you&#8217;ll see something like this.</p>
<pre class="brush: bash; title: ; notranslate">
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
</pre>
<h1>Kubernetes user</h1>
<p>While still logged as root on the master, create the Kubernetes user that you will use for managing the k8s cluster. In my case, I&#8217;ll create a user called k8s with <strong>secret </strong>as password.</p>
<pre class="brush: bash; title: ; notranslate">
useradd k8s -g docker
usermod -aG wheel k8s
echo -e &quot;secret\nsecret&quot; | passwd k8s
</pre>
<p>Log as this user (k8s) and execute these commands. These lines were also an output of the <strong>kubeadm init</strong> command above (5,6 and 7). </p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
</pre>
<p>If you want to add another user to manage the Kubernetes cluster, make sure you execute these 3 lines above for that user. Check if everything looks good.</p>
<pre class="brush: bash; title: ; notranslate">
docker ps
</pre>
<p>You should see a bunch of Kubernetes system containers running (etcd, scheduler, API server).<br />
Then check the nodes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                      STATUS     ROLES    AGE   VERSION
k8smaster.andreev.local   NotReady   master   12m   v1.14.0
k8snode1.andreev.local    NotReady   &lt;none&gt;   10m   v1.14.0
</pre>
<p>The reason the master and the node are not ready is because we don&#8217;t have a network for the cluster. </p>
<h1>Network CNI</h1>
<p>Depending on how you&#8217;ve initialized the cluster, pick one of the network plugins (Flannel or Calico). </p>
<h2>Flannel</h2>
<p>For the network to work, we&#8217;ll have to use one of the CNI plugins. There are many, Flannel, Weave Net, Calico etc.<br />
Let&#8217;s install Flannel. Do this on the master only logged as k8s user. The master will take care of the nodes.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</pre>
<p>Start this little infinite loop and you&#8217;ll see that after 20-30 seconds, both the master and the node will change their status to <strong>Ready</strong>. Hit Ctrl-C to end.</p>
<pre class="brush: bash; title: ; notranslate">
while true
do
kubectl get nodes
sleep 3
done
</pre>
<p>Now, you have a fully working cluster ready. </p>
<h2>Calico</h2>
<p>For the network to work, we&#8217;ll have to use one of the CNI plugins. There are many, Flannel, Weave Net, Calico etc.<br />
Let&#8217;s install Flannel. Do this on the master only logged as k8s user. The master will take care of the nodes.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
</pre>
<p>Start this little infinite loop and you&#8217;ll see that after 20-30 seconds, both the master and the node will change their status to <strong>Ready</strong>. Hit Ctrl-C to end.</p>
<pre class="brush: bash; title: ; notranslate">
while true
do
kubectl get nodes
sleep 3
done
</pre>
<p>Now, you have a fully working cluster ready. </p>
<h1>Deployment</h1>
<p>In this example, I&#8217;ll create a small container that runs a Node.js app that when run it will display &#8220;Hello from &#8221; the hostname of the container. On top of that we&#8217;ll create a load balancer, so we can see how that works.<br />
First, let&#8217;s create the container based on Node.js image. Create a file named <strong>Dockerfile </strong>with this content. </p>
<pre class="brush: bash; title: ; notranslate">
FROM node:latest
LABEL maintainer &quot;kliment@andreev.it&quot;
ADD appv1.js /app.js
ENTRYPOINT &#x5B;&quot;node&quot;, &quot;app.js&quot;]
</pre>
<p>This is our application. Save it as <strong>appv1.js</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
const http = require('http');
const os = require('os');
const port = 3000;

const server = http.createServer((req, res) =&gt; {
  res.statusCode = 200;
  res.end('Hello from ' + os.hostname() + '\n');
});

server.listen(port);
</pre>
<p>Create the container. You&#8217;ll need a valid Docker Hub login. In my case, my username is klimenta. Replace it with yours.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t klimenta/appv1:latest .
</pre>
<p>It&#8217;s time to login to Docker Hub and upload the image there. You&#8217;ll be prompted for a username and password.</p>
<pre class="brush: bash; title: ; notranslate">
docker login
</pre>
<p>Upload the image.</p>
<pre class="brush: bash; title: ; notranslate">
docker push klimenta/appv1:latest
</pre>
<p>Create a Kubernetes deployment file named <strong>deployment.yaml</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: appv1
spec:
  replicas: 3
  template:
    metadata:
      name: appv1
      labels:
        app: appv1
    spec:
      containers:
      - image: klimenta/appv1:latest
        name: nodejs
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: appv1
  ports:
  - port: 80
    targetPort: 3000
</pre>
<p>We are creating a deployment with 3 replicas and a load balancer that listens on port 80 and sends the traffic to port 3000 on the pods with our application.<br />
Create the deployment and the load balanced service.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f deployment.yaml
</pre>
<p>After about 30 seconds, you&#8217;ll see that your pods are ready. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
appv1-596dd64666-4k7qn   1/1     Running   0          93m
appv1-596dd64666-gn5gr   1/1     Running   0          93m
appv1-596dd64666-vv9h5   1/1     Running   0          93m
</pre>
<p>The load balancer is also ready. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get svc
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1       &lt;none&gt;        443/TCP        102m
loadbalancer   LoadBalancer   10.111.212.41   &lt;pending&gt;     80:30310/TCP   94m
</pre>
<p>If you hit the load balancer, you&#8217;ll see a response. Replace the IP with yours.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl http://10.111.212.41
Hello from appv1-596dd64666-gn5gr
</pre>
<p>Now, let&#8217;s say that we created a new version of our application. Copy appv1.js as appv2.js and change  <strong>appv2.js</strong> a little bit.</p>
<pre class="brush: bash; title: ; notranslate">
cp appv1.js appv2.js
</pre>
<p>The appv2.js should look like this.</p>
<pre class="brush: xml; highlight: [7]; title: ; notranslate">
const http = require('http');
const os = require('os');
const port = 3000;

const server = http.createServer((req, res) =&gt; {
  res.statusCode = 200;
  res.end('Greetings from ' + os.hostname() + '\n');
});

server.listen(port);
</pre>
<p>Change the <strong>Dockerfile </strong>to look like this.</p>
<pre class="brush: bash; title: ; notranslate">
FROM node:latest
LABEL maintainer &quot;kliment@andreev.it&quot;
ADD appv2.js /app.js
ENTRYPOINT &#x5B;&quot;node&quot;, &quot;app.js&quot;]
</pre>
<p>Build the new image and upload it to Docker Hub.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t klimenta/appv2:latest .
docker push klimenta/appv2:latest
</pre>
<p>Deploy the new application.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set image deployment appv1 nodejs=klimenta/appv2:latest
</pre>
<p>If you check the app now, you&#8217;ll see that it reflects the new version.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl http://10.111.212.41
Greetings from appv1-5d949774f5-b794k
</pre>
<p>But what if there is a bug in our application and we want to revert it back to the initial one? Easy.</p>
<pre class="brush: bash; highlight: [1,3]; title: ; notranslate">
kubectl rollout undo deployment appv1
deployment.extensions/appv1 rolled back
curl http://10.111.212.41
Hello from appv1-596dd64666-94gqh
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS, pfSense: Site-to-site VPN tunnel with strongswan and pfSense</title>
		<link>https://blog.andreev.it/2019/03/150-centos-pfsense-site-to-site-vpn-tunnel-with-strongswan-and-pfsense/</link>
					<comments>https://blog.andreev.it/2019/03/150-centos-pfsense-site-to-site-vpn-tunnel-with-strongswan-and-pfsense/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sat, 09 Mar 2019 20:32:16 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[pfSense]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[strongswan]]></category>
		<category><![CDATA[VPN]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=4701</guid>

					<description><![CDATA[I have written a lot about pfSense and different types of VPN scenarios (AWS,&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>I have written a lot about pfSense and different types of VPN scenarios (AWS, Azure), but never created a post about a site-to-site VPN tunnel with CentOS running strongswan and pfSense. The scenario described here works with CentOS, but it will work with any other Linux of BSD distribution. The syntax for strongswan is the same, it&#8217;s just the configuration file names and locations that are different across distributions. </p>
<h1>Prerequisites</h1>
<p>Both the pfSense box and CentOS need to have public IPs. While it&#8217;s possible to have them behind NAT, this scenario only covers configurations with public IPs. You should disable the firewalld on CentOS (initially). Both of them need two network interfaces. In my case this is how it looks like.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-01.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-01.jpg" alt="" width="896" height="532" class="aligncenter size-full wp-image-8464" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-01.jpg 896w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-01-300x178.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-01-768x456.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-01-585x347.jpg 585w" sizes="(max-width: 896px) 100vw, 896px" /></a><br />
It&#8217;s also easier if you create a small spreadsheet so you don&#8217;t loose track of the IPs and subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-02.png" alt="" width="396" height="89" class="aligncenter size-full wp-image-8465" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-02.png 396w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-02-300x67.png 300w" sizes="(max-width: 396px) 100vw, 396px" /></a></p>
<h1>CentOS configuration</h1>
<p>I am using CentOS 7. First, we have to install strongswan and disable the firewall temporarily.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install epel-release
yum -y install strongswan
systemctl enable strongswan
systemctl stop firewalld
systemctl disable firewalld
</pre>
<p>Once installed, change these system variables so you allow IP forwarding and prevent ICMP redirects. </p>
<pre class="brush: bash; title: ; notranslate">
echo &quot;net.ipv4.ip_forward=1&quot; &gt;&gt; /etc/sysctl.conf
echo &quot;net.ipv4.conf.all.accept_redirects=0&quot; &gt;&gt; /etc/sysctl.conf
echo &quot;net.ipv4.conf.all.send_redirects=0&quot; &gt;&gt; /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
</pre>
<p>strongswan on CentOS places its config under <strong>/etc/strongswan</strong> directory. We’ll use a config with pre-shared key because it’s easier to implement. So, edit the <strong>/etc/strongswan/ipsec.conf</strong> file, remove everything there and paste this config. Of course, this assumes that you don’t have any working configs there. Now, look at the highlighted lines. That’s where you have to replace the IPs and the subnets with your values. E.g. line 13 is the private IP of the CentOS server, 14 is the subnet, 15 is the ID of the server (IP or hostname), 17 is the external IP of the pfSense server, 18 is its subnet and line 19 is the ID of the pfSense server (IP or hostname). This is where the small spreadsheet above comes handy.</p>
<pre class="brush: bash; highlight: [13,14,15,17,18,19]; title: ; notranslate">
config setup

conn %default
        ikelifetime=60m
        keylife=20m
        rekeymargin=3m
        keyingtries=1
        authby=secret
        keyexchange=ikev2
        mobike=no

conn net-net
        left=148.27.56.195
        leftsubnet=10.1.96.0/20
        leftid=148.27.56.195
        leftfirewall=no
        right=23.183.125.249
        rightsubnet=192.168.1.1/24
        rightid=23.183.125.249
        ike=aes256-sha256-modp2048!
        esp=aes256-sha256!
        pfs=no
        auto=start
        lifetime=1h
</pre>
<p>Then, edit the <strong>/etc/strongswan/ipsec.secret</strong> file and specify the pre-shared key. This is a password that the servers will use to authenticate. Make sure it’s something more complex in production. In my case, the password is &#8220;<strong>secret</strong>&#8220;.</p>
<pre class="brush: bash; title: ; notranslate">	
# ipsec.secrets - strongSwan IPsec secrets file
148.27.56.195 23.183.125.249 : PSK &quot;secret&quot;
</pre>
<h1>pfSense</h1>
<p>Now that the CentOS strongswan box is configured, we can configure pfSense. In pfSense, go to <strong>VPN | IPSec</strong> from the menu and click on <strong>Add P1</strong> button. Fill out the <strong>General Information</strong> section, so it looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-03.png" alt="" width="1153" height="431" class="aligncenter size-full wp-image-8466" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-03.png 1153w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-03-300x112.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-03-1024x383.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-03-768x287.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-03-585x219.png 585w" sizes="(max-width: 1153px) 100vw, 1153px" /></a><br />
As you can see, we use <strong>IKEv2 </strong>and our remote gateway is <strong>148.27.56.195</strong> which is the CentOS strongswan box.<br />
For <strong>Phase 1 Proposal (Authentication and Algorithms)</strong>, fill out the section so it looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-04.png" alt="" width="1160" height="535" class="aligncenter size-full wp-image-8467" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-04.png 1160w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-04-300x138.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-04-1024x472.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-04-768x354.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-04-585x270.png 585w" sizes="(max-width: 1160px) 100vw, 1160px" /></a><br />
From the screenshot above, you can see that the values correspond to what we have in ipsec.conf file.<br />
For the <strong>Advanced Options</strong> section, use the defaults.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-05.png" alt="" width="1173" height="511" class="aligncenter size-full wp-image-8468" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-05.png 1173w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-05-300x131.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-05-1024x446.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-05-768x335.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-05-1170x511.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-05-585x255.png 585w" sizes="(max-width: 1173px) 100vw, 1173px" /></a><br />
Click <strong>Save</strong> and then from the main <strong>IPSec | Tunnels</strong> menu, click on <strong>Add P2</strong> button.<br />
For Phase 2, fill out these values for the <strong>General Information</strong> section.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-06.png" alt="" width="1161" height="424" class="aligncenter size-full wp-image-8469" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-06.png 1161w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-06-300x110.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-06-1024x374.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-06-768x280.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-06-585x214.png 585w" sizes="(max-width: 1161px) 100vw, 1161px" /></a><br />
Change the values for <strong>Local Network</strong> and <strong>Remote Network</strong> to suit your needs. These are the local subnets behind pfSense and strongswan.<br />
For <strong>Phase 2 Proposal (SA/Key Exchange)</strong> section, choose these values.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-07.png" alt="" width="1150" height="631" class="aligncenter size-full wp-image-8470" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-07.png 1150w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-07-300x165.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-07-1024x562.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-07-768x421.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-07-585x321.png 585w" sizes="(max-width: 1150px) 100vw, 1150px" /></a><br />
For the <strong>Advanced Configuration</strong> section, you can leave it as is, or put the private IP of the CentOS box so the IPSec protocol sends keep-alive pings. It&#8217;s not mandatory, but if your tunnel fails frequently, you can configure this field.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-08.png" alt="" width="1156" height="131" class="aligncenter size-full wp-image-8471" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-08.png 1156w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-08-300x34.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-08-1024x116.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-08-768x87.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-08-585x66.png 585w" sizes="(max-width: 1156px) 100vw, 1156px" /></a><br />
Click <strong>Save </strong>and the VPN config is done. But, we have to tell pfSense to allow IPSec traffic. So, from the menu go to <strong>Firewall | Rules</strong> and click on <strong>IPSec </strong>submenu. Click <strong>Add </strong>and fill out the form so it looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-09.png" alt="" width="1156" height="429" class="aligncenter size-full wp-image-8472" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-09.png 1156w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-09-300x111.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-09-1024x380.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-09-768x285.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-09-585x217.png 585w" sizes="(max-width: 1156px) 100vw, 1156px" /></a><br />
&#8230;and this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/03/P127-10.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/03/P127-10.png" alt="" width="1157" height="442" class="aligncenter size-full wp-image-8473" srcset="https://blog.andreev.it/wp-content/uploads/2019/03/P127-10.png 1157w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-10-300x115.png 300w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-10-1024x391.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-10-768x293.png 768w, https://blog.andreev.it/wp-content/uploads/2019/03/P127-10-585x223.png 585w" sizes="(max-width: 1157px) 100vw, 1157px" /></a><br />
Click <strong>Save </strong>and go back to the CentOS box.</p>
<h1>Site-to-site VPN tunnel</h1>
<p>At this point you can start the VPN.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start strongswan
</pre>
<p>If everything is OK, the log will look like this.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
tail -f /var/log/messages
Mar  9 16:15:00 titan charon: 09&#x5B;NET] sending packet: from 148.27.56.195&#x5B;500] to 23.183.125.249&#x5B;500] (256 bytes)
Mar  9 16:15:00 titan charon: 10&#x5B;NET] received packet: from 23.183.125.249&#x5B;500] to 148.27.56.195&#x5B;500] (224 bytes)
Mar  9 16:15:00 titan charon: 10&#x5B;ENC] parsed IKE_AUTH response 1 &#x5B; IDr AUTH N(ESP_TFC_PAD_N) SA TSi TSr ]
Mar  9 16:15:00 titan charon: 10&#x5B;IKE] authentication of '23.183.125.249' with pre-shared key successful
Mar  9 16:15:00 titan charon: 10&#x5B;IKE] IKE_SA net-net&#x5B;1] established between 148.27.56.195&#x5B;148.27.56.195]...23.183.125.249&#x5B;23.183.125.249]
Mar  9 16:15:00 titan charon: 10&#x5B;IKE] scheduling reauthentication in 3350s
Mar  9 16:15:00 titan charon: 10&#x5B;IKE] maximum IKE_SA lifetime 3530s
Mar  9 16:15:00 titan charon: 10&#x5B;IKE] received ESP_TFC_PADDING_NOT_SUPPORTED, not using ESPv3 TFC padding
Mar  9 16:15:00 titan charon: 10&#x5B;CFG] selected proposal: ESP:AES_CBC_256/HMAC_SHA2_256_128/NO_EXT_SEQ
Mar  9 16:15:00 titan charon: 10&#x5B;IKE] CHILD_SA net-net{1} established with SPIs c82fdbae_i ce57b856_o and TS 10.1.96.0/20 === 192.168.1.0/24
</pre>
<p>Check the status.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
strongswan status
Security Associations (1 up, 0 connecting):
     net-net&#x5B;3]: ESTABLISHED 3 minutes ago, 148.27.56.195&#x5B;148.27.56.195]...23.183.125.249&#x5B;23.183.125.249]
     net-net{3}:  INSTALLED, TUNNEL, reqid 2, ESP SPIs: c818e2d7_i c34c4c3d_o
     net-net{3}:   10.1.96.0/20 === 192.168.1.0/24
</pre>
<p>If the current CentOS box is not your Internet gateway for the servers behind, you have to add a static route to the pfSense subnet. So, on each server behind CentOS, do something like this.</p>
<pre class="brush: bash; title: ; notranslate">
route add 192.168.1.0 mask 255.255.255.0 10.1.96.3 -p
</pre>
<p>Check the documentation for other OSes. You have to tell the OS to use 10.1.96.3 (internal strongswan IP) as a gateway to 192.168.1.0 subnet. Because my pfSense box is also my gateway, I don&#8217;t have to make these changes. </p>
<h1>Firewall</h1>
<p>Now, everything is up and running, but we have the firewall disabled and the CentOS box is exposed to Internet. Not good. In order to fix that, we have to make some changes and allow the IPSec protocol to flow through the firewall.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --set-default-zone=dmz
firewall-cmd --zone=dmz --permanent --add-rich-rule='rule protocol value=&quot;esp&quot; accept' 
firewall-cmd --zone=dmz --permanent --add-rich-rule='rule protocol value=&quot;ah&quot; accept' 
firewall-cmd --zone=dmz --permanent --add-port=500/udp 
firewall-cmd --zone=dmz --permanent --add-port=4500/udp 
firewall-cmd --permanent --add-service=&quot;ipsec&quot;
firewall-cmd --zone=dmz --permanent --add-masquerade
firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -m policy --pol ipsec --dir out -j ACCEPT
firewall-cmd --reload
</pre>
<p>&#8230;and you are all set.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/03/150-centos-pfsense-site-to-site-vpn-tunnel-with-strongswan-and-pfsense/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Upgrade PostgreSQL from 9.2 to 9.6</title>
		<link>https://blog.andreev.it/2019/02/146-centos-postgresql-upgrade-postgresql-from-9-2-to-9-6/</link>
					<comments>https://blog.andreev.it/2019/02/146-centos-postgresql-upgrade-postgresql-from-9-2-to-9-6/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 05 Feb 2019 00:12:37 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[upgrade]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=4381</guid>

					<description><![CDATA[If you&#8217;ve installed BitBucket on a CentOS 7 server and use PostgreSQL as the&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>If you&#8217;ve installed BitBucket on a CentOS 7 server and use PostgreSQL as the back-end database, you&#8217;ve probably seen this nagging warning that support for PostgreSQL 9.2.24 has been deprecated and will be removed in an upcoming release.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/02/P123-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/02/P123-01.png" alt="" width="508" height="257" class="aligncenter size-full wp-image-8420" srcset="https://blog.andreev.it/wp-content/uploads/2019/02/P123-01.png 508w, https://blog.andreev.it/wp-content/uploads/2019/02/P123-01-300x152.png 300w" sizes="(max-width: 508px) 100vw, 508px" /></a><br />
While PostgeSQL 9.2 is quite old and they are already announcing version 12, mind that any of the Atlassian stack can&#8217;t support anything more than version 9.6 at the time of this writing.<br />
So, I&#8217;ll explain how to upgrade PostgreSQL on CentOS from 9.2.24 to PostgreSQL 9.6. While this post is for CentOS, it might work for other distros too. There are some prerequisites though. First, I assume that you installed PostgreSQL using <strong>yum </strong>from the default repo. In this case, your database most likely resides under <strong>/var/lib/pgsql/data</strong> and the binaries are under <strong>/usr/bin/psql</strong>. If you use the default version, then you probably use <strong>systemctl start/stop postgresql</strong> to manage the start/stop of the database daemon. If you have something different, the following tutorial will probably work, but you&#8217;ll have to adjust some directories.<br />
First thing first, let&#8217;s check the version of PostgreSQL that we use.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
psql --version
psql (PostgreSQL) 9.2.24
</pre>
<p>Next, stop any services that use the database, such as BitBucket, Jira or Confluence or whatever you have there.<br />
Log as the postgres user that you use to manage the database. By the default this user is <strong>postgres</strong>.<br />
Do a full backup and move the backup somewhere because the home directory for the postgres user is where the database home is. You can compress the SQL file first. It&#8217;s just a plain text file.</p>
<pre class="brush: bash; title: ; notranslate">
pg_dumpall &gt; backup.sql
mv backup.sql somewhere/
</pre>
<p>Once done, stop the postgreSQL daemon, but log as root first. You won&#8217;t be able to stop the daemon using the postgres user.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl stop postgresql
</pre>
<p>Go to a temp folder and get the RPM package from the PostgreSQL site. In case the link doesn&#8217;t work, go to the website using a browser and see what&#8217;s the latest 9.6 version.</p>
<pre class="brush: bash; title: ; notranslate">
cd /tmp
wget https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
</pre>
<p>Install the newer version.</p>
<pre class="brush: bash; title: ; notranslate">
rpm -ivh pgdg-centos96-9.6-3.noarch.rpm
yum install postgresql96-server
</pre>
<p>You&#8217;ll get some warning that symlinks can&#8217;t be created but you can ignore them. The new version installs the binaries under <strong>/usr/psql-9.6/bin</strong>.<br />
Log as the postgres user and initialize the new database.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
/usr/pgsql-9.6/bin/initdb -D /var/lib/pgsql/9.6/data/
The files belonging to this database system will be owned by user &quot;postgres&quot;.
This user must also own the server process.

The database cluster will be initialized with locale &quot;en_US.UTF-8&quot;.
The default database encoding has accordingly been set to &quot;UTF8&quot;.
The default text search configuration will be set to &quot;english&quot;.

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/pgsql/9.6/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

WARNING: enabling &quot;trust&quot; authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    /usr/pgsql-9.6/bin/pg_ctl -D /var/lib/pgsql/9.6/data/ -l logfile start
</pre>
<p>Ignore the line that says that we can start the server. We have to upgrade the current database first. Make sure that the directories below are the same as yours. If not, make changes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
/usr/pgsql-9.6/bin/pg_upgrade --old-datadir /var/lib/pgsql/data/ --new-datadir /var/lib/pgsql/9.6/data/ --old-bindir /usr/bin/ --new-bindir /usr/pgsql-9.6/bin/
Performing Consistency Checks
-----------------------------
Checking cluster versions                                   ok

*failure*
Consult the last few lines of &quot;pg_upgrade_server.log&quot; for
the probable cause of the failure.

connection to database failed: could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket &quot;/var/lib/pgsql/.s.PGSQL.50432&quot;?


could not connect to old postmaster started with the command:
&quot;/usr/bin/pg_ctl&quot; -w -l &quot;pg_upgrade_server.log&quot; -D &quot;/var/lib/pgsql/data/&quot; -o &quot;-p 50432 -b  -c listen_addresses='' -c unix_socket_permissions=0700 -c unix_socket_directory='/var/lib/pgsql'&quot; start
Failure, exiting
</pre>
<p>Oh boy. If you check the error message, you&#8217;ll get some more info.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
tail /var/lib/pgsql/pg_upgrade_server.log
server stopped


command: &quot;/usr/bin/pg_ctl&quot; -w -l &quot;pg_upgrade_server.log&quot; -D &quot;/var/lib/pgsql/data/&quot; -o &quot;-p 50432 -b  -c listen_addresses='' -c unix_socket_permissions=0700 -c unix_socket_directory='/var/lib/pgsql'&quot; start &gt;&gt; &quot;pg_upgrade_server.log&quot; 2&gt;&amp;1
waiting for server to start....FATAL:  unrecognized configuration parameter &quot;unix_socket_directory&quot;
 stopped waiting
pg_ctl: could not start server
Examine the log output.
</pre>
<p>The reason being that as of PostgreSQL version 9.3 the config parameter <strong>unix_socket_directory</strong> has been replaced with <strong>unix_socket_directories</strong>. There is a workaround thanks to this <a href="https://dba.stackexchange.com/users/55596/ziggy-crueltyfree-zeitgeister" rel="noopener noreferrer" target="_blank">guy</a>. The link for more info is <a href="https://dba.stackexchange.com/questions/50135/pg-upgrade-unrecognized-configuration-parameter-unix-socket-directory" rel="noopener noreferrer" target="_blank">here</a>.<br />
Pretty much, all you have to do is execute these lines first. Execute these lines as root or sudo user.</p>
<pre class="brush: bash; title: ; notranslate">
mv /usr/bin/pg_ctl{,-orig}
echo '#!/bin/bash' &gt; /usr/bin/pg_ctl
echo '&quot;$0&quot;-orig &quot;${@/unix_socket_directory/unix_socket_directories}&quot;' &gt;&gt; /usr/bin/pg_ctl
chmod +x /usr/bin/pg_ctl
</pre>
<p>Now you can do the upgrade.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
/usr/pgsql-9.6/bin/pg_upgrade --old-datadir /var/lib/pgsql/data/ --new-datadir /var/lib/pgsql/9.6/data/ --old-bindir /usr/bin/ --new-bindir /usr/pgsql-9.6/bin/
Performing Consistency Checks
-----------------------------
Checking cluster versions                                   ok
Checking database user is the install user                  ok
Checking database connection settings                       ok
Checking for prepared transactions                          ok
Checking for reg* system OID user data types                ok
Checking for contrib/isn with bigint-passing mismatch       ok
Checking for roles starting with 'pg_'                      ok
Checking for invalid &quot;line&quot; user columns                    ok
Creating dump of global objects                             ok
Creating dump of database schemas
                                                            ok
Checking for presence of required libraries                 ok
Checking database user is the install user                  ok
Checking for prepared transactions                          ok

If pg_upgrade fails after this point, you must re-initdb the
new cluster before continuing.

Performing Upgrade
------------------
Analyzing all rows in the new cluster                       ok
Freezing all rows on the new cluster                        ok
Deleting files from new pg_clog                             ok
Copying old pg_clog to new server                           ok
Setting next transaction ID and epoch for new cluster       ok
Deleting files from new pg_multixact/offsets                ok
Setting oldest multixact ID on new cluster                  ok
Resetting WAL archives                                      ok
Setting frozenxid and minmxid counters in new cluster       ok
Restoring global objects in the new cluster                 ok
Restoring database schemas in the new cluster
                                                            ok
Setting minmxid counter in new cluster                      ok
Copying user relation files
                                                            ok
Setting next OID for new cluster                            ok
Sync data directory to disk                                 ok
Creating script to analyze new cluster                      ok
Creating script to delete old cluster                       ok

Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade so,
once you start the new server, consider running:
    ./analyze_new_cluster.sh

Running this script will delete the old cluster's data files:
    ./delete_old_cluster.sh
</pre>
<p>Start the new database now and check everything. As you can see, the startup script is different. This time we have to specify the version of PostgreSQL. As root do:</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start postgresql-9.6
</pre>
<p>You can revert the change back.</p>
<pre class="brush: bash; title: ; notranslate">
mv -f /usr/bin/pg_ctl{-orig,}
</pre>
<p>Log as postgres user and do a check up. It might take some time if your database is large.</p>
<pre class="brush: bash; title: ; notranslate">
./analyze_new_cluster.sh
</pre>
<p>You can delete the old database if you want.</p>
<pre class="brush: bash; title: ; notranslate">
./delete_old_cluster.sh
</pre>
<p>If you check the new version, you&#8217;ll see that it&#8217;s the same.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
psql --version
psql (PostgreSQL) 9.2.24
</pre>
<p>The database is upgraded and running 9.6, it&#8217;s just the <strong>psql </strong>binary that&#8217;s the old version. You have to tell the postgres user to use the new binaries. Delete the old binaries first, uninstall the old version, make sure it doesn&#8217;t start on boot and enable the new version. Also, if you have any specific configurations, make sure you compare<strong> postgresql.conf</strong> and <strong>pg_hba.conf</strong> under <strong>/var/lib/pgsql/data</strong> and <strong>/var/lib/pgsql/9.6/data</strong>. Log as root first. Make sure it says 9.2 when you run yum remove.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl disable postgresql
yum remove postgresql
</pre>
<p>Enable postgreSQL to boot on start.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable postgresql-9.6
</pre>
<p>Add the following line under <strong>.bash_profile</strong> for the postgres user.</p>
<pre class="brush: bash; title: ; notranslate">
export PATH=$PATH:/usr/pgsql-9.6/bin
</pre>
<p>If you don&#8217;t have a <strong>.bash_profile</strong> file, check if there is any <strong>.bash_profile.rpmsave</strong>. Rename this one as <strong>.bash_profile</strong> and add the above line. It should look like this.</p>
<pre class="brush: bash; title: ; notranslate">
&#x5B; -f /etc/profile ] &amp;&amp; source /etc/profile
PGDATA=/var/lib/pgsql/9.6/data
export PGDATA
# If you want to customize your settings,
# Use the file below. This is not overridden
# by the RPMS.
&#x5B; -f /var/lib/pgsql/.pgsql_profile ] &amp;&amp; source /var/lib/pgsql/.pgsql_profile
export PATH=$PATH:/usr/pgsql-9.6/bin
</pre>
<p>If you log off, log back on as the postgres user or execute <strong>source .bash_profile</strong> and check the version, you&#8217;ll see that you are all set.</p>
<pre class="brush: bash; title: ; notranslate">
psql --version
psql (PostgreSQL) 9.6.11
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/02/146-centos-postgresql-upgrade-postgresql-from-9-2-to-9-6/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Apache Guacamole &#8211; Remote Desktop Gateway</title>
		<link>https://blog.andreev.it/2018/12/142-centos-apache-guacamole-remote-desktop-gateway/</link>
					<comments>https://blog.andreev.it/2018/12/142-centos-apache-guacamole-remote-desktop-gateway/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Fri, 28 Dec 2018 23:06:43 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[bypass proxy]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[Guacamole]]></category>
		<category><![CDATA[RDP gateway]]></category>
		<category><![CDATA[ssh gateway]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=4239</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to install Apache Guacamole from the source on&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to install <a href="https://guacamole.apache.org/" rel="noopener noreferrer" target="_blank">Apache Guacamole</a> from the source on a CentOS server. Guacamole is a clientless remote gateway that supports VNC, RDP, SSH and telnet protocols. The purpose of the remote gateway is to  allow you to connect to a device over the gateway, but not directly, in case you have firewall restrictions between your computer and the end device. Let&#8217;s say you have a desktop at work and a server with a public IP at home or in the cloud. The server at home/cloud normally listens on port 3389 for RDP or port 22 for SSH. But your corporate firewall doesn&#8217;t allow outgoing traffic on these ports so you can&#8217;t connect from your work to your servers. Here comes the remote gateway. This server listens on ports 80 and/or 443 and will tunnel the traffic from your work desktop to your home server over 80 or 443. The remote gateway will need access to your end device on the specified ports for RDP or SSH. Most corporate environments allow port 80 and 443 only, that way you can bypass your corporate firewall. I have another <a href="https://blog.andreev.it/?p=3765" rel="noopener noreferrer" target="_blank">post </a>that describes the same exact scenario using Microsoft RDP Gateway, shellinabox and tunneling over putty.  </p>
<h1>Install Apache Guacamole</h1>
<p>Apache Guacamole has some pre-requisites and we need to install them first. Let&#8217;s create a work directory where we can place all files necessary.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /workdir &amp;&amp; cd /workdir
yum -y install wget libpng-devel libjpeg-turbo-devel cairo-devel uuid-devel  epel-release maven tomcat
yum -y install freerdp-devel pango-devel libssh2-devel libtelnet-devel libvncserver-devel 
yum -y install pulseaudio-libs-devel openssl-devel libvorbis-devel libwebp-devel terminus-fonts
systemctl enable tomcat
</pre>
<p>The FFmpeg library is not in the official or EPEL repo, so we need to install it as RPM package.</p>
<pre class="brush: bash; title: ; notranslate">
rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum -y install ffmpeg-devel
</pre>
<p>We also need some compiler tools. </p>
<pre class="brush: bash; title: ; notranslate">
yum -y groupinstall &quot;Development Tools&quot;
</pre>
<p>Guacamole comes in two parts, a server and a client. The client is a Java servlet running on Tomcat, but both are installed on a server. Don&#8217;t get confused, the Guacamole client has nothing to do with the end user client. You don&#8217;t need to install any clients on your desktop. Just a plain HTML5 browser.<br />
Let&#8217;s download the server first.</p>
<pre class="brush: bash; title: ; notranslate">
wget -O server.tar.gz &quot;http://archive.apache.org/dist/guacamole/0.9.14/source/guacamole-server-0.9.14.tar.gz&quot;
</pre>
<p>Untar the tarball and configure the server before compiling it.</p>
<pre class="brush: bash; title: ; notranslate">
tar xzvf server.tar.gz
cd guacamole-server-0.9.14
./configure --with-init-dir=/etc/init.d
</pre>
<p>You should see something like this as the output. Make sure it matches. All of the components except winsock should be installed. If not, check the first <strong>yum install</strong> commands above and see what went wrong.<br />
<strong>NOTE</strong>: Some newer versions of CentOS 7 now come with freerdp 2.0 as a default package. If you see that freerdp sections says no, it&#8217;s because you have freerdp 2.0 installed. Install freerdp1.2 with <strong>yum -y install freerdp1.2</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
Library status:

     freerdp ............. yes
     pango ............... yes
     libavcodec .......... yes
     libavutil ........... yes
     libssh2 ............. yes
     libssl .............. yes
     libswscale .......... yes
     libtelnet ........... yes
     libVNCServer ........ yes
     libvorbis ........... yes
     libpulse ............ yes
     libwebp ............. yes
     wsock32 ............. no

   Protocol support:

      RDP ....... yes
      SSH ....... yes
      Telnet .... yes
      VNC ....... yes

   Services / tools:

      guacd ...... yes
      guacenc .... yes

   Init scripts: /etc/init.d

Type &quot;make&quot; to compile guacamole-server. 
</pre>
<p>If everything looks good, compile and install.</p>
<pre class="brush: bash; title: ; notranslate">
make
make install
ldconfig
</pre>
<p>Let&#8217;s enable the automatic startup for the Guacamole daemon.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable guacd
</pre>
<p>It&#8217;s time to install the client. You can get a pre-compiled WAR library for the client or you can compile it yourself. I&#8217;ll do the later.</p>
<pre class="brush: bash; title: ; notranslate">
cd /workdir
wget -O client.tar.gz &quot;http://archive.apache.org/dist/guacamole/0.9.14/source/guacamole-client-0.9.14.tar.gz&quot;
tar xzvf client.tar.gz
cd guacamole-client-0.9.14
mvn package
</pre>
<p>Copy the WAR file under Tomcat.</p>
<pre class="brush: bash; title: ; notranslate">
cp guacamole/target/guacamole-0.9.14.war /var/lib/tomcat/webapps/guacamole.war
</pre>
<h1>MySQL server</h1>
<p>In order for the end users to authenticate to Guacamole, we can choose LDAP authentication, file based authentication in XML file and authentication that&#8217;s stored in a SQL database (MySQL, PostgreSQL). While the file based authentication is the easiest to configure, it doesn&#8217;t allow configuring different levels of access. LDAP is an overkill, so the best bet is a MySQL server that&#8217;s used to store the authentication.<br />
I&#8217;ll use MariaDB SQL. Let&#8217;s install it and configure it first.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install mariadb-server
systemctl enable mariadb
systemctl start mariadb
</pre>
<p>This is the initial configuration. Highlighted lines is what you type, the rest is output.</p>
<pre class="brush: bash; highlight: [1,11,17,18,19,31,37,44,53]; title: ; notranslate">
mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none):
OK, successfully used password, moving on...

Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.

Set root password? &#x5B;Y/n] Y
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables..
 ... Success!


By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? &#x5B;Y/n] Y
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? &#x5B;Y/n] Y
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? &#x5B;Y/n] Y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? &#x5B;Y/n] Y
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!
</pre>
<p>We&#8217;ll have to create the database now and assign a SQL user with rights to that database. Enter the root SQL password that you&#8217;ve previously created and change the database name, user and password to suit your needs.</p>
<pre class="brush: bash; highlight: [1,2,11,14,17,20,23]; title: ; notranslate">
mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 11
Server version: 5.5.60-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB &#x5B;(none)]&gt; CREATE DATABASE guacamole_db;
Query OK, 1 row affected (0.00 sec)

MariaDB &#x5B;(none)]&gt; CREATE USER 'guacamole_user'@'localhost' IDENTIFIED BY 'guacamole_password';
Query OK, 0 rows affected (0.00 sec)

MariaDB &#x5B;(none)]&gt; GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user'@'localhost';
Query OK, 0 rows affected (0.00 sec)

MariaDB &#x5B;(none)]&gt; FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

MariaDB &#x5B;(none)]&gt; quit
Bye
</pre>
<p>We also need a JDBC auth plugin for MySQL, so Guacamole can talk to MySQL. You&#8217;ll be prompted for the MySQL root password at the end of the <strong>cat </strong>command.</p>
<pre class="brush: bash; title: ; notranslate">
cd /workdir
wget -O auth.tar.gz &quot;http://archive.apache.org/dist/guacamole/0.9.14/binary/guacamole-auth-jdbc-0.9.14.tar.gz&quot;
tar xzvf auth.tar.gz
cd guacamole-auth-jdbc-0.9.14/mysql/schema/
cat *.sql | mysql -u root -p guacamole_db
</pre>
<p>We have to copy the JAR archive to a specific location so Guacamole can find it.</p>
<pre class="brush: bash; title: ; notranslate">
cd /workdir
mkdir -p /etc/guacamole/{extensions,lib}
cp guacamole-auth-jdbc-0.9.14/mysql/guacamole-auth-jdbc-mysql-0.9.14.jar /etc/guacamole/extensions
</pre>
<p>We also need a MySQL connector for Java, so the Java servlets can talk to MySQL.</p>
<pre class="brush: bash; title: ; notranslate">
cd /workdir
wget -O conn.tar.gz https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.13.tar.gz
tar xzvf conn.tar.gz
cp mysql-connector-java-8.0.13/mysql-connector-java-8.0.13.jar /etc/guacamole/lib/
</pre>
<p>Finally, we have to tell Guacamole that we&#8217;ll use MySQL. Create a file called <strong>guacamole.properties</strong> under <strong>/etc/guacamole</strong> directory. Make sure you have the correct database name, user and password for MySQL when you created the database.</p>
<pre class="brush: plain; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/guacamole/guacamole.properties
# Hostname and port of guacamole proxy
guacd-hostname: localhost
guacd-port:     4822
# MySQL properties
mysql-hostname: localhost
mysql-port: 3306
mysql-database: guacamole_db
mysql-username: guacamole_user
mysql-password: guacamole_password
mysql-default-max-connections-per-user: 0
mysql-default-max-group-connections-per-user: 0
EOF
</pre>
<h1>Start the application</h1>
<p>Now it&#8217;s time to start Guacamole.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start guacd
systemctl start tomcat
</pre>
<p>By default, Tomcat apps use port 8080, so if you have a firewall, you have to open the port.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --reload
</pre>
<p>If you use SELinux, you have to allow the HTTP traffic.</p>
<pre class="brush: bash; title: ; notranslate">
setsebool -P httpd_can_network_connect 1
</pre>
<p>At this point you can go and test Guacamole by going to http://guacamole_ip:8080/guacamole. The default admin user and password is <strong>guacadmin</strong>.<br />
<strong>NOTE:</strong> I strongly suggest that you change the password or even create a new admin user.<br />
When I created a test connection, everything worked fine, but I was stuck at the remote session and couldn&#8217;t go back to Guacamole. Every time I went to http://guacamole_ip:8080/guacamole, the browser opened the session. If this happens to you, make sure you have at least two sessions configured or simply go to http://guacamole_ip:8080/guacamole/#settings/sessions URL.</p>
<h1>nginx as reverse proxy</h1>
<p>Using nginx as reverse proxy is optional, but initially I said that most corporate environments block everything except port 80 and 443. Guacamole runs on port 8080 which means it will be blocked as well. You can probably change the port from 8080 to 80, but then you have to reconfigure Tomcat and there might be some issues because everything running on ports 1024 and below requires root privileges. So, I&#8217;ll do what everyone else does, use a reverse proxy.<br />
The purpose of nginx will be to listen on port 80 or port 443 and forward that traffic locally to Tomcat on 8080.<br />
Let&#8217;s install and configure nginx.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install nginx
systemctl enable nginx
systemctl start nginx
</pre>
<p>Open <strong>/etc/nginx/nginx.conf</strong> and remove the section with the server directive. So, this section has to go. Comment it or delete it, then save the file.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P119-01.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P119-01.jpg" alt="" width="639" height="456" class="aligncenter size-full wp-image-8307" /></a><br />
Now, create a new nginx config file.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/nginx/conf.d/guacamole.conf
server {
    listen 80;
    server_name localhost;
        location /guacamole/ {
                proxy_pass http://localhost:8080/guacamole/;
                proxy_buffering off;
                proxy_http_version 1.1;
                proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
                proxy_set_header Upgrade \$http_upgrade;
                proxy_set_header Connection \$http_connection;
                access_log off;
        }
}
EOF
</pre>
<p>Reload the nginx config and you can access your server as <em>http://guacamole_ip/guacamole</em> now.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart nginx
</pre>
<p>Make sure you open the firewall if you have it installed.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload
</pre>
<p>If you want to go even further and use a specific URL and SSL certificates, then do this. Let&#8217;s say you want to access your Guacamole install using <em>https://guac.domain.com</em>. In that case, your guacamole.conf should look like this. </p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/nginx/conf.d/guacamole.conf
server {
    listen 80;
    server_name guac.domain.com;
        return 301 https://\$host\$request_uri;
}
server {
    listen 443 ssl;
    server_name guac.domain.com;
    ssl_certificate star.domain.com.crt;
    ssl_certificate_key star.domain.com.key;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    access_log off;
    location / {
        proxy_pass http://localhost:8080/guacamole/;
        proxy_buffering off;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection \$http_connection;
    }
}
EOF
</pre>
<p>Put the certificates under <strong>/etc/nginx</strong> directory, allow the port 443 on the firewall and restart nginx.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --reload
systemctl restart nginx
</pre>
<p>For this to work, you&#8217;ll need guac.domain.com to point to your Guacamole server IP in DNS and you&#8217;ll need the SSL certificate and private key under <strong>/etc/nginx</strong> directory.<br />
Finally, when everything looks OK, get rid of the workdir.</p>
<pre class="brush: bash; title: ; notranslate">
rm -Rf /workdir
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2018/12/142-centos-apache-guacamole-remote-desktop-gateway/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
	</channel>
</rss>
