Tag Archives: RHEL

Hosting Your Own RPM Repository

Introduction

There are lots of reasons why you might want to host your own RPM repository. A few reasons up front:

  • Speed: Performing a yum (or dnf) update from one of your locally hosted repositories is much faster than checking the ones on the internet!
  • Cost: If you run a business, then you know bandwidth can be expensive. If Linux is the operating system your developers and/or employees use, then hosting the repository internally means they won’t waste bandwidth each time one of them performs a system update. You’ll have downloaded everything once, and provided a central controlled share point they can apply all of their updates from.
  • Retention: Over time, your successfully deployed systems will age and the packages that were once readily available for it may be a lot harder to acquire! Sometimes it’s a good thing to create and manage your own internal repository so that you always have access to the exact RPMs you used to deploy/create your development environment with. This is especially the case for that one rare day that arises requiring you to go back and debug a legacy system of yours. If the packages are already available to you locally; you should have no problem reproducing any detected problems since you’ll be able to reconstruct their environment exactly.
  • Mock: I love this tool; mock allows you to dynamically generate different distributions of CentOS/Fedora (new and old) allowing you to build your applications to test them in it. You simply create a mock environment of the distribution of interest. In this new environment, you can do whatever it was you wanted to, then you can easily blow it away when you’re done. The best part about mock is that it allows you to build/test things without hauling in all of the development libraries into your native working environment. Quite frankly, I couldn’t have hosted or tested half of the things I do in all my blogs and my repository without it. Mock generates it’s ‘throw/away’ environments by connection to a yum repository and setting itself up. If you host your own repositories, it can greatly speed up this process.
  • Consistency: If everyone in your department were to reference the same rpm repository instead of one of the hundreds of mirrors available on the internet, you would consistently be hosting and sharing the same packages internally with your team. This is fantastic in a software development environment where everyone should be using the same packages anyway. This isn’t to say that all public CentOS mirrors are different, but they do go up and down from time to time. They also all synchronize themselves with whatever the latest and greatest at different times too.
  • Mirrors: Even if you just host your own mirror publicly, you’d be doing the Linux community good! You’d become another server of the hundreds already out there providing a source for free software! Your efforts would offload network congestion others face and speed up everyone’s Linux experience.
  • Custom Media: Hosting your own RPM packages can grant you the power to build your own custom media. You can accomplish the same task without hosting internally, but it’s much (,much) slower! This will make for a great topic in another blog though since this process is a topic of it’s own. I do however explain how to host your own custom repositories in this blog though!

Local Hosting Environment

The whole hosting process will take up some disk-space… 150GB or so if you decide to host everything I identify here. Nothing major, but worth noting for those with smaller disk drives.

The first thing you need to do is decide where you’ll host everything from. I will use the following directories for this blog entry:

Directory Details
/var/share/repo The location we’ll host the repository from.
/var/share/isos The location we’ll store our ISO image files in.

First let’s make sure our directories exist:

# Repository data will go here: /var/share/repo
[ ! -d /var/share/repo ] && \
   mkdir -p var/share/repo

# ISOs downloaded/kept will go here: /var/share/isos
[ ! -d /var/share/isos ] && \
   mkdir -p var/share/isos

You’ll want to have the following tools on hand as well if you plan on hosting your own repositories:

# Mirroring tools
yum install -y rsync lftp

# Repository management tools
yum install -y createrepo yum-utils find

Repository Hosting

The next few sections focus on the following repositories; you may or may not need them all. So feel free to just use what you need.

Distribution 64-Bit 32-Bit
CentOS 5.11 x86_64 i386
CentOS 6.8 x86_64 i386
CentOS 7.2 x86_64 n/a

Note: The blog utilizes the lftp tool for all of the synchronization, but the rsync command is also documented in the comments if you prefer that route as well. It’s also worth noting that you can re-run these mirror commands again and again to keep your local repository updated with the latest.

CentOS 5.11

64-bit

# Change to our ISO directory
pushd /var/share/isos

wget http://mirror.csclub.uwaterloo.ca/centos/5.11/isos/x86_64/CentOS-5.11-x86_64-bin-DVD-1of2.iso \
   -O /var/share/isos/CentOS-5.11-x86_64-DVD1.iso
wget http://mirror.csclub.uwaterloo.ca/centos/5.11/isos/x86_64/CentOS-5.11-x86_64-bin-DVD-2of2.iso \
   -O /var/share/isos/CentOS-5.11-x86_64-DVD2.iso

# Create temporary directories to work in
mkdir dvd1
mkdir dvd2

# Mount it
mount -o loop,ro CentOS-5.11-x86_64-DVD1.iso dvd1
mount -o loop,ro CentOS-5.11-x86_64-DVD2.iso dvd2

# CentOS Base Mirror
[ ! -d /var/share/repo/centos/5.11/x86_64/os ] && \
    mkdir -p /var/share/repo/centos/5.11/x86_64/os

# CentOS Updates Mirror
[ ! -d /var/share/repo/centos/5.11/x86_64/updates ] && \
    mkdir -p /var/share/repo/centos/5.11/x86_64/updates

# CentOS Extras Mirror
[ ! -d /var/share/repo/centos/5.11/x86_64/extras ] && \
    mkdir -p /var/share/repo/centos/5.11/x86_64/extras

# Setup EPEL location
[ ! -d /var/share/repo/centos/5/x86_64/epel ] && \
    mkdir -p /var/share/repo/centos/5/x86_64/epel

# Create a fixed link to newest repo
pushd /var/share/repo/centos/5/x86_64
ln -snf ../../5.11/x86_64/os os
ln -snf ../../5.11/x86_64/updates updates
ln -snf ../../5.11/x86_64/extras extras
popd

# Set up other repositories
pushd /var/share/repo/centos/5.11/x86_64
ln -snf ../../5/x86_64/epel epel
popd

# Mirror repository
rsync -av --ignore-existing dvd1/ /var/share/repo/centos/5.11/x86_64/os/
rsync -av --ignore-existing dvd2/CentOS/ /var/share/repo/centos/5.11/x86_64/os/Packages/
 
# Cleanup
umount dvd1
umount dvd2
rmdir dvd1
rmdir dvd2
popd

# Mirror (5.11) Updates
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/5.11/updates/x86_64/ /var/share/repo/centos/5.11/x86_64/updates/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/5.11/updates/x86_64/ /var/share/repo/centos/5.11/x86_64/updates"

# Mirror (5.11) Updates
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/5.11/extras/x86_64/ /var/share/repo/centos/5.11/x86_64/extras/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/5.11/extras/x86_64/ /var/share/repo/centos/5.11/x86_64/extras"

# EPEL: (see https://admin.fedoraproject.org/mirrormanager/mirrors/EPEL/5 for list of servers)
# In the future you can run these commands again and again to update your repository
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/fedora-enchilada/epel/6/x86_64/ /var/share/repo/centos/5/x86_64/epel/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /fedora/epel/5/x86_64/ /var/share/repo/centos/5/x86_64/epel"

32-bit

# Change to our ISO directory
pushd /var/share/isos

