From some blogs I’ve written in the past you might already know that we are using AWS SSM to patch and maintain the complete EC2 and onprem instances at one of our customers. The previous posts about that topic are here:

While that in general is working fine and fully automated we ran into an issue lately which forced us to create our own CentOS 7 repositories and use them with SSM to apply the patches to the CentOS machines.

To describe the issue: We have two patch baselines per operating system. One for all development and test systems that applies all patches that are released up until the date the patch baseline is running. Then we have second one for the production systems with an approval delay of 14 days. As we run production patching 2 weeks after we patched the development and test systems that should guarantee that we get the same patches applied to production. And exactly here is the issue: “if a Linux repository doesn’t provide release date information for packages, Systems Manager uses the build time of the package as the auto-approval delay for Amazon Linux, Amazon Linux 2, RHEL, and CentOS. If the system isn’t able to find the build time of the package, Systems Manager treats the auto-approval delay as having a value of zero.”. That basically means: As you never know when CentOS will release their patches, which are based on the RedHat sources, you can never be sure that you get the same patches applied to production as they were applied 14 days before to development and test. Lets do an example: Our patching for development and test happened the 10th of April. The kernel package v3.10.0-1127 was released for CentOS on April 27th and was therefore not applied to the development and test systems. When production patching happened two weeks later that kernel package was available but also satisfied our auto approval rule of 14 days. So we basically had a patch installed on the production which never made it to the development and test systems. This is why we decided to go for our own repositories so we can decide when the repositories are synced.

Setting up a local yum repository is quite easy and you can find plenty of howtos in the internet, so here is just a summary without much explanation. We deployed a new CentOS 7 EC2 instance, then installed a webserver and the epel repository:

[centos@ip-10-47-99-158 ~]$ sudo yum install epel-release nginx -y
[centos@ip-10-47-99-158 ~]$ sudo systemctl start nginx
[centos@ip-10-47-99-158 ~]$ sudo systemctl enable nginx
[centos@ip-10-47-99-158 ~]$ sudo systemctl status nginx

As yum gets the packages over http or https adjust the firewall rules:

[centos@ip-10-47-99-158 ~]$ sudo systemctl start firewalld
[centos@ip-10-47-99-158 ~]$ sudo systemctl enable firewalld
[centos@ip-10-47-99-158 ~]$ sudo firewall-cmd --zone=public --permanent --add-service=http
[centos@ip-10-47-99-158 ~]$ sudo firewall-cmd --zone=public --permanent --add-service=https
[centos@ip-10-47-99-158 ~]$ sudo firewall-cmd --reload

Update the complete system and install the yum utilities and the createrepo packages:

[centos@ip-10-47-99-158 ~]$ sudo yum update -y
[centos@ip-10-47-99-158 ~]$ sudo yum install createrepo  yum-utils -y

Prepare the directory structure and synchronize the repositories:

[centos@ip-10-47-99-158 ~]$ sudo mkdir -p /var/www/html/repos
[centos@ip-10-47-99-158 ~]$ sudo chmod -R 755 /var/www/html/repos
[centos@ip-10-47-99-158 ~]$ sudo reposync -g -l -d -m --repoid=base --newest-only --download-metadata --download_path=/var/www/html/repos/centos-7/7/
[centos@ip-10-47-99-158 ~]$ sudo reposync -l -d -m --repoid=extras --newest-only --download-metadata --download_path=/var/www/html/repos/centos-7/7/
[centos@ip-10-47-99-158 ~]$ sudo reposync -l -d -m --repoid=updates --newest-only --download-metadata --download_path=/var/www/html/repos/centos-7/7/
[centos@ip-10-47-99-158 ~]$ sudo reposync -l -d -m --repoid=epel --newest-only --download-metadata --download_path=/var/www/html/repos/centos-7/7/

Create the repositories from what was synced above:

[centos@ip-10-47-99-158 ~]$ sudo createrepo /var/www/html/repos/centos-7/7/base
[centos@ip-10-47-99-158 ~]$ sudo createrepo /var/www/html/repos/centos-7/7/extras
[centos@ip-10-47-99-158 ~]$ sudo createrepo /var/www/html/repos/centos-7/7/updates
[centos@ip-10-47-99-158 ~]$ sudo createrepo /var/www/html/repos/centos-7/7/epel

… and set the selinux context:

[centos@ip-10-47-99-158 ~]$ sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/html/repos(/.*)?"
[centos@ip-10-47-99-158 ~]$ sudo restorecon -Rv /var/www/html/repos

Configure nginx to point to the repositories:

[centos@ip-10-47-99-158 ~]$ sudo vi /etc/nginx/conf.d/repos.conf 
## add the folling section
server {
        listen   80;
        server_name  10.47.99.158;	
        root   /var/www/html/repos/;
        location / {
                index  index.php index.html index.htm;
                autoindex on;	#enable listing of directory index
        }
}

