<?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/category/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>Alibaba Cloud, CentOS: Build WordPress site using NFS, RDS and Load Balancers</title>
		<link>https://blog.andreev.it/2019/04/alibaba-cloud-centos-build-wordpress-site-using-nfs-rds-and-load-balancers/</link>
					<comments>https://blog.andreev.it/2019/04/alibaba-cloud-centos-build-wordpress-site-using-nfs-rds-and-load-balancers/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 15 Apr 2019 16:50:40 +0000</pubDate>
				<category><![CDATA[Alibaba]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Alibaba cloud]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5202</guid>

					<description><![CDATA[I got an invitation to attend an event in NYC for the Alibaba cloud,&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>I got an invitation to attend an event in NYC for the Alibaba cloud, so I decided to play a little bit with their cloud offering. I was actually surprised to see that they are doing a really good job. While many features are still not there, the ease and simplicity of Alibaba cloud is a plus. If you are familiar with AWS, it won&#8217;t take you more than 5 minutes to feel comfortable. The interface, although different is very intuitive. You can get a free trial (a credit card and phone verification are needed) and start playing. Visit this <a href="https://us.alibabacloud.com/" rel="noopener noreferrer" target="_blank">link </a>to start. Once you open an account and verify it, you&#8217;ll be presented with a console.<br />
I&#8217;ve decided to demonstrate how to start in Alibaba Cloud by building a WordPress site running on two instances behind a load balancer. The database will be a MySQL RDS offering from Alibaba. They still don&#8217;t offer NFS filesystem, so I had to create an instance for that. First thing first.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-01.png" alt="" width="1792" height="1063" class="aligncenter size-full wp-image-8482" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-01.png 1792w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-01-300x178.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-01-1024x607.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-01-768x456.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-01-1536x911.png 1536w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-01-1170x694.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-01-585x347.png 585w" sizes="(max-width: 1792px) 100vw, 1792px" /></a></p>
<h1>VPC</h1>
<p>The ubiquitous VPCs from AWS are named the same in Alibaba and they serve the same purpose. From the top menu choose a region, from the left click on <strong>Virtual Private Cloud</strong> and then click on <strong>Create VPC</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-02.png" alt="" width="451" height="282" class="aligncenter size-full wp-image-8483" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-02.png 451w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-02-300x188.png 300w" sizes="(max-width: 451px) 100vw, 451px" /></a><br />
I&#8217;ll name my VPC, <strong>TestVPC </strong>and the CIDR (size of the VPC in terms of available IPs and subnets) is going to be 192.168.0.0/16 (which is default).<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-03.png" alt="" width="565" height="432" class="aligncenter size-full wp-image-8484" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-03.png 565w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-03-300x229.png 300w" sizes="(max-width: 565px) 100vw, 565px" /></a><br />
If you scroll down, you&#8217;ll see that you&#8217;ll also have to create a <strong>VSwitch</strong>. A <strong>VSwitch </strong>is nothing by a subnet or availability zone (AZ) in AWS terms. My first one will be called VSwitchA in Virginia Zone A and I&#8217;ll carve a smaller subnet 192.168.1.0/24 for that.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-04.png" alt="" width="504" height="469" class="aligncenter size-full wp-image-8485" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-04.png 504w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-04-300x279.png 300w" sizes="(max-width: 504px) 100vw, 504px" /></a><br />
Let&#8217;s create another one, but this time in a different zone. The subnets can&#8217;t overlap. On the left side, you&#8217;ll see the menu for <strong>VSwitches</strong>, so click there and then click on<strong> Create VSwitch</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-05.png" alt="" width="493" height="321" class="aligncenter size-full wp-image-8486" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-05.png 493w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-05-300x195.png 300w" sizes="(max-width: 493px) 100vw, 493px" /></a><br />
Do pretty much the same as previously. Choose the right VPC this time, name the VSwitch, choose the second zone and choose a different subnet. In my case it&#8217;s 192.168.2.0/24.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-06.png" alt="" width="596" height="540" class="aligncenter size-full wp-image-8487" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-06.png 596w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-06-300x272.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-06-585x530.png 585w" sizes="(max-width: 596px) 100vw, 596px" /></a></p>
<h1>Security Groups</h1>
<p>We&#8217;ll need several <strong>Security Groups</strong>. From the top left menu, click on <strong>Security Groups</strong> and the click on <strong>Create Security Group</strong> from the top right.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-07.png" alt="" width="256" height="368" class="aligncenter size-full wp-image-8488" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-07.png 256w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-07-209x300.png 209w" sizes="(max-width: 256px) 100vw, 256px" /></a><br />
For the Load Balancer, we&#8217;ll need a security group that will allow port 80 from everywhere (0.0.0.0/0). For the two instances that will run Apache and PHP, we&#8217;ll allow port 22 only from specific addresses and port 80 allowed from the load balancer only. We don&#8217;t want the actual instances to be exposed on port 80 on the Internet. For the RDS instance, we&#8217;ll create a security group that allows port 3306 (MySQL) from the two instances only. Finally, for the NFS server, we&#8217;ll also allow access from the two instances only on port 2049 (TCP).<br />
Here is an example of what I did for the first security group. I choose a <strong>custom </strong>template, named it <strong>sgLoadBalancer</strong>, added a description and chose my <strong>TestVPC</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-08.png" alt="" width="691" height="454" class="aligncenter size-full wp-image-8489" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-08.png 691w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-08-300x197.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-08-585x384.png 585w" sizes="(max-width: 691px) 100vw, 691px" /></a><br />
You can&#8217;t create the rules like you do in AWS, but once you create the security group, you&#8217;ll see a pop-up reminding you to create the rules. Click on <strong>Create Rules Now</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-09.png" alt="" width="593" height="223" class="aligncenter size-full wp-image-8490" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-09.png 593w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-09-300x113.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-09-585x220.png 585w" sizes="(max-width: 593px) 100vw, 593px" /></a><br />
This is how my rule for HTTP traffic looks like.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-10.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-10.png" alt="" width="513" height="479" class="aligncenter size-full wp-image-8491" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-10.png 513w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-10-300x280.png 300w" sizes="(max-width: 513px) 100vw, 513px" /></a><br />
Name the second security group <strong>sgInstances</strong>, but before you create the group copy the security group ID for the first group that you created.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-11.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-11.png" alt="" width="852" height="93" class="aligncenter size-full wp-image-8493" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-11.png 852w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-11-300x33.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-11-768x84.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-11-585x64.png 585w" sizes="(max-width: 852px) 100vw, 852px" /></a><br />
When you add the rules for the 2nd security group for the instances, make sure that you change the <strong>Authorization Type</strong> parameter to <strong>Security Group</strong> instead of <strong>IPv4 CID Block</strong>. This way you can specify that another security group has access to a specific port, not an IP. We don&#8217;t know the IP of the load balancer and it probably changes from time to time.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-12.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-12.png" alt="" width="473" height="432" class="aligncenter size-full wp-image-8494" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-12.png 473w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-12-300x274.png 300w" sizes="(max-width: 473px) 100vw, 473px" /></a><br />
You have to create another rule for the instances. This time for the port 22. I chose to restrict port 22 to my home IP only. This is how those rules actually look like once created.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-13.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-13.png" alt="" width="777" height="245" class="aligncenter size-full wp-image-8495" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-13.png 777w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-13-300x95.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-13-768x242.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-13-585x184.png 585w" sizes="(max-width: 777px) 100vw, 777px" /></a><br />
For the next two security group, copy the security group ID of the newly created group <strong>sgInstances</strong>.<br />
Create two new security groups, <strong>sgRDS </strong>and <strong>sgNFS</strong>. For <strong>sgRDS </strong>allow port <strong>3306 </strong>from the security group ID of <strong>sgInstances </strong>and for <strong>sgNFS </strong>allow port <strong>2049 </strong>for the same security group ID of <strong>sgInstances</strong>.<br />
At the end you should have 4 security groups: sgLoadBalancer, sgInstances, sgRDS and sgNFS. These are the rules.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-14.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-14.png" alt="" width="385" height="131" class="aligncenter size-full wp-image-8496" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-14.png 385w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-14-300x102.png 300w" sizes="(max-width: 385px) 100vw, 385px" /></a><br />
And these security groups should be assigned to these resources.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-15.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-15.png" alt="" width="314" height="106" class="aligncenter size-full wp-image-8497" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-15.png 314w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-15-300x101.png 300w" sizes="(max-width: 314px) 100vw, 314px" /></a></p>
<h1>SSH Keys</h1>
<p>Before we start provisioning the servers, let&#8217;s create a SSH key pair so we can access them. The menu for the creation of SSH keys is bellow <strong>Security Groups</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-16.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-16.png" alt="" width="694" height="269" class="aligncenter size-full wp-image-8498" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-16.png 694w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-16-300x116.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-16-585x227.png 585w" sizes="(max-width: 694px) 100vw, 694px" /></a><br />
Click on <strong>Create SSH Key Pair</strong> in the top right corner. You can import or create your own key pair. I chose to create the key pair.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-17.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-17.png" alt="" width="799" height="322" class="aligncenter size-full wp-image-8499" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-17.png 799w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-17-300x121.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-17-768x310.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-17-585x236.png 585w" sizes="(max-width: 799px) 100vw, 799px" /></a><br />
As a result you&#8217;ll get a certificate file (.pem). Keep this file somewhere safe. If you use putty to connect to your instances, you&#8217;ll have to <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/putty.html#putty-private-key" rel="noopener noreferrer" target="_blank">convert </a>this file to ppk using puttygen.  </p>
<h1>NFS instance</h1>
<p>From the menu, go to <strong>Instances </strong>and then from the top right, click <strong>Create Instance</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-18.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-18.png" alt="" width="730" height="281" class="aligncenter size-full wp-image-8500" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-18.png 730w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-18-300x115.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-18-585x225.png 585w" sizes="(max-width: 730px) 100vw, 730px" /></a><br />
I am going with <strong>Pay-As-You-Go</strong> model and <strong>Random </strong>zone. Choose your instance type. The cheapest are under <strong>Entry-Level (Shared)</strong>. I went with 1 CPU and 1GB RAM and 20GB disk on CentOS 7.6 x-64. Looks like 20GB is minimum, I couldn&#8217;t choose anything below 20GB for the disk. Click <strong>Next: Networking</strong> when you are done here.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-19.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-19.png" alt="" width="1366" height="1612" class="aligncenter size-full wp-image-8501" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-19.png 1366w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-19-254x300.png 254w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-19-868x1024.png 868w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-19-768x906.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-19-1302x1536.png 1302w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-19-1170x1381.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-19-585x690.png 585w" sizes="(max-width: 1366px) 100vw, 1366px" /></a><br />
On the Networking screen, I have these settings. The defaults for the VPC and the zone, click to <strong>Assign public IP</strong> and click to select a security group. For some reason, I was able to select only one security group, so select <strong>sgInstances </strong>as a security group.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-20.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-20.png" alt="" width="1366" height="1012" class="aligncenter size-full wp-image-8502" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-20.png 1366w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-20-300x222.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-20-1024x759.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-20-768x569.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-20-1170x867.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-20-585x433.png 585w" sizes="(max-width: 1366px) 100vw, 1366px" /></a><br />
If you click next, you&#8217;ll see the <strong>System Configuration </strong>page. Specify the key that you&#8217;ve selected, name the instance so it shows up properly in the console, add a description if you want and enter the hostname (optional). From here, you can accept the default and next-through all the way to the end. Alibaba will create the instance for you in 1-3 minutes.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-21.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-21.png" alt="" width="1366" height="893" class="aligncenter size-full wp-image-8504" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-21.png 1366w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-21-300x196.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-21-1024x669.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-21-768x502.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-21-1170x765.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-21-585x382.png 585w" sizes="(max-width: 1366px) 100vw, 1366px" /></a><br />
After the instance is create, go back to the <strong>Security Groups</strong> menu and find the <strong>sgNFS </strong>group. Click on <strong>Manage Instances</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-22.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-22.png" alt="" width="1088" height="114" class="aligncenter size-full wp-image-8505" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-22.png 1088w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-22-300x31.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-22-1024x107.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-22-768x80.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-22-585x61.png 585w" sizes="(max-width: 1088px) 100vw, 1088px" /></a><br />
From the top right, click on <strong>Add Instance</strong> and add the <strong>nfs </strong>instance. This way, we&#8217;ll have both <strong>sgInstances </strong>and <strong>sgNFS </strong>securty groups attached to the <strong>nfs </strong>server.<br />
Go back to your instances and you&#8217;ll see the instance is up and running. SSH to its public IP and specify the SSH key to log in. Your username is <strong>root</strong> and the ppk file is your password.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-23.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-23.png" alt="" width="1102" height="112" class="aligncenter size-full wp-image-8506" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-23.png 1102w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-23-300x30.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-23-1024x104.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-23-768x78.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-23-585x59.png 585w" sizes="(max-width: 1102px) 100vw, 1102px" /></a><br />
Once you are in, you&#8217;ll be greeted with the Alibaba banner.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-24.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-24.png" alt="" width="418" height="170" class="aligncenter size-full wp-image-8507" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-24.png 418w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-24-300x122.png 300w" sizes="(max-width: 418px) 100vw, 418px" /></a><br />
Now you are ready to install the NFS filesystem that will be used as a shared disk for both WordPress instances. I already have a small tutorial for this, so go to this <a href="https://blog.andreev.it/?p=4180" rel="noopener noreferrer" target="_blank">link </a>and do the server part. You can skip the part for the firewall (last two commands). The firewall is not enabled by default on Alibaba CentOS. SELinux is also disabled. Once you are done, we have to download WordPress and extract it under the <strong>nfs </strong>share. Then, we we build the webservers we&#8217;ll mount this directory and point our webserver there.</p>
<pre class="brush: bash; title: ; notranslate">
cd /nfs
wget https://www.wordpress.org/latest.tar.gz
tar xzvf latest.tar.gz --strip 1
</pre>
<h1>RDS</h1>
<p>We&#8217;ll use the PaaS offering from Alibaba for our MySQL database. From the menu on the left, click <strong>AsparaDB for RDS</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-25.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-25.png" alt="" width="316" height="208" class="aligncenter size-full wp-image-8508" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-25.png 316w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-25-300x197.png 300w" sizes="(max-width: 316px) 100vw, 316px" /></a><br />
Click <strong>Create Instance</strong> in the top right corner. This is what I chose for my database. Make sure <strong>Pay-As-You-Go</strong> is selected and then choose the RDS instance in the same region as your other instances. The engine is MySQL 5.7 with no HA. I chose a small 1 CPU/1GB DB instance with 20GB storage.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-26.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-26.png" alt="" width="1299" height="1325" class="aligncenter size-full wp-image-8509" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-26.png 1299w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-26-294x300.png 294w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-26-1004x1024.png 1004w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-26-768x783.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-26-1170x1193.png 1170w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-26-585x597.png 585w" sizes="(max-width: 1299px) 100vw, 1299px" /></a><br />
Click <strong>Buy Now</strong> and then <strong>Pay Now</strong> when you are done. If you go to the console, you won&#8217;t see your database. Just wait for a minute or two and it will show up with a status of <strong>Creating</strong>. After 3-5 mins, once the status changes to <strong>Running</strong>, click on the database. Let&#8217;s assign a security group first. Click on <strong>Basic Infomation</strong> on the left and then click on <strong>Set Whitelist</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-27.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-27.png" alt="" width="581" height="306" class="aligncenter size-full wp-image-8510" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-27.png 581w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-27-300x158.png 300w" sizes="(max-width: 581px) 100vw, 581px" /></a><br />
Look at the bottom right, where it says <strong>Add to Security Group</strong>. The names won&#8217;t show up, so get your security group ID for <strong>sgRDS </strong>from the <strong>Security Groups</strong> menu first. Unlike AWS, you can assign only one security group per DB instance. If you get an error that this operation is not supported, do not assign a security group. Just whitelist the VPC subnet. Click <strong>Add a Whitelist Group</strong> and add the VPC subnet 192.168.0.0/16.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-28.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-28.png" alt="" width="595" height="490" class="aligncenter size-full wp-image-8511" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-28.png 595w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-28-300x247.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-28-585x482.png 585w" sizes="(max-width: 595px) 100vw, 595px" /></a><br />
Now, go to the <strong>Accounts </strong>menu on the left and click <strong>Create Account</strong> on the right. I&#8217;ve created a superuser called <strong>dbmaster</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-29.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-29.png" alt="" width="1119" height="916" class="aligncenter size-full wp-image-8512" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-29.png 1119w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-29-300x246.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-29-1024x838.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-29-768x629.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-29-585x479.png 585w" sizes="(max-width: 1119px) 100vw, 1119px" /></a><br />
Again, go to <strong>Databases </strong>menu on the left and click <strong>Create Database</strong> on the right. Type the name of the database <strong>dbwordpress </strong>and click to create a new account. You&#8217;ll be back at the accounts menu. This time create a user called <strong>wordpress </strong>and make it a standard user. It&#8217;s kind of kludgy, but now you have to go back to the <strong>Databases </strong>menu, click <strong>Create Database</strong> again, fill the name <strong>dbwordpress </strong>again, but this time you can choose the account that you just created.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-30.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-30.png" alt="" width="697" height="496" class="aligncenter size-full wp-image-8513" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-30.png 697w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-30-300x213.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-30-585x416.png 585w" sizes="(max-width: 697px) 100vw, 697px" /></a><br />
Finally, go to the <strong>Basic Information</strong> menu and choose the menu next to Intranet address. Here you have to create a whitelist group of IPs that can access the database. By default, only 127.0.0.1 is allowed. We have to enter our VPC CIDR.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-31.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-31.png" alt="" width="509" height="193" class="aligncenter size-full wp-image-8514" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-31.png 509w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-31-300x114.png 300w" sizes="(max-width: 509px) 100vw, 509px" /></a><br />
Once you do that, you&#8217;ll see your RDS hostname.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-32.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-32.png" alt="" width="662" height="334" class="aligncenter size-full wp-image-8515" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-32.png 662w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-32-300x151.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-32-585x295.png 585w" sizes="(max-width: 662px) 100vw, 662px" /></a></p>
<h1>WordPress instances</h1>
<p>We&#8217;ll create two instances, install Apache + PHP, mount the NFS system and configure the web server. First, create two instances, each in a different zone. Use the section above for the NFS server. Name the servers <strong>web1 </strong>and <strong>web2 </strong>and assign the <strong>sgInstances </strong>security group to both of them. Make sure you choose <strong>Pay-As-You-Go</strong> model and assign public IPs so you can log in. And choose the SSH key pair that we created. Log to both of the instances.<br />
First, mount the NFS system. Replace the IP with the IP of your NFS server in the <strong>mount </strong>command. </p>
<pre class="brush: bash; title: ; notranslate">
yum -y install nfs-utils
mount 192.168.1.91:/nfs /var/www/html
echo &quot;192.168.1.91:/nfs       /var/www/html    nfs     defaults 0 0&quot; &gt;&gt; /etc/fstab
chown -R apache:apache /var/www/html
</pre>
<p>Then, check if you can reach the RDS MySQL server. You should receive some garbage.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
yum -y install telnet
telnet rm-2evrh4h3aq9tk800z.mysql.rds.aliyuncs.com 3306
Trying 172.16.1.41...
Connected to rm-2evrh4h3aq9tk800z.mysql.rds.aliyuncs.com.
Escape character is '^]'.
N
5.7.20-log▒F5;I!]:p]H3R&#x5B;^mysql_native_password
</pre>
<p>Second, install Apache and all PHP modules on both servers. I&#8217;ll install PHP 5.4 because it&#8217;s already in the default repo. You should install PHP 7.x, it&#8217;s a little bit more work.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install httpd php php-common php-mysql php-gd php-xml php-mbstring php-mcrypt
systemctl enable httpd
systemctl start httpd
</pre>
<p>Now, we have to configure the Apache server. It&#8217;s a simple change in <strong>/etc/httpd/conf/httpd.conf</strong> file. You&#8217;ll probably want to use virtual hosts, but in interest of keeping this short, I&#8217;ll use a single site config. Find the directive that says #ServerName www.example.com:80 and remove the comment.</p>
<pre class="brush: bash; title: ; notranslate">
sed -i '/ServerName www/s/^#//g' /etc/httpd/conf/httpd.conf
</pre>
<h1>Load Balancer</h1>
<p>From the menu on the left, click on <strong>Server Load Balancer</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-33.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-33.png" alt="" width="232" height="337" class="aligncenter size-full wp-image-8516" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-33.png 232w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-33-207x300.png 207w" sizes="(max-width: 232px) 100vw, 232px" /></a><br />
Once the screen shows up, click on <strong>Create SLB Instance</strong>. Then choose the specs. Make sure it is in the same region with your instances and in the same zones (VSWitches). Name the load balancer too, e.g. <strong>lbWordPress</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-34.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-34.png" alt="" width="868" height="731" class="aligncenter size-full wp-image-8517" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-34.png 868w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-34-300x253.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-34-768x647.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-34-585x493.png 585w" sizes="(max-width: 868px) 100vw, 868px" /></a><br />
Once it&#8217;s ready, click on <strong>Add Backend Servers</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-35.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-35.png" alt="" width="1005" height="102" class="aligncenter size-full wp-image-8518" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-35.png 1005w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-35-300x30.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-35-768x78.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-35-585x59.png 585w" sizes="(max-width: 1005px) 100vw, 1005px" /></a><br />
From the list of the available servers, choose web1 and web2 in the <strong>Default Server Group</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-36.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-36.png" alt="" width="664" height="291" class="aligncenter size-full wp-image-8519" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-36.png 664w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-36-300x131.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-36-585x256.png 585w" sizes="(max-width: 664px) 100vw, 664px" /></a><br />
Click on <strong>Next: Set Weight and Port</strong> button and click <strong>OK</strong> to accept the defaults. Go back to the load balancer and this time click <strong>Configure Listener</strong> (above <strong>Add Backend Servers</strong>).<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-37.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-37.png" alt="" width="1063" height="134" class="aligncenter size-full wp-image-8520" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-37.png 1063w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-37-300x38.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-37-1024x129.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-37-768x97.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-37-585x74.png 585w" sizes="(max-width: 1063px) 100vw, 1063px" /></a><br />
Choose HTTP traffic, port 80, click <strong>Modify </strong>next to <strong>Advanced </strong>and <strong>Enable Session Persistence</strong>. Click <strong>Next</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-38.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-38.png" alt="" width="640" height="511" class="aligncenter size-full wp-image-8521" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-38.png 640w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-38-300x240.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-38-585x467.png 585w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-39.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-39.png" alt="" width="394" height="208" class="aligncenter size-full wp-image-8522" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-39.png 394w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-39-300x158.png 300w" sizes="(max-width: 394px) 100vw, 394px" /></a><br />
On this screen click on <strong>Default Server Group</strong>, type port <strong>80 </strong>for the servers and click <strong>Next</strong>. You can accept the defaults in the next screen or make a change if you like.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-40.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-40.png" alt="" width="1010" height="499" class="aligncenter size-full wp-image-8523" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-40.png 1010w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-40-300x148.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-40-768x379.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-40-585x289.png 585w" sizes="(max-width: 1010px) 100vw, 1010px" /></a><br />
Open a browser and go to your load balancer IP. It might take up a minute for the LB to configure so be patient if your browser is just sitting there.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-41.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-41.png" alt="" width="397" height="126" class="aligncenter size-full wp-image-8524" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-41.png 397w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-41-300x95.png 300w" sizes="(max-width: 397px) 100vw, 397px" /></a><br />
If you used my username and database names, fill out the config page like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/04/P130-42.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/04/P130-42.png" alt="" width="776" height="567" class="aligncenter size-full wp-image-8525" srcset="https://blog.andreev.it/wp-content/uploads/2019/04/P130-42.png 776w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-42-300x219.png 300w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-42-768x561.png 768w, https://blog.andreev.it/wp-content/uploads/2019/04/P130-42-585x427.png 585w" sizes="(max-width: 776px) 100vw, 776px" /></a><br />
&#8230;and pretty much that&#8217;s it.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/04/alibaba-cloud-centos-build-wordpress-site-using-nfs-rds-and-load-balancers/feed/</wfw:commentRss>
			<slash:comments>0</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>
		<item>
		<title>CentOS: Install an authoritative DNS server (BIND)</title>
		<link>https://blog.andreev.it/2018/12/141-centos-install-an-authoritative-dns-server-bind/</link>
					<comments>https://blog.andreev.it/2018/12/141-centos-install-an-authoritative-dns-server-bind/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Wed, 19 Dec 2018 20:50:09 +0000</pubDate>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[BIND]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[DNS]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=4219</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to install and configure BIND DNS server to&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to install and configure BIND DNS server to act as an authoritative server for a public domain in a master/slave configuration. This is not a setup for a server that will act as a DNS server in your local environment and does DNS resolution for your local network. In this post, the DNS server won&#8217;t resolve any DNS queries except for the domain that it is authoritative. The domain that I&#8217;ll use is <strong>cloudwerk.us</strong> registered with Go Daddy, so some of the configurations will apply to Go Daddy only. The IPs and keys that I&#8217;ll use will be visible. The setup will be gone by the time you read this.<br />