wget http://mirror.csclub.uwaterloo.ca/centos/5.11/isos/i386/CentOS-5.11-i386-bin-DVD-1of2.iso \
   -O /var/share/isos/CentOS-5.11-i386-DVD1.iso
wget http://mirror.csclub.uwaterloo.ca/centos/5.11/isos/i386/CentOS-5.11-i386-bin-DVD-2of2.iso \
   -O /var/share/isos/CentOS-5.11-i386-DVD2.iso

# Create temporary directories to work in
mkdir dvd1
mkdir dvd2

# Mount it
mount -o loop,ro CentOS-5.11-i386-DVD1.iso dvd1
mount -o loop,ro CentOS-5.11-i386-DVD2.iso dvd2

# CentOS Base Mirror
[ ! -d /var/share/repo/centos/5.11/i386/os ] && \
    mkdir -p /var/share/repo/centos/5.11/i386/os

# CentOS Updates Mirror
[ ! -d /var/share/repo/centos/5.11/i386/updates ] && \
    mkdir -p /var/share/repo/centos/5.11/i386/updates

# CentOS Extras Mirror
[ ! -d /var/share/repo/centos/5.11/i386/extras ] && \
    mkdir -p /var/share/repo/centos/5.11/i386/extras

# Setup EPEL Mirror
[ ! -d /var/share/repo/centos/5/i386/epel ] && \
    mkdir -p /var/share/repo/centos/5/i386/epel

# Create a fixed link to newest repo
pushd /var/share/repo/centos/5/i386
ln -snf ../../5.11/i386/os os
ln -snf ../../5.11/i386/updates updates
ln -snf ../../5.11/i386/extras extras
popd

# Set up other repositories
pushd /var/share/repo/centos/5.11/i386
ln -snf ../../5/i386/epel epel
popd

# Mirror repository
rsync -av --ignore-existing dvd1/ /var/share/repo/centos/5.11/i386/os/
rsync -av --ignore-existing dvd2/CentOS/ /var/share/repo/centos/5.11/i386/os/Packages/
 
# Cleanup
umount dvd1
umount dvd2
rmdir dvd1
rmdir dvd2
popd

# Mirror (5.11) Updates
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/5.11/updates/i386/ /var/share/repo/centos/5.11/i386/updates/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/5.11/updates/i386/ /var/share/repo/centos/5.11/i386/updates"

# Mirror (5.11) Extras
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/5.11/extras/i386/ /var/share/repo/centos/5.11/i386/extras/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/5.11/extras/i386/ /var/share/repo/centos/5.11/i386/extras"

# EPEL: (see https://admin.fedoraproject.org/mirrormanager/mirrors/EPEL/5 for list of servers)
# In the future you can run these commands again and again to update your repository
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/fedora-enchilada/epel/6/i386/ /var/share/repo/centos/5/i386/epel/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /fedora/epel/5/i386/ /var/share/repo/centos/5/i386/epel"

CentOS 6.8

64-bit

# Change to our ISO directory
pushd /var/share/isos

wget http://mirror.its.dal.ca/centos/6.8/isos/x86_64/CentOS-6.8-x86_64-bin-DVD1.iso \
   -O /var/share/isos/CentOS-6.8-x86_64-DVD1.iso
wget http://mirror.its.dal.ca/centos/6.8/isos/x86_64/CentOS-6.8-x86_64-bin-DVD2.iso \
   -O /var/share/isos/CentOS-6.8-x86_64-DVD2.iso

# Create temporary directories to work in
mkdir dvd1
mkdir dvd2

# Mount it
mount -o loop,ro CentOS-6.8-x86_64-DVD1.iso dvd1
mount -o loop,ro CentOS-6.8-x86_64-DVD2.iso dvd2

# CentOS Base Mirror
[ ! -d /var/share/repo/centos/6.8/x86_64/os ] && \
    mkdir -p /var/share/repo/centos/6.8/x86_64/os

# CentOS Updates Mirror
[ ! -d /var/share/repo/centos/6.8/x86_64/updates ] && \
    mkdir -p /var/share/repo/centos/6.8/x86_64/updates

# CentOS Extras Mirror
[ ! -d /var/share/repo/centos/6.8/x86_64/extras ] && \
    mkdir -p /var/share/repo/centos/6.8/x86_64/extras

# Setup EPEL location
[ ! -d /var/share/repo/centos/6/x86_64/epel ] && \
    mkdir -p /var/share/repo/centos/6/x86_64/epel

# Create a fixed link to newest repo
pushd /var/share/repo/centos/6/x86_64
ln -snf ../../6.8/x86_64/os os
ln -snf ../../6.8/x86_64/updates updates
ln -snf ../../6.8/x86_64/extras extras
popd

# EPEL Repository Mirror
pushd /var/share/repo/centos/6.8/x86_64
ln -snf ../../6/x86_64/epel epel
popd

# Mirror repository
rsync -av --ignore-existing dvd1/ /var/share/repo/centos/6.8/x86_64/os/
rsync -av --ignore-existing dvd2/Packages/ /var/share/repo/centos/6.8/x86_64/os/Packages/

# Cleanup
umount dvd1
umount dvd2
rmdir dvd1
rmdir dvd2
popd

# Mirror (6.8) Updates
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/6.8/updates/x86_64/ /var/share/repo/centos/6.8/x86_64/updates/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/6.8/updates/x86_64/ /var/share/repo/centos/6.8/x86_64/updates"

# Mirror (6.8) Extras
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/6.8/extras/x86_64/ /var/share/repo/centos/6.8/x86_64/extras/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/6.8/extras/x86_64/ /var/share/repo/centos/6.8/x86_64/extras"

# EPEL: (see https://admin.fedoraproject.org/mirrormanager/mirrors/EPEL/6 for list of servers)
# In the future you can run these commands again and again to update your repository
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/fedora-enchilada/epel/6/x86_64/ /var/share/repo/centos/6/x86_64/epel/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /fedora/epel/6/x86_64/ /var/share/repo/centos/6/x86_64/epel"

32-bit

# Change to our ISO directory
pushd /var/share/isos

wget http://mirror.its.dal.ca/centos/6.8/isos/i386/CentOS-6.8-i386-bin-DVD1.iso \
   -O /var/share/isos/CentOS-6.8-i386-DVD1.iso
wget http://mirror.its.dal.ca/centos/6.8/isos/i386/CentOS-6.8-i386-bin-DVD2.iso \
   -O /var/share/isos/CentOS-6.8-i386-DVD2.iso

# Create temporary directories to work in
mkdir dvd1
mkdir dvd2

# Mount it
mount -o loop,ro CentOS-6.8-i386-DVD1.iso dvd1
mount -o loop,ro CentOS-6.8-i386-DVD2.iso dvd2

# CentOS Base Mirror
[ ! -d /var/share/repo/centos/6.8/i386/os ] && \
    mkdir -p /var/share/repo/centos/6.8/i386/os

# CentOS Updates Mirror
[ ! -d /var/share/repo/centos/6.8/i386/updates ] && \
    mkdir -p /var/share/repo/centos/6.8/i386/updates