… and restart the webserver:

[centos@ip-10-47-99-158 ~]$ sudo systemctl restart nginx

From now on you should see the directory structure when you point your browser to the IP of the EC2 instance:

To regularly synchronize the repositories depending on your requirements create a small script that does the job and schedule that with cron, e.g.:

#!/bin/bash
LOCAL_REPOS="base extras updates epel"
##a loop to update repos one at a time
for REPO in ${LOCAL_REPOS}; do
    if [ "$REPO" = "base" ]; then
        reposync -g -l -d -m --repoid=$REPO --newest-only --download-metadata --download_path=/var/www/html/repos/centos-7/7/
    else
        reposync -l -d -m --repoid=extras --newest-only --download-metadata --download_path=/var/www/html/repos/centos-7/7/
    fi
    createrepo /var/www/html/repos/centos-7/7/$REPO
    semanage fcontext -a -t httpd_sys_content_t "/var/www/html/repos(/.*)?"
    restorecon -Rv /var/www/html/repos
done

Test the repository from another CentOS 7 instance:

Using username "centos".
Authenticating with public key "imported-openssh-key"
[centos@ip-10-47-98-80 ~]$ sudo bash
[root@ip-10-47-98-80 centos]$ cd /etc/yum.repos.d/
[root@ip-10-47-98-80 yum.repos.d]$ ls
CentOS-Base.repo  CentOS-CR.repo  CentOS-Debuginfo.repo  CentOS-fasttrack.repo  CentOS-Media.repo  CentOS-Sources.repo  CentOS-Vault.repo
[root@ip-10-47-98-80 yum.repos.d]$ rm -f *
[root@ip-10-47-98-80 yum.repos.d]$ ls -la
total 12
drwxr-xr-x.  2 root root    6 Jun 25 06:39 .
drwxr-xr-x. 77 root root 8192 Jun 25 06:36 ..


[root@ip-10-47-98-80 yum.repos.d]$ cat local-centos.repo
[local]
name=CentOS Base
baseurl=http://10.47.99.158/centos-7/7/base/
gpgcheck=0
enabled=1

[extras]
name=CentOS Extras
baseurl=http://10.47.99.158/centos-7/7/extras/
gpgcheck=0
enabled=1

[updates]
name=CentOS Updates
baseurl=http://10.47.99.158/centos-7/7/updates/
gpgcheck=0

[epel]
name=CentOS Updates
baseurl=http://10.47.99.158/centos-7/7/epel/
gpgcheck=0
[root@ip-10-47-98-80 yum.repos.d]#


[root@ip-10-47-98-80 yum.repos.d]$ yum search wget
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
======================================================================= Name Exactly Matched: wget =======================================================================
wget.x86_64 : A utility for retrieving files using the HTTP or FTP protocols

  Name and summary matches only, use "search all" for everything.
[root@ip-10-47-98-80 yum.repos.d]#

… and you’re done from a repository perspective.

Now it is time to tell SSM to use the local repositories with your patch baseline. If you don’t know how SSM works or how you can apply patches using SSM check the previous post.

All you need to do is to adjust the patch baseline to include your repositories as “Patch sources”:

Schedule your patching and then check the logs. You should see that SSM is now using the local repositories:

...
u'sources': [{u'configuration': u'[local]\nname=CentOS Base\nbaseurl=http://10.47.99.158/centos-7/7/base/\ngpgcheck=0\nenabled=1', u'products': [u'*'], u'name': u'base'}, {u'configuration': u'[extras]\nname=CentOS Extras\nbaseurl=http://10.47.99.158/centos-7/7/extras/\ngpgcheck=0\nenabled=1', u'products': [u'*'], u'name': u'extras'}, {u'configuration': u'[updates]\nname=CentOS Updates\nbaseurl=http://10.47.99.158/centos-7/7/updates/\ngpgcheck=0', u'products': [u'*'], u'name': u'updates'}
...
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-Base.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-CR.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-Debuginfo.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-Media.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-Sources.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-Vault.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-fasttrack.repo
06/25/2020 10:00:28 root [INFO]: Moving file: CentOS-x86_64-kernel.repo
06/25/2020 10:00:28 root [INFO]: Executing lambda _create_custom_repos
06/25/2020 10:00:28 root [INFO]: Creating custom repo base
06/25/2020 10:00:28 root [INFO]: Creating custom repo extras
06/25/2020 10:00:28 root [INFO]: Creating custom repo updates
06/25/2020 10:00:28 root [INFO]: Creating custom repo epel
Loaded plugins: fastestmirror

That’s it. Doing it this way you have full control about which packages will be installed. The downside, of course, is, that you need to maintain your own copy of the repositories.