So, I have my master server, <strong>ns1.cloudwerk.us (67.205.160.87)</strong> and the slave server <strong>ns2.cloudwerk.us (104.248.184.142)</strong>.<br />
I will be using CentOS 7.5 and BIND 9.9.4-72. Before you start, make sure you have your domain purchased and you have 2 servers with public IPs up and running. You can leave the default registrant&#8217;s DNS servers for now. </p>
<h1>Installation, startup and control</h1>
<p>First, I&#8217;ll install BIND on both servers. All commands below are executed as root.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install bind
</pre>
<p>Then, we have to configure the <strong>rndc (Remote Name Daemon Control) </strong>utility. This utility allows management of the <strong>named </strong>(DNS) daemon. Moreover, you can manage remote DNS servers and I&#8217;ll explain how. So, let&#8217;s create the keys (highlighted line is what you type, the rest is output). DO this on the master server only.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
rndc-confgen -a -r /dev/urandom 
wrote key file &quot;/etc/rndc.key&quot; 
</pre>
<p>As you can see, there is a file called <strong>rndc.key</strong> created under <strong>/etc</strong> directory. This is the default directory where BIND expects to find its config files. The zone files are under <strong>/var</strong>.<br />
Now, copy this file to the second server in the same location. You can use scp, copy &#038; paste in a terminal session, it doesn&#8217;t matter. What matters is that you have to change the permissions and the owner of the file. BIND runs under the <strong>named </strong>user, not root. Do this on both servers. </p>
<pre class="brush: bash; title: ; notranslate">
cd /etc
chown root:named rndc.key
chmod 640 rndc.key
</pre>
<p>Let&#8217;s make sure that BIND starts on boot. On both servers, execute:</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable named  
</pre>
<p>Now, we can start the BIND daemon on both. </p>
<pre class="brush: bash; title: ; notranslate">
systemctl start named 
</pre>
<p>Check the logs.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
tail /var/log/messages
Dec 19 16:33:40 master named&#x5B;1308]: zone 1.0.0.127.in-addr.arpa/IN: loaded serial 0
Dec 19 16:33:40 master named&#x5B;1308]: zone localhost.localdomain/IN: loaded serial 0
Dec 19 16:33:40 master named&#x5B;1308]: zone 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa/IN: loaded serial 0
Dec 19 16:33:40 master named&#x5B;1308]: all zones loaded
Dec 19 16:33:40 master named&#x5B;1308]: running
Dec 19 16:33:40 master systemd: Started Berkeley Internet Name Domain (DNS).
Dec 19 16:33:40 master systemd: Reached target Host and Network Name Lookups.
Dec 19 16:33:40 master systemd: Starting Host and Network Name Lookups.
Dec 19 16:33:40 master named&#x5B;1308]: network unreachable resolving './DNSKEY/IN': 2001:500:84::b#53
Dec 19 16:33:40 master named&#x5B;1308]: network unreachable resolving './NS/IN': 2001:500:84::b#53
</pre>
<p>We can&#8217;t start the server with the <strong>rndc </strong>utility, but we can stop it if needed with <strong>rndc stop</strong> or simply <strong>systemctl stop named</strong>.<br />
To control the slave server from the master, we have to modify the main BIND configuration file, <strong>named.conf</strong> on the slave server. Also, the keys have to be the same, but we already took care of that part above.<br />
So, in the same <strong>/etc</strong> directory, edit <strong>named.conf</strong> and add these lines, right before the <strong>options </strong>directive. Do this on the slave server, not the master.</p>
<pre class="brush: bash; title: ; notranslate">
include &quot;/etc/rndc.key&quot;;