# CentOS Extras Mirror
[ ! -d /var/share/repo/centos/6.8/i386/extras ] && \
    mkdir -p /var/share/repo/centos/6.8/i386/extras

# EPEL Repository Mirror
[ ! -d /var/share/repo/centos/6/i386/epel ] && \
    mkdir -p /var/share/repo/centos/6/i386/epel

# Create a fixed link to newest repo
pushd /var/share/repo/centos/6/i386
ln -snf ../../6.8/i386/os os
ln -snf ../../6.8/i386/updates updates
ln -snf ../../6.8/i386/extras extras
popd

# Set up other repositories
pushd /var/share/repo/centos/6.8/i386
ln -snf ../../6/i386/epel epel
popd

# Mirror repository
rsync -av --ignore-existing dvd1/ /var/share/repo/centos/6.8/i386/os/
rsync -av --ignore-existing dvd2/Packages/ /var/share/repo/centos/6.8/i386/os/Packages/

# Cleanup
umount dvd1
umount dvd2
rmdir dvd1
rmdir dvd2
popd

# Mirror (6.8) Updates
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/6.8/updates/i386/ /var/share/repo/centos/6.8/i386/updates/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/6.8/updates/i386/ /var/share/repo/centos/6.8/i386/updates"

# Mirror (6.8) Extras
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/6.8/extras/i386/ /var/share/repo/centos/6.8/i386/extras/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/6.8/extras/i386/ /var/share/repo/centos/6.8/i386/extras"

# EPEL: (see https://admin.fedoraproject.org/mirrormanager/mirrors/EPEL/6 for list of servers)
# In the future you can run these commands again and again to update your repository
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/fedora-enchilada/epel/6/i386/ /var/share/repo/centos/6/i386/epel/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /fedora/epel/6/i386/ /var/share/repo/centos/6/i386/epel"

CentOS 7.2

64-bit

# Change to our ISO directory
pushd /var/share/isos

# CentOS 7.2
[ ! -d /var/share/isos/ ] && mkdir -p /var/share/isos/
wget http://mirror.its.dal.ca/centos/7.2.1511/isos/x86_64/CentOS-7-x86_64-Everything-1511.iso \
   -O /var/share/isos/CentOS-7.2.1511-x86_64-Everything.iso

# CentOS Base Mirror
[ ! -d /var/share/repo/centos/7.2/x86_64/os ] && \
    mkdir -p /var/share/repo/centos/7.2/x86_64/os

# CentOS Updates Mirror
[ ! -d /var/share/repo/centos/7.2/x86_64/updates ] && \
    mkdir -p /var/share/repo/centos/7.2/x86_64/updates

# CentOS Extras Mirror
[ ! -d /var/share/repo/centos/7.2/x86_64/extras ] && \
    mkdir -p /var/share/repo/centos/7.2/x86_64/extras

# EPEL Repository Mirror
[ ! -d /var/share/repo/centos/7/x86_64/epel ] && \
    mkdir -p /var/share/repo/centos/7/x86_64/epel

# Create a fixed link to newest repo
pushd /var/share/repo/centos/7/x86_64
ln -snf ../../7.2/x86_64/os os
ln -snf ../../7.2/x86_64/updates updates
ln -snf ../../7.2/x86_64/extras extras
popd

# Set up other repositories
pushd /var/share/repo/centos/7.2/x86_64
ln -snf ../../7/x86_64/epel epel
popd

# Mount it
mount -o loop,ro /var/share/isos/CentOS-7.2.1511-x86_64-Everything.iso \
    /var/share/repo/centos/7.2/x86_64/os

# Update our fstab file so we persistently do this on reboots; This saves disk space
sed -i -e '/CentOS-7\.2\.1511-x86_64-Everything\.iso/d' /etc/fstab
echo '/var/share/isos/CentOS-7.2.1511-x86_64-Everything.iso /var/share/repo/centos/7.2/x86_64/os iso9660 loop,ro,auto 0 0' >> /etc/fstab
# If you prefer not to run the above command, you can follow through with the same rsync
# commands identified above and mirror the contents of the iso to disk

# Mirror (7.2) Updates
# In the future you can run these commands again and again to update your repository
# Updates: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/7.2.1511/updates/x86_64/ /var/share/repo/centos/7.2/x86_64/updates/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/7.2.1511/updates/x86_64/ /var/share/repo/centos/7.2/x86_64/updates"

# Mirror (7.2) Extras
# In the future you can run these commands again and again to update your repository
# EPEL: (see https://www.centos.org/download/mirrors/ for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/centos/7.2.1511/extras/x86_64/ /var/share/repo/centos/7.2/x86_64/extras/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /centos/7.2.1511/extras/x86_64/ /var/share/repo/centos/7.2/x86_64/extras"

# Mirror EPEL
# In the future you can run these commands again and again to update your repository
# EPEL: (see https://admin.fedoraproject.org/mirrormanager/mirrors/EPEL/7 for list of servers)
# rsync -av --ignore-existing rsync://mirror.csclub.uwaterloo.ca/fedora-enchilada/epel/7/x86_64/ /var/share/repo/centos/7/x86_64/epel/
# or via lftp:
lftp mirror.csclub.uwaterloo.ca -e "mirror --verbose /fedora/epel/7/x86_64/ /var/share/repo/centos/7/x86_64/epel"

Web Hosting

Once you’ve got your repositories mirrored, you need to host them. Here is the easiest way to do so:
If you’re (planning on) using NginX, then the following will get you going:

# Install nginx (if it's not there already)
yum install nginx -y

# Enable for future reboots
systemctl enable nginx.service

# Start it up
systemctl start nginx.service

cat << _EOF > /etc/nginx/default.d/repo.conf
location /repo/ {
   alias         /var/share/repo/;
   autoindex on;
}
_EOF
# Reload
systemctl reload nginx.service

Now you should be able to access your website by visiting the server you set this up on with /repo as the path. ie: http://localhost/repo

SELinux

Users running SELinux in enforcing mode will want to do the following so that they’re repository can be hosted properly:

# make sure /var/share/repo can host website content without conflicting
# with SELinux
semanage fcontext -a -t httpd_sys_content_t '/var/share/repo(/.*)?'

YUM Repositories

Now we’ll want to update our servers around our office, or maybe just on this PC we’re using to point to our new repositories. Here is probably the easiest way:

# Disable any existing repository setup (the following has
# to be ran as root):
pushd /etc/yum.repo.d/
sed -e 's/^\(enabled\)=.*/\1=0/g' \
   fedora*.repo centos*.repo epel*.repo &>/dev/null
popd

# Set this variable to the ip/host of the machine you set up your
# repository on:
MYREPOADDR=localhost

# Now install our new file:
cat << _EOF > /etc/yum.repo.d/centos.internal.repo
[internal-base]
name=CentOS \$releasever - \$basearch - Base
baseurl=http://$MYREPOADDR/repo/centos/\$releasever/\$basearch/os/
enabled=1
priority=1
gpgcheck=0
skip_if_unavailable=False

[internal-updates]
name=CentOS \$releasever - \$basearch - Updates
failovermethod=priority
baseurl=http://$MYREPOADDR/repo/centos/\$releasever/\$basearch/updates/
enabled=1
priority=1
gpgcheck=0
skip_if_unavailable=True

[internal-extras]
name=CentOS \$releasever - \$basearch - Extras
failovermethod=priority
baseurl=http://$MYREPOADDR/repo/centos/\$releasever/\$basearch/extras/
enabled=1
priority=1
gpgcheck=0
skip_if_unavailable=True

[internal-epel]
name=CentOS \$releasever - \$basearch - Internal EPEL
failovermethod=priority
baseurl=http://$MYREPOADDR/repo/centos/\$releasever/\$basearch/epel/
enabled=1
priority=1
gpgcheck=0
skip_if_unavailable=True
_EOF

You can easily take the examples provided here and alter them for other repositories you wish to host.

Custom Repositories

Perhaps you’re picking and choosing RPMs from a ton of different sources, or you’re building your own. It’s a good idea not to touch the repositories we’re already mirroring. Leave them exactly the way they are. However, you can create your own repository instead that you can place your personal collection of rpms in:

# Lets presume we want to start a custom CentOS 7 (64-bit) repository.
# we'll call it 'custom' for now, but you can call it whatever you want

[ ! -d /var/share/repo/centos/7/x86_64/custom ] && \
    mkdir -p /var/share/repo/centos/7/x86_64/custom

# Create a fixed link to our repo
pushd /var/share/repo/centos/7.2/x86_64
ln -snf ../../7/x86_64/custom custom
popd

# Okay... now you can drop all the RPMs you want into this custom repository.
# just copy them in; no strings attached.
cp my.awesome.application-1.0.0-1.x86_64.rpm

# The next step is to make the repository accessible by yum
# If you're using dnf, there is no problem, these commands still apply:

# First we want to reset our repodata folder.  This probably won't
# exist the first time you do this, but in the future, when you add
# more rpms into this directory, this will be an essential step:
[ -d /var/share/repo/centos/7/x86_64/custom/repodata ] && \
    rm -rf /var/share/repo/centos/7/x86_64/custom/repodata

# Now lets clean out any duplicate/old RPM entries
# This is optional; it basically looks for 2 RPMs of the same name
# but of different versions and only keeps the newest.  If you like
# hanging on to all of the versions of your software, you don't
# need to run the below command.
repomanage -o /var/share/repo/centos/7/x86_64/custom/repodata | \
   xargs rm -f

The next step requires us to generate a comps file. These are XML files that define details of our repository. You can get complicated and define it yourself if you like, but to make things simple, you can just use this script i wrote which will generate one on the fly based on the directory you specify it to parse:

#!/bin/sh
# Name: updaterepo
# Author: Chris Caron <lead2gold@gmail.com>
# URL: See https://nuxref.com for details
# Description:
#   A simple script for creating a repository directory.
#
# Syntax: updaterepo <path_to_repository_hosting_rpms>
#
REPODIR=$1
if [ -z "$REPODIR" ]; then
   echo "You must specify a repository directory to scan (containing rpms)."
   exit 1
fi

if [ ! -d "$REPODIR" ]; then
   echo "You must specify a repository directory to scan (containing rpms)."
   exit 1
fi

# Ensure we're dealing with absolute paths
pushd $REPODIR &>/dev/null && REPODIR=$(pwd) && popd &>/dev/null

# Some Default Variables; please feel free to change these to
# suit your own needs and package identification. Whatever you
# specify here will become a 'group' as far as yum and dnf are
# concerned allowing you to run (set $ID according):
#   yum groupinstall custom
ID="custom"
NAME="Core"
DESC="Custom Built Packages"

# Generate a comps file for a repository dynamically
COMPSFILE=comps-$ID.xml
COMPSDIR=$REPODIR/repodata
COMPS=$COMPSDIR/$COMPSFILE

# Directory Management
[ -d $COMPSDIR ] && rm -rf $COMPSDIR
[ ! -d $COMPSDIR ] && mkdir -p $COMPSDIR

FILES=$(find -L $REPODIR -mindepth 1 -maxdepth 1 -type f -name "*.rpm" -exec basename {} \;)

   cat << _EOF > "$COMPS"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE comps PUBLIC "-//REDHAT//DTD Comps info//EN" "comps.dtd">
<comps>
  <group>
    <id>$ID</id>
    <name>$NAME</name>
    <description>$DESC</description>
    <default>True</default>
    <uservisible>True</uservisible>
    <packagelist>
_EOF
for FILE in $FILES; do
   PFILE=$(rpm -qp "$REPODIR/$FILE" --nosignature --queryformat="%{NAME}" 2>/dev/null)
   cat << _EOF >> "$COMPS"
         <packagereq type="default">$PFILE</packagereq>
_EOF
done
cat << _EOF >> "$COMPS"
    </packagelist>
  </group>
  <category>
    <id>$ID</id>
    <name>$NAME</name>
    <display_order>99</display_order>
    <grouplist>
      <groupid>$ID</groupid>
    </grouplist>
  </category>
</comps>
_EOF

# Now rebuild our repo information
createrepo -d -q -g $COMPS $REPODIR

Place the script file above into your /usr/bin directory if you like (called updaterepo) and call it on any directory you want to turn into a repository from then on:

# Make sure our new script is executable
chmod 775 /bin/updaterepo

# Now call it against any directory we want to turn into a repository
[ ! -d /var/share/repo/centos/7/x86_64/custom ] && 
    mkdir -p  /var/share/repo/centos/7/x86_64/custom

# Now we can copy our rpms into this directory at will.
# Then we just need to run this command when we're done to apply
# our changes. This will allow people who are pointing to our
# repository to see these new changes we made and access it via yum/dnf
updaterepo  /var/share/repo/centos/7/x86_64/custom


You're done! Now you can update your <strong>/etc/yum.repos.d/</strong> to include this new location with each yum call you make! Just use the other examples already provided in this blog as a template!

<h1>Mock</h1>
<a href="https://github.com/rpm-software-management/mock/wiki" target="_blank">Mock</a> is a fantastic tool for RPM management.  It's also a great tool for someone who just wants to test and see if they're code will run on another platform.  You could almost think of Mock as a poor man's <a href="https://linuxcontainers.org/" target="_blank">Linux Container</a> which are pretty popular these days.  Mock isn't as contained, but it can accomplish the same feat and is even faster since it doesn't have the (Linux) container overhead.

Mock is set up as follows:

# first install it (as root)
yum install -y mock

# If you followed all of the instructions above, then this will pull mock
# out of your internal epel repository! Nice!

# Now just add your user account you usually use on your system
# (a non-root user).  This user will be granted mock privileges.
# substitute [User name] below with the username you usually use:
usermod -a -G mock [User name]

Just like that' you're now ready to create mock environments. Now by default, mock is configured to fetch it's information from the external repositories on the internet. But it's configuration is really easy to get ahold of and update. Check out the /etc/mock directory.