controls {
        inet 127.0.0.1 allow { localhost; } keys { &quot;rndc-key&quot;; };
        inet 104.248.184.142 allow { 67.205.160.87; } keys { &quot;rndc-key&quot;; };
};
</pre>
<p>If you look at my IPs above, this means that the rndc utility will be accepted from the localhost and from the the IP 67.205.160.87 which is the master server. But, we also have to tell BIND to listen on the public IP. By default, it listens on the <strong>localhost </strong>only.<br />
Do this on BOTH servers. In <strong>named.conf</strong> find the <strong>options </strong>directive and modify the <strong>listen-on</strong> option so it looks like this. You should put the public IP of each server there, so the line will differ on both servers. </p>
<pre class="brush: bash; title: ; notranslate">
//Master server
listen-on       { 127.0.0.1; 67.205.160.87; };
</pre>
<pre class="brush: bash; title: ; notranslate">
//Slave server
listen-on       { 127.0.0.1; 104.248.184.142; };
</pre>
<p>Allow traffic on the firewalls if you have them enabled. Do this on both.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=53/tcp --permanent 
firewall-cmd --add-port=53/udp --permanent 
</pre>
<p>On the master node, do this.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --permanent --zone=public --add-rich-rule=' 
  rule family=&quot;ipv4&quot; 
  source address=&quot;104.248.184.142/32&quot; 
  port protocol=&quot;tcp&quot; port=&quot;953&quot; accept'