You can now edit the mock environment you wish to host and optionally update the repositories to point to your own. For example, pick a file like /etc/mock/epel-7-x86_64.cfg which is used to generate a 64-bit Enterprise Linux (RedHat/CentOS) 7.x environment.

You'll see entries like this which will look very familiar (it's a yum/dnf configuration file entry just like the ones identified above). You can comment out the mirrorlist entry and swap it with your own local repository.

...  ...
[base]
name=BaseOS
# comment out the mirrorlist reference:
# mirrorlist=http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=os
# point to our own local repository instead:
baseurl=http://localhost/repo/centos/7/x86_64/os/
failovermethod=priority
gpgkey=file:///usr/share/distribution-gpg-keys/centos/RPM-GPG-KEY-CentOS-7
gpgcheck=1

[updates]
name=updates
enabled=1
# comment out the mirrorlist reference:
# mirrorlist=http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=updates
# point to our own local repository instead:
baseurl=http://localhost/repo/centos/7/x86_64/updates/
failovermethod=priority
gpgkey=file:///usr/share/distribution-gpg-keys/centos/RPM-GPG-KEY-CentOS-7
gpgcheck=1

[extras]
name=extras
enabled=1
# comment out the mirrorlist reference:
# mirrorlist=http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=extras
# point to our own local repository instead:
baseurl=http://localhost/repo/centos/7/x86_64/extras/
failovermethod=priority
gpgkey=file:///usr/share/distribution-gpg-keys/centos/RPM-GPG-KEY-CentOS-7
gpgcheck=1

[epel]
name=epel
# comment out the mirrorlist reference:
# mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=epel-7&arch=x86_64
# point to our own local repository instead:
baseurl=http://localhost/repo/centos/7/x86_64/updates/
failovermethod=priority
gpgkey=file:///usr/share/distribution-gpg-keys/epel/RPM-GPG-KEY-EPEL-7
gpgcheck=1

...  ...

You can also just copy and paste one of the config files to another and change it around. Add repositories, remove some; the config file for mock is really straight forward if you use another as a template.

Mock is pretty straight forward to use. You just need to pass it in the environment you're using with each call you make to it.

# Initialize a Mock Environment of Enterprise Linux 7 (64-bit):
mock -r epel-7-x86_64 --init

# The above command works because a /etc/mock/epel-7-x86_64.cfg file exists.
# mock is relatively quiet and doesn't output a lot of information which can
# sometimes make you unsure if it's actually doing anything.  So it doesn't
# hurt to also get in the habit of adding -v to every single mock action you
# perform.

# You can install RPMs into your mock environment easily with:
mock -v -r epel-7-x86_64 install hostname vi

# The following command will cause us to leave our current native environment
# and access the mock environment (an advanced chroot basically):
mock -v -r epel-7-x86_64 --shell

# Once inside, you can do whatever you want; just press Ctrl-D or type 'exit'
# to return back to your normal environment.

# You can even rebuild a source rpm package from another distribution
# into one you might use.  For example, perhaps you visited pkgs.org and saw
# a newer version of an awesome application available for Fedora 24, but
# can't get it for CentOS 7.  Well you can download the src.rpm file and
# rebuild it using mock:
mock -v -r epel-7-x86_64 --rebuild awesome.application-v2.0.0-1.fc24.src.rpm \
    --resultdir=awesome.app

# I added the --resultdir= on the line above to tell mock that after it rebuilds
# our new RPMs for CentOS 7, it places them into a directory called 'awesome.app'.
# This isn't necessary, but makes it easier to find the RPMs when the build is
# complete.

Credit

If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.

Sources

  • Mock: A fantastic development tool that allows you to dynamically generate a fully functional development environment for a CentOS/Red Hat/Fedora distribution of your choice. It allows you to keep development libraries out of your native (working) environment keeping things clean.
  • Some good repositories worth mirroring (in no particular order):
    • CentOS 5.x, 6.x and 7.x: Here is a list of all of the mirrors you can sync from.
    • Extra Packages for Enterprise Linux (EPEL): CentOS/Red Hat users shouldn't be without this one. It has tons of the building blocks you might need to reconstruct things with.
    • NuxRef: Yes... my repository; I mean why not, right?
    • Fedora: Bleeding edge distributions don't stick around long. After a year or so, you'll be hard-pressed to find RPMs. It might be wise to mirror the distribution you're using if you don't plan on upgrading anytime soon. There is enough information in this blog to mirror a Fedora repository if you want to do so.

Block and Unblock Ports on Your Firewall Quickly

Introduction

This blog just shares and explains the function of a script I created for *nux based systems that can be used to quickly block and unblock ports on your firewall. It works with both IPTables and FirewallD. It can also be easily configured to work with a cron and ran at key times.

There are several reasons why you might want to quickly block a port on your firewall.

  • Maybe you suspect someone is intruding or violating your system.
  • Maybe you want to leave your computer running (it’s crunching data for you), but for security reasons you want to close all of the ports when you’re not around (such as after work hours or over the weekend).

The Goods

At the present time, the script supports blocking inbound tcp and udp ports. It also supports blocking port ranges.
Simply copy this into a script file which I called blockport.sh (don’t forget to make it executable) and place it in your path:

#!/bin/sh
# Name: blockport.sh
# Author: Chris Caron <lead2gold@gmail.com>
# Date: Jul 17th, 2015
# Description: A simple script that blocks and unblocks outbound traffic
#              The intent is to set up a cron that will block stuff at night
#              and then unblock it in the morning every day preventing
#              any unnecessary things from making outbound requests.
#
# Dependencies: sed, logger, grep, and iptables
#
# This script must run as root!

SCRIPTNAME=$(basename $0)
IPTABLES_BIN=iptables
IPTABLES_PRINT=echo

# Set the command to what you want to actually do
# some scripts explicitly reference the above
IPTABLES=$IPTABLES_PRINT
IPTABLES=$IPTABLES_BIN

# Default Line Numbers (where inserts will take place
IPTABLES_OUTPUT_LINE=${IPTABLES_OUTPUT_LINE:=1}
IPTABLES_INPUT_LINE=${IPTABLES_INPUT_LINE:=1}

show_syntax () {
   echo "Syntax: \$> $SCRIPTNAME <Action> [Options]"
   echo "  Actions:"
   echo "     -l"
   echo "     --list                  List the ports blocked by this script."
   echo
   echo "     -bo PORT"
   echo "     --block-outbound=PORT   Blocks outbound ports (both udp and tcp). You can"
   echo "                              use space or comma to delimite multiple ports"
   echo "     -uo PORT"
   echo "     --unblock-outbound=PORT Unblocks outbound ports (both udp and tcp)"
   echo "                              previously blocked. You can use space or comma to"
   echo "                              delimit multiple ports"
   echo "     -bi PORT"
   echo "     --block-inbound=PORT    Blocks inbound ports (both udp and tcp). You can"
   echo "                              use space or comma to delimite multiple ports"
   echo "     -ui PORT"
   echo "     --unblock-inbound=PORT  Unblocks inbound ports (both udp and tcp)"
   echo "                              previously blocked. You can use space or comma to"
   echo "                              delimit multiple ports"
   echo ""
}