</pre>
<p>On the slave node, do this.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --permanent --zone=public --add-rich-rule=' 
  rule family=&quot;ipv4&quot; 
  source address=&quot;67.205.160.87/32&quot; 
  port protocol=&quot;tcp&quot; port=&quot;953&quot; accept'
</pre>
<p>Reload the rules on both.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --reload
</pre>
<p>Restart BIND on both.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart named
</pre>
<p>Now, you can manage them with rndc.</p>
<pre class="brush: bash; title: ; notranslate">
rndc reload
rndc status
</pre>
<p>From the master server, try to reload the slave server.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
rndc -s 104.248.184.142 reload
server reload successful
</pre>
<p>What we also want to do is tell the OS that from now on, the default DNS server will be our local DNS.<br />
So, edit <strong>/etc/resolv.conf</strong> and add this line as the first line on both. </p>
<pre class="brush: bash; title: ; notranslate">
nameserver 127.0.0.1
</pre>
<p>If we try to ping google.com we should get a response or at least an IP resolved. That&#8217;s good. From another computer, try to use any of our servers as a DNS. This is what you should get: Query refused. That&#8217;s good too, because we don&#8217;t want our server to be used as a DNS server for other domains. We want to access the Internet from the server, e.g. to update the server, so we need to be able to use the local server to resolve any domains but only from localhost. </p>
<pre class="brush: bash; highlight: [1,5,9]; title: ; notranslate">
C:\Users\klimenta&gt;nslookup
Default Server:  google-public-dns-a.google.com
Address:  8.8.8.8

&gt; server 67.205.160.87
Default Server:  &#x5B;67.205.160.87]
Address:  67.205.160.87

&gt; www.google.com
Server:  &#x5B;67.205.160.87]
Address:  67.205.160.87

*** &#x5B;67.205.160.87] can't find www.google.com: Query refused
</pre>
<p>You can allow this server to act as a forwarder by including <strong>recursion yes</strong>; option under <strong>options </strong>and <strong>allow-access</strong> to all IPs (0.0.0.0/0), but that&#8217;s not the intended use of an authoritative DNS server. </p>
<h1>Zone file</h1>
<p>Now that we have the servers up and running, let&#8217;s create the zone file. The zone file tells where the records can be found. On the master server only, edit <strong>named.conf</strong> and add these two lines right after the <strong>listen-on</strong> option. We are allowing the transfer of the zones to the slave server only</p>
<pre class="brush: bash; title: ; notranslate">
allow-transfer { localhost; 104.248.184.142; };
notify yes;
</pre>
<p>Go all the way at the bottom and add these line to define the zone file.<br />
<strong>NOTE:</strong> If you have SELinux enabled, you can&#8217;t define your zone anywhere. Make sure you use <strong>/var/named</strong> for your zone file.).</p>
<pre class="brush: bash; title: ; notranslate">
zone &quot;cloudwerk.us&quot; {
        type master;
        file &quot;/var/named/db.cloudwerk.us&quot;;
};
</pre>
<p>Now, go to the <strong>/var/named </strong>directory and create the zone file to look like this. The file should be called <strong>db.cloudwerk.us</strong>. Do this on the master server only. You might want to read the following <a href="https://help.dyn.com/how-to-format-a-zone-file" rel="noopener noreferrer" target="_blank">link </a>if you are not familiar with the syntax of a zone file. </p>
<pre class="brush: bash; title: ; notranslate">
$TTL    604800
@       IN      SOA     ns1.cloudwerk.us.       admin.cloudwerk.us. (
                        2018121901;     Serial
                        3H;             Refresh
                        15M;            Retry
                        2W;             Expiry
                        1D );           Minimum