clean_ports(){
   # A Simple function that cleans up port ranges
   local PORTS=$(echo $1 | sed -e 's/[, \\/]\+/ /g' -e 's/[;-]\+/:/' \
             -e 's/[^:0-9 ]//g' -e 's/^[^0-9]\+//g' -e 's/[^0-9]\+$//g')
   [ -z "$PORTS" ] && return 1
   echo "$PORTS"
   return 0
}

indexes() {
   # Possible types are OUTPUT, INPUT, and FORWARD
   local TYPE=$1
   # A simple function that returns the index(es) of a iptable
   # entry (if it's blocked or not).
   local PORTS=$(clean_ports $2)
   [ $? -ne 0 ] && return 1

   local INDEXES=""
   # This magical line was constructed off of what i learned here:
   # http://unix.stackexchange.com/questions/129087/grep-log-and-get-text-between-log-delimiters
   # It extracts the DROP lines we created in the OUTPUT Chain
   for PORT in $PORTS; do
      INDEX=$($IPTABLES_BIN -nL --line-numbers | \
         grep -a -zPo "(\n?Chain $TYPE [^\n]*\n)\K(.|\n)+?[^\n][^Cc](.|\n)+?(?=\nChain [^\n]*\n)" | \
                egrep -a 'DROP' | egrep '^[0-9]' | egrep " dpts?:$PORT[ \t]*\$" | \
                sed -e 's/^[ \t]*\([0-9]\+\)[^0-9].*/\1/g')
      INDEXES="$INDEXES $INDEX"
   done

   [ -z "$INDEXES" ] && return 1

   # Sort the INDEXES (largest # to smallest) because we want to
   # process the list backwards
   INDEXES=$(echo $INDEXES | tr -s '[:space:]' '\n' | sort -n -r | uniq)
   echo $INDEXES
   return 0
}

unblock() {
   # Possible types are OUTPUT, INPUT, and FORWARD
   local TYPE=$1
   # A simple function that returns the index(es) of a iptable
   # entry (if it's blocked or not).
   local PORTS=$2

   # Defaults
   [ -z "$TYPE" ] && TYPE=INPUT

   # Stores the indexes (if set)
   local INDEXES="$(indexes $TYPE $PORTS)"
   [ -z "$INDEXES" ] && return 0

   # Sort the INDEXES (largest # to smallest) because we want to
   # process the list backwards
   INDEXES=$(echo $INDEXES | tr -s '[:space:]' '\n' | sort -n -r | uniq)

   for INDEX in $INDEXES; do
      $IPTABLES -D $TYPE $INDEX
   done
   logger "INFO - blockport.sh: Unblocked $TYPE $PORTS"
   return 0
}

block(){
   # Possible types are OUTPUT, INPUT, and FORWARD
   local TYPE=$1
   # A simple function that returns the index(es) of a iptable
   # entry (if it's blocked or not).
   local PORTS=$(clean_ports $2)
   [ $? -ne 0 ] && return 1

   # Defaults
   [ -z "$TYPE" ] && TYPE=INPUT

   # If indexes already exist, then we don't have to do anything
   local INDEXES="$(indexes $TYPE $PORTS)"
   [ ! -z "$INDEXES" ] && return 0

   local LINE=$(eval "echo \$IPTABLES_${TYPE}_LINE")
   [ -z $LINE ] && LINE=1
   for PORT in $PORTS; do
      $IPTABLES -I $TYPE $LINE -p tcp -s 0/0 --destination-port $PORT -j DROP
      $IPTABLES -I $TYPE $LINE -p udp -s 0/0 --destination-port $PORT -j DROP
   done
   logger "INFO - blockport.sh: Blocked $TYPE $PORTS"
   return 0
}

list() {
   echo
   for TYPE in "INPUT" "OUTPUT" ; do
      echo "Listing $TYPE Ports Blocked:"
      $IPTABLES_BIN -nL --line-numbers | \
         grep -a -zPo "(\n?Chain $TYPE [^\n]*\n)\K(.|\n)+?[^\n][^Cc](.|\n)+?(?=\nChain [^\n]*\n)" | \
         egrep -a 'DROP' | egrep '^[0-9]' | egrep " dpts?:[0-9:]+[ \t]*\$"
      echo
   done
   return 0
}

##########################################################
##                          Main                        ##
##########################################################
PATH=/bin:/sbin:/usr/bin:/usr/sbin
if [ $(whoami) != "root" ]; then
   echo "Error: you must be root to execute this script."
fi

ACTION="x"
RETVAL=0
while : ; do
   case $1 in
      -l) list; exit 0 ;;
      --list) list; exit 0 ;;
      -bo) ACTION='bo';
           block "OUTPUT" $2;
           [ $? -ne 0 ] && RETVAL=1
           shift; shift ;;
      --block-outbound) ACTION='bo';
           block "OUTPUT" $(echo $1 | sed -e 's/--block-outbound=//g' -e "s/'//g" -e 's/\"//g')
           [ $? -ne 0 ] && RETVAL=1
           shift ;;
      -uo) ACTION='uo';
           unblock "OUTPUT" $2;
           [ $? -ne 0 ] && RETVAL=1
           shift; shift ;;
      --unblock-outbound) ACTION='uo';
           unblock "OUTPUT" $(echo $1 | sed -e 's/--unblock-outbound=//g' -e "s/'//g" -e 's/\"//g')
           [ $? -ne 0 ] && RETVAL=1
           shift ;;
      -bi) ACTION='bi';
           block "INPUT" $2;
           [ $? -ne 0 ] && RETVAL=1
           shift; shift ;;
      --block-inbound) ACTION='bi';
           block "INPUT" $(echo $1 | sed -e 's/--block-inbound=//g' -e "s/'//g" -e 's/\"//g')
           [ $? -ne 0 ] && RETVAL=1
           shift ;;
      -ui) ACTION='ui';
           unblock "INPUT" $2;
           [ $? -ne 0 ] && RETVAL=1
           shift; shift ;;
      --unblock-inbound) ACTION='ui';
           unblock "INPUT" $(echo $1 | sed -e 's/--unblock-inbound=//g' -e "s/'//g" -e 's/\"//g')
           [ $? -ne 0 ] && RETVAL=1
           shift ;;
      -h) show_syntax ; exit 0 ;;
      --help) show_syntax ; exit 0 ;;
       *) if [ -z "$1" ]; then break; fi
          echo "[error] Invalid option '$1' specified; see --help (-h) for more info."
          exit 1
          ;;
   esac
done

if [ $ACTION == "x" ]; then
   show_syntax
   exit 1
fi

exit $RETVAL

Again, I state: this script must be ran as root (because it wraps IPTables). So stick sudo in front of it’s calls if running as a regular user (assuming you’re set up with sudoer’s privileges).

Syntax