; name servers - NS records
        IN      NS      ns1.cloudwerk.us.
        IN      NS      ns2.cloudwerk.us.

; name servers - A records
ns1.cloudwerk.us.       IN      A       67.205.160.87
ns2.cloudwerk.us.       IN      A       104.248.184.142

; other servers - A records
www.cloudwerk.us.       IN      A       52.71.63.84
</pre>
<p>All you have to do now is to reload the new config with <strong>rndc reload</strong>. At this point we have the master server up and running. Now, we have to configure the slave server. Edit the <strong>named.conf</strong> file and add these lines at the bottom. Very self-explanatory. </p>
<pre class="brush: bash; title: ; notranslate">
zone &quot;cloudwerk.us&quot; {
        type slave;
        masters { 67.205.160.87; };
        file &quot;/var/named/slaves/db.cloudwerk.us&quot;;
};
</pre>
<p>Restart the <strong>named </strong>daemon and if you go to <strong>/var/named/slaves</strong> directory, you&#8217;ll see that the zone file replicated from the master. Finally, on both servers you have to allow the queries from the Internet. Edit <strong>named.con</strong>f and comment this line.</p>
<pre class="brush: bash; title: ; notranslate">
//allow-query     { localhost; }; 
</pre>
<p>Reload with <strong>rndc reload</strong> on both after.<br />
At this point, we have both the master and the slave servers configured, up and running. But no one on the Internet knows that our servers are authoritative servers. You have to make this change at your domain registrant. In my case, I&#8217;ve purchased the domain from Go Daddy, so I went to the DNS management panel and added two A records for ns1.cloudwerk.us and ns2.cloudwerk.us. Then, at the bottom of the web control panel, I&#8217;ve changed the DNS from xy.domaincontrol.com to custom and added my servers. I&#8217;ve encountered some issues, but the on-line chat with the support resolved this in 5 minutes. In essence, if you open up the DNS console, scroll all the way down and under <strong>Advanced Features</strong>, you&#8217;ll see <strong>Host names</strong> link. Click on this one and create two records, ns1 and ns2 with your IPs. Now, you can switch to <strong>Custom Nameserves</strong> on the same page.<br />
Leave some time for replication. It might take up to 24 hrs, but in my case it took less than 20 mins.<br />
You should know that you are in charge when these commands return your servers. In Windows:</p>
<pre class="brush: bash; highlight: [1,5,6,19,20]; title: ; notranslate">
C:\Users\klimenta&gt;nslookup
Default Server:  google-public-dns-a.google.com
Address:  8.8.8.8

&gt; set query=soa
&gt; cloudwerk.us
Server:  google-public-dns-a.google.com
Address:  8.8.8.8

Non-authoritative answer:
cloudwerk.us
        primary name server = ns1.cloudwerk.us
        responsible mail addr = admin.cloudwerk.us
        serial  = 2018121901
        refresh = 10800 (3 hours)
        retry   = 900 (15 mins)
        expire  = 1209600 (14 days)
        default TTL = 86400 (1 day)
&gt; set query=ns
&gt; cloudwerk.us
Server:  google-public-dns-a.google.com
Address:  8.8.8.8

Non-authoritative answer:
cloudwerk.us    nameserver = ns1.cloudwerk.us
cloudwerk.us    nameserver = ns2.cloudwerk.us
</pre>
<p>In Linux/BSD if you use <strong>dig</strong>:</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
dig +short NS cloudwerk.us
ns2.cloudwerk.us.
ns1.cloudwerk.us.
</pre>
<p>Or, if you want to do it <a href="https://mxtoolbox.com/dnscheck.aspx" rel="noopener noreferrer" target="_blank">on-line</a>.<br />
This is what I got.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-01.png" alt="" width="1342" height="412" class="aligncenter size-full wp-image-8296" /></a><br />
It says that the slave server is leaking the zone which means anyone can list all the records. In order to prevent that, we have to edit <strong>named.conf</strong> on the slave server and add this line after <strong>listen-on </strong>option.</p>
<pre class="brush: bash; title: ; notranslate">
allow-transfer { localhost; 67.205.160.87; };
</pre>
<p>Do <strong>rndc reload</strong> after and if you re-execute the test, you&#8217;ll see that everything looks OK.</p>
<h1>DNSSEC (optional)</h1>
<p>I won&#8217;t go into details of what is DNSSEC, I&#8217;ll just explain how to configure and secure your DNS server. It&#8217;s pretty much a PKI infrastructure for securing your records by signing them with a private key. If you need more info, you can find tons of info by googling DNSSEC.<br />
On the master server, add this line after <strong>dnssec-validation yes;</strong> option in <strong>named.conf</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
key-directory &quot;/var/named/keys&quot;;
</pre>
<p>On the slave server don&#8217;t do anything.<br />
Add these lines in the same file (named.conf) on the master server  only. They should be below <strong>file </strong>directive where your zone is defined (at the bottom of the file). These lines are part of your zone config, not separate directives.</p>
<pre class="brush: bash; title: ; notranslate">
auto-dnssec maintain;
inline-signing yes;
</pre>
<p>Let&#8217;s create the KSK and ZSK keys. Master server only.</p>
<pre class="brush: bash; title: ; notranslate">
cd /var/named/keys
dnssec-keygen -r /dev/urandom -a RSASHA256 -b 2048 -f KSK -n ZONE cloudwerk.us
ls -l
echo &quot;Take a note of the key file that ends with .key&quot;
dnssec-keygen -r /dev/urandom -a RSASHA256 -b 2048 -n ZONE cloudwerk.us
</pre>
<p>You&#8217;ll see 4 files created. Scribble down the first key file that was created (.key), see the output from above. Reload the config on the master but make sure BIND can access the keys.</p>
<pre class="brush: bash; title: ; notranslate">
chown -R named:named /var/named/keys
rndc reload
</pre>
<p>You&#8217;ll see two more files (.jbk and .signed) under <strong>/var/named</strong>. These are the signed files. If you check the <strong>slaves </strong>directory on the slave node, you&#8217;ll see a signed file there as well.<br />
Let&#8217;s do a test. Go to this <a href="https://dnssec-analyzer.verisignlabs.com/" rel="noopener noreferrer" target="_blank">link</a>, type your domain name and hit enter.<br />
You should see something like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-02.png" alt="" width="717" height="763" class="aligncenter size-full wp-image-8297" /></a><br />
Now, stop the master server with <strong>rndc stop</strong> and refresh the web page. The test will take a little bit longer, because NS1 is down, but eventually you&#8217;ll get something like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-03.png" alt="" width="796" height="782" class="aligncenter size-full wp-image-8298" /></a><br />
That&#8217;s normal. It tells us that ns1 is down. What&#8217;s not normal is the red X mark that says <strong>&#8220;No DS records found for coudwerk.us in the us zone&#8221;</strong>. This translates as, yes you signed your zone, but no one can validate it. It&#8217;s like a fake certificate. So, we need to tell our registrant that we really own the domain and that we signed it.<br />
This portion is also registrant&#8217;s  dependent. I&#8217;ll describe how to overcome this error with Go Daddy.<br />
So, go to the <strong>keys </strong>directory and execute this command against the first file, that one that you took note about. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
cd /var/named/keys
dnssec-dsfromkey Kcloudwerk.us.+008+23956.key
cloudwerk.us. IN DS 23956 8 1 2DA94FECA549E0A2B137FA6C64D09D2CBADF8864
cloudwerk.us. IN DS 23956 8 2 8CC2CB79E1B8A6767F11230485C7499D52162135E1F6DAA503ADEC4470E417A9
</pre>
<p>Now go to the DNS management panel for your domain at Go Daddy and you&#8217;ll see something like this. Click on <strong>DNSSEC</strong> and then click <strong>ADD</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-04.png" alt="" width="419" height="529" class="aligncenter size-full wp-image-8299" /></a><br />
Looks confusing but it&#8217;s very simple. Just look at the output from the <strong>dnssec-dsfromkey</strong> and add accordingly. You&#8217;ll have to add both hash values.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-05.png" alt="" width="800" height="573" class="aligncenter size-full wp-image-8300" /></a><br />
For <strong>Key Tag</strong> enter <strong>23956</strong>, <strong>Algorithm</strong> is <strong>8</strong>, <strong>Digest Type</strong> is <strong>1</strong>, <strong>Digest </strong>is the hash value and <strong>Key Data Algorithm</strong> is also <strong>8</strong>, the value of <strong>Algorithm</strong>. Click <strong>Update </strong>, do the 2nd line of the output and wait 30-45 mins. Sometimes, even more. It depends on your registrant.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-06.png" alt="" width="861" height="892" class="aligncenter size-full wp-image-8301" /></a><br />
At the end it should look like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-07.png" alt="" width="1478" height="325" class="aligncenter size-full wp-image-8302" /></a><br />
And if you analyze your domain again, the result should look like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2018/12/P118-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2018/12/P118-08.png" alt="" width="713" height="929" class="aligncenter size-full wp-image-8303" /></a><br />
Or if you use dig, use the Google&#8217;s DNS server to check.</p>
<pre class="brush: bash; title: ; notranslate">
dig +trace +noadditional DS cloudwerk.us. @8.8.8.8 | grep DS
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2018/12/141-centos-install-an-authoritative-dns-server-bind/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