Syntax: $> blockport.sh  [Options]
  Actions:
     -l
     --list                  List the ports blocked by this script.

     -bo PORT
     --block-outbound=PORT   Blocks outbound ports (both udp and tcp). You can
                              use space or comma to delimit multiple ports
     -uo PORT
     --unblock-outbound=PORT Unblocks outbound ports (both udp and tcp)
                              previously blocked. You can use space or comma to
                              delimit multiple ports
     -bi PORT
     --block-inbound=PORT    Blocks inbound ports (both udp and tcp). You can
                              use space or comma to delimit multiple ports
     -ui PORT
     --unblock-inbound=PORT  Unblocks inbound ports (both udp and tcp)
                              previously blocked. You can use space or comma to
                              delimit multiple ports

Demo

Here is a simple example that just blocks 2 ports. We can chain more than one port by placing a comma in between each one. It is also valid to keep using one switch after another (they’ll be executed in order).

# Here is how easy it is to use; first we'll block (inbound) ports 80 and 443
blockport.sh -bi 80,443

# This is also valid syntax:
#    blockport.sh -bi 80 -bi 443

# We know they're blocked now, but we can have a look anyway:
blockport.sh -l
# The output will look like this:
# Listing INPUT Ports Blocked:
# 1    DROP   udp  --  0.0.0.0/0   0.0.0.0/0   udp dpt:443
# 2    DROP   tcp  --  0.0.0.0/0   0.0.0.0/0   tcp dpt:443
# 3    DROP   udp  --  0.0.0.0/0   0.0.0.0/0   udp dpt:80
# 4    DROP   tcp  --  0.0.0.0/0   0.0.0.0/0   tcp dpt:80
#
# Listing OUTPUT Ports Blocked:
#

# We can reverse this by typing:
blockport.sh -ui 80,443

# This is also valid syntax:
# blockport.sh -ui 80 -ui 443

You can do ranges too; just use the colon (:) or hyphen (-). In the example below, we block a range of outgoing traffic from a system:

# Below blocks outbound ports 20, 21, and all ports (and including) 8000-8500.
blockport.sh -bo 20,21,8000-8500

# This is also valid syntax:
#    blockport.sh -bo 20 -bo 21 -bo 8000-8500

# We know they're blocked now, but we can have a look anyway:
blockport.sh -l
# The output will look like this:
# Listing INPUT Ports Blocked:
#
# Listing OUTPUT Ports Blocked:
# 1    DROP   udp  --  0.0.0.0/0   0.0.0.0/0   udp dpts:8000:8500
# 2    DROP   tcp  --  0.0.0.0/0   0.0.0.0/0   tcp dpts:8000:8500
# 3    DROP   udp  --  0.0.0.0/0   0.0.0.0/0   udp dpt:21
# 4    DROP   tcp  --  0.0.0.0/0   0.0.0.0/0   tcp dpt:21
# 5    DROP   udp  --  0.0.0.0/0   0.0.0.0/0   udp dpt:20
# 6    DROP   tcp  --  0.0.0.0/0   0.0.0.0/0   tcp dpt:20

# We can reverse this by typing:
blockport.sh -uo 20,21,8000-8500

# This is also valid syntax:
# blockport.sh -uo 20 -uo 21 -uo 8000-8500

Note: You can only unblock what you block. Here is an example of what I mean:

# Blocking a range:
blockport.sh -bo 8000-8500

# You CAN NOT just unblock a port from it (this will not work):
blockport.sh -uo 8400

# Similarly, you can not block individual ports and then try to unblock them
# as a range:
blockport.sh -bo 20,21

# You CAN NOT just unblock this as a range (this will not work):
blockport.sh -uo 20-21

Caution
This script is intended to be an instant port blocker. It intentionally destroys any pre-established connections utilizing the port marked. Keep this in mind so that you don’t block yourself out of your own Server or VPS. Hence DON’T CLOSE PORT 22 unless you know what and why you’re doing it. You have been warned! 🙂

Use Cases
Suppose you want to deny access out of a server you host for your company after hours, you could create a cron like this:

# Block defined outbound ports at around 5:30pm every evening on Weekdays
# (Mon - Fri)
30 17 * * 1-5 /root/bin/blockport.sh -bo 80,443,22,21 &>/dev/null

# Unblock the defined ports every morning at 7am on Weekdays
# (Mon - Fri) keeping them blocked over the weekends
0 7 * * 1-5 /root/bin/blockport.sh -uo 80,443,22,20,21 &>/dev/null

Alternatively, maybe something looked bad in /var/log/messages or /var/log/audit/audit.log to you and you simply just want to immediately block the port.

Credit

If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.

Permanently Ban Those Caught By Fail2Ban

Introduction

Fail2ban is probably one of the best intrusive detection based tools an administrator can deploy onto their system. This is especially the case if your system is connected to the internet. If you aren’t already using it; consider reading my blog entry here that talks about it.

In this blog, I provide a scripted solution that will capture the current list of banned users from Fail2Ban and make their ban permanent. This allows us to limit the constant emails one might receive about the same people trying to compromise our system(s). For those who aren’t using the emailing portion of Fail2Ban; this script still greatly takes the load off of Fail2Ban because it no longer has to manage the constant repeat offenders. Our logs are less cluttered too.

The script will address several things:

  1. It will handle multiple attacks from people within the same Class C type netmask.
  2. It will allow for the permanent bans to stick even after your system is rebooted. Unlike Fail2Ban’s list of blocked perpetrators, which is lost if the system (or iptables) is restarted.
  3. It will enforce the use of iptable’s DROP directive instead of REJECT. This is a slightly more secure approach in handling those who aren’t ever allowed to come back.
  4. It will support the fact that over time, you may want to add and remove from this new ban list I keep speaking of. Basically you can re-run the steps outlined in this blog again (and again) and not lose the addresses you’ve already blocked.
  5. The script maintains a global list of addresses in a simple delimited format. You can then choose to share this list with other systems (or colleagues) to block the same unwanted users on these systems too.

Script Dependencies and Requirements

For this script to work, you can virtually use any Linux/FreeBSD/Unix operating system as long as you’re also using fail2ban in conjunction with iptables.

The script makes use of sed, and gawk to massage the data. These tools are common an available to all operating systems. But not all of them necessarily have them installed by default. Make sure you’ve got them before proceeding:

# Fedora / CentOS 4,5,6 / RedHat 4,5,6
yum -i install gawk sed

# Ubuntu / Debian
apt-get install gawk sed

The Goods

Without further ado, the below code is documented quite heavily so that you can just copy the sections into your terminal screen as the systems root user. Don’t try the next section until you’re done with the previous.

Although this code works for me, I still must caution you all the same: I will not be held liable for any loss, damage or expense as a result of the actions taken by my script. I’ve only used it without problem with CentOS 6.x. That said, the simplicity of it should make it work with any other *nux based OS as well.

# Author: Chris Caron
# Date: Tue, Apr 7th, 2015
########################################
# Environment Variables
########################################
# YOU MUST CORRECTLY SET THESE OR THE REMAINING FUNCTIONS OF
# THIS CODE WILL NOT WORK CORRECTLY.
#
# THIS SCRIPT ASSUMES YOU ARE RUNNING AS 'root'
#
# Public Ethernet Device
# For my home system, i have this set to ppp0; You'll want to
# set this to the Ethernet device that harm can venture from.
# such as the internet.
PUBDEV=eth0

# The name of the iptables chain to manage the permanent bans
# within. The name doesn't matter so long that it doesn't
# collide with a chain you're already managing.
# Also, Do not change this value in the future because that
# will hinder the ability to upgrade/append new bans easily.
# It is doubtful that it's current set value will conflict
# with anything. Therefore just leave the name the way it is
# now:
FILTER=nuxref-perm-ban

# The script makes an effort to detect IP Addresses all
# coming from the same Class C type network.  Rather then
# have an entry per IP like fail2ban manages; we group
# them into 1 to speed the look-ups iptables preforms.
# You'll want to identify the minimum number of IP
# addresses matched within the same alike (Class C) network
# before this grouping takes place.
CLASSCGRP_MIN=2

# IP Tables configuration file read during your system
# startup.
IPTABLES_CFG=/etc/sysconfig/iptables

# IPTables Chain Index
# Identify where you want your ban list to be applied
# To reduce overhead, the banlist should be processed 'after'
# some core checks you do.  For example I have a series of other
# checks i do first such as allowing already established
# connections to pass through.  I didn't want the ban list
# being applied to these, so for my system, i set this to 11.
# Setting it to 1 is safe because it guarantees it's at least
# processed first on systems who don't actively maintain their
# own custom firewall list.
CHAINID=1

########################################
# Preparation
########################################

# First we built a massive sorted and unique list of
# already banned IP addresses.
# The below is clever enough to include previous content
# you've banned from before (if any exists):
(
   # carry over old master list
   iptables -L -n | awk "/Chain $FILTER/, $NF ~ /RETURN/" | 
    egrep DROP | sed -e 's/^[^0-9.]*([0-9]+.[0-9]+.[0-9]+).([0-9/]+).*/1 .2/g'
   # update master list with new data
   iptables -L -n | egrep REJECT | 
    sed -e 's/^[^0-9.]*([0-9]+.[0-9]+.[0-9]+).([0-9]+).*/1 .2/g'
) | sort -n | uniq > list
 
# Now we build a separate list (in memory) that will track
# all of the ip addresses that met our $CLASSCGRP_MIN flag.
CLASSC_LIST=$(cat list | cut -f1 -d' ' | uniq -c | 
    sed 's/^ *//g' | sort -n | 
    awk "int($1)>=$CLASSCGRP_MIN" | cut -f2 -d' ')
 
# We eliminate the duplicates already pulled into memory
# from the master list of IPs
for NW in $CLASSC_LIST; do sed -i -e "/^$NW /d" list; done

# Now we scan our list and repopulate them into our master
# list. We place these entries at the head of the file so
# that they'll be added to our iptable ban chain first.
for NW in $CLASSC_LIST; do sed -i "1s/^/$NW .0/24n/" list; done

# Using our list of banned IP addresses, we now generate
# the actual iptable entries (into a file called
# 'commands':
(
   # Creates the chain
   echo iptables -N $FILTER
 
   # Build List of Addresses using our list file
   while read line; do           
      IP=$(echo $line | tr -d '[:space:]')
      echo iptables -A $FILTER -s $IP -j DROP;
   done < list
 
   # Allow future iptable processing to continue 
   # after reading through this chain by appending
   # the RETURN action.
   echo iptables -A $FILTER -j RETURN
 
   # Add chain to INPUT
   echo iptables -t filter -I INPUT $CHAINID -i $PUBDEV -j $FILTER
) > commands

########################################
# IPTables (Temporary Instalment)
########################################

# Have a look at our commands if you want:
cat commands

# Apply these new rules now with the following command:
sh commands

# The commands generated in the 'commands' text file 
# are only temporary; they will be lost if your
# machine (or iptables) is ever restarted

########################################
# IPTables (Permanent Installation)
########################################
# Consider making a backup of your configuration in case you
# need to roll back
/bin/cp -f $IPTABLES_CFG $IPTABLES_CFG.backup

# Now we generate all of the commands needed to place
# into our iptables configuration file:
sed -e 's/^iptables[ ]*//g' commands | 
    egrep "^-A $FILTER -s " > commands-iptables
# Clean up old configuration
sed -i -e "/^:$FILTER -/d" "$IPTABLES_CFG"
sed -i -e "/^-A INPUT .* $FILTER$/d" "$IPTABLES_CFG"
sed -i -e "/^-A $FILTER -/d" "$IPTABLES_CFG"
 
# Now push all of our new ban entries into the iptables file
sed -i -e "/:OUTPUT .*/a:$FILTER - [0:0]" "$IPTABLES_CFG"
sed -i -e "/:$FILTER - .*/a-A INPUT -i $PUBDEV -j $FILTER" "$IPTABLES_CFG"
sed -i -e "/-A INPUT -i $PUBDEV -j $FILTER.*/r commands-iptables" "$IPTABLES_CFG"

# Now preform the following to reset all of the fail2ban
# jails you've got as well load your new permanent ban setup
service fail2ban stop
service iptables restart
service fail2ban start

# If you have a problem; just roll back your backup you
# created and rerun the 3 commands above again. You can
# have a look at the table with the following command:
iptables -L -n -v

########################################
# IPTables (Optional Tiding)
########################################
# the above will insert the banlist at the top
# The below will just correct this and move it
# clean entry(s) from INPUT
(
   while [ 1 -eq 1 ]; do
      ID=$(iptables -nL --line-numbers | 
           egrep "^[0-9]+[ t]+$FILTER " | 
           head -n 1 | 
           sed -e 's/^([0-9]+).*/1/g')
      [ -z "$ID" ] && break;
      iptables -D INPUT $ID
   done
   # Re insert at the correct $CHAINID
   iptables -t filter -I INPUT $CHAINID -i $PUBDEV -j $FILTER
)

# have another look if you want (if you tidied)
iptables -L -n -v

########################################
# Cleanup (Optional)
########################################
# Remove temporary files when your done; or save them if you
# want to port this data to another server:
rm -f list commands commands-iptables

You can undo and destroy the new entries an any time using the following:

# disassociate filter from INPUT
while [ 1 -eq 1 ]; do
   ID=$(iptables -nL --line-numbers | 
        egrep "^[0-9]+[ t]+$FILTER " | 
        head -n 1 | 
        sed -e 's/^([0-9]+).*/1/g')
   [ -z "$ID" ] && break;
   iptables -D INPUT $ID
done
# flush filter
iptables -F $FILTER
# remove filter
iptables -X $FILTER

Credit

This blog took some time to put together and test! If you like what you see and wish to copy and paste this HOWTO, please reference back to this blog post at the very least. It’s really all I ask.

Sources

There were not many sources used to make this entry. Most of it is just shell scripting knowledge I’ve adopted over the years mixed with some iptable commands. Here are some sources anyway that are applicable: