Last week, I published a blog about implementing a PXE server [1]. Today I’ll show how kickstart/anaconda files can be used to deploy a server. I will use the example of a DNS-server to explain what can be changed where in kickstart files and what you can do when the kickstart file isn’t enough for your purposes. The full kickstart file and an example menu for the deployment can be found both at the end of this blog and on my github repository [2].
1. Kickstart files
The best place to change settings is in the kickstart file itself: many checks will be done before the installation starts. The VM that we deployed in the previous blog was configured based on DHCP, it had the following line in it:
network –bootproto=dhcp –device=eth0 –ipv6=auto –activate
For a DNS, we should use a static DNS address, so we will change this into:
network –bootproto=static –ip=192.168.2.3 –gateway=192.168.2.254 –nameserver=192.168.2.254 –netmask=255.255.255.0 –ipv6=auto –device=eth0 –activate
The network directive is also the place to configure the name of the server:
network –hostname=dns
There is also room for specifying additional packages to the (in this case: minimal) base install. kexec-tools was already in the kickstart file, it is needed for the install itself. I added bind (for the DNS-server) and bind-utils (for utilities like dig, nslookup etc) myself:
%packages
@^minimal-environment
bind
bind-utils
kexec-tools
%end
2. The post-install part of kickstart
There is a part in the kickstart file that by default isn’t there. I’m talking about the post install part, you can use this part to add or change files after the installation, but before the reboot. This part starts with %post and ends with %end.
For the DNS-server, there should be changes in the configuration file /etc/named.conf. After the installation of the DNS-server, the DNS-server will by default respond only to connections from its own server (localhost). To be useful for other servers in the network this should be changed to the network the DNS server is running on. Two lines must be changed: the line where we listen on port 53 must be changed from 127.0.0.1 to the network address of the DNS-server, in my case 192.168.2.3 (“listen-on port 53 { 192.168.2.3;};”), and the line where we allow queries from our network must be changed from localhost to 192.168.2.0/24 (every node in our network: “allow-query { 192.168.2.0/24;};”).
The configuration file should also contain our own (local) domain mydomain.org, where we can add some DNS-entries. After the line
zone "." IN { type "hints"; };
the following text should be added:
zone "mydomain.org" IN { type master; file "mydomain.org"; allow-update { none; } ; };
The “allow-update { none;};” will take care that the domain cannot be changed by other nodes, the only way to change the content of this zone is to change the configuration file mydomain.org.
After that, the file mydomain.org will be created in the /var/named directory, with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $TTL 86400 $ORIGIN mydomain.org. @ IN SOA dns.mydomain.org. info.mydomain.org ( 20200214 3600 1800 604800 86400) @ IN NS dns.mydomain.org. dns IN A 192.168.2.3 server1 IN A 192.168.2.11 server2 IN A 192.168.2.12 |
Now, the only thing left to do is to change the firewall. When we would configure a DNS server by hand, we would simply use the following commands on the command line:
firewall-cmd –add-port=53/tcp
firewall-cmd –add-port=53/tcp –permanent
firewall-cmd –add-port=53/udp
firewall-cmd –add-port=53/udp –permanent
The problem is, however, that when we would use this in the kickstart file, the changes would be made on the installation environment, not on the destination host. So we have to come up with a trick to do these changes directly after the reboot of the server.
3. Start up script
We can do that by creating a start up script that will only run once, after the first start of the server. The script should run after the firewall is started. We will create a “oneshot service” in systemd, which removes itself after execution. Anaconda should create a file /etc/systemd/system/dnsconf.service with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [Unit] Description=Configure selinux for named After=firewalld.service [Service] Type=oneshot ExecStart=/bin/firewall-cmd --add-port=53/tcp ExecStart=/bin/firewall-cmd --add-port=53/tcp --permanent ExecStart=/bin/firewall-cmd --add-port=53/udp ExecStart=/bin/firewall-cmd --add-port=53/udp --permanent ExecStart=rm -f /etc/systemd/system/dnsconf.service ExecStart=rm -f /etc/systemd/system/multi-user.target.wants/dnsconf.service [Install] WantedBy=multi-user.target |
To let this work, there should be a softlink in /etc/systemd/system/multi-user.target.wants. Normally, you wouldn’t create this softlink yourself, it is placed there by the command systemctl enable dnsconf. But because systemd is not running on the target system when you create these files, Anaconda will have to put the softlink there for you. The creation of the file /etc/systemd/system/dnsconf.service and the creation of the softlink is done in the %post part of the Anaconda file. The same is true for the DNS service itself:
ln -s /etc/systemd/system/dnsconf.service /etc/systemd/system/multi-user.target.wants/dnsconf.service ln -s /etc/systemd/system/named.service /etc/systemd/system/multi-user.target.wants/named.service
4. Conclusion
The nice thing about this solution is that you can deploy an OS and configure it over the network. Even without logging on to the server, this solution will work as soon as you see the login prompt: you can use dig @192.168.2.3 server1.mydomain.org from another server and you will see the address 192.168.2.11 as a result.
For a small organization with few changes in their DNS configuration, it might be considered not to allow users on this new machine at all (not even an administrator) for security reasons: in that case, a userdel command can be added in the %post installation part of Anaconda to delete the (in this example) frederique user. When there should be changes in the DNS environment, the changes are made on the kickstart file on the PXE server and the DNS server is redeployed with PXE after that.
Footnotes
[1] https://technology.amis.nl/2020/02/08/deploying-centos-8-using-pxe/
[2] https://github.com/FrederiqueRetsema/AMIS-Blog-PXE, in the dns directory
Content of the file /etc/lib/tftpboot/pxelinux.cfg/C0A802:
1 2 3 4 5 6 7 | DEFAULT menu.c32 PROMPT 0 TIMEOUT 30 LABEL CentOS 8 dns MENU centos8dns KERNEL /networkboot/vmlinuz APPEND initrd=/networkboot/initrd.ing inst.repo=ftp://192.168.2.131/CentOS8 ks=ftp://192.168.2.131/centos8dns.cfg |
Content of /var/ftp/centos8dns.cfg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #version=RHEL8 ignoredisk --only-use=sda autopart --type=lvm # Partition clearing information clearpart --all zerombr # Use graphical install graphical repo --name=centos-updates --mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=BaseOS --cost=1000 repo --name=appstream-updates --mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=AppStream --cost=1000 repo --name=extras-updates --mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=Extras --cost=1000 # Use FTP installation media url --url=ftp://192.168.2.154/CentOS8 # Keyboard layouts keyboard --vckeymap=us --xlayouts='us' # System language lang en_US.UTF-8 # Network information network --bootproto=static --ip=192.168.2.3 --gateway=192.168.2.254 --nameserver=192.168.2.254 --netmask=255.255.255.0 --ipv6=auto --device=eth0 --activate network --hostname=dns.mydomain.org #Root password rootpw --lock # Run the Setup Agent on first boot firstboot --enable # Do not configure the X Window System skipx # System services services --enabled="chronyd" # System timezone timezone Europe/Amsterdam --isUtc user --groups=wheel --name=frederique --password=$6$ISxpNV3kE7gfvlpi$RRWVusy/EiatEuCEIdYhR.R1PdT6KBt2xuaPGaKdZRx93RB5bua4QEEh4aQNmTIpVLcz.4YXaVvk8brKlwslf/ --iscrypted --gecos="Frederique" %packages @^minimal-environment bind bind-utils kexec-tools %end %addon com_redhat_kdump --enable --reserve-mb='auto' %end %post sed -i 's/127.0.0.1/192.168.2.3/g' /etc/named.conf sed -i '/allow-query/c \ \ \ \ \ \ \ \ allow-query { 192.168.2.0/24;};' /etc/named.conf ZONELINENO=`grep -rne "zone \".\" IN {" /etc/named.conf | awk -F":" '{print $1}'` let ZONELINENO=ZONELINENO+3 echo "" > /tmp/mydomain.org echo "zone \"mydomain.org\" IN {" >> /tmp/mydomain.org echo " type master;" >> /tmp/mydomain.org echo " file \"mydomain.org\";" >> /tmp/mydomain.org echo " allow-update{ none; };" >> /tmp/mydomain.org echo "};" >> /tmp/mydomain.org sed -i "${ZONELINENO} r/tmp/mydomain.org" /etc/named.conf echo "\$TTL 86400" > /var/named/mydomain.org echo "\$ORIGIN mydomain.org." >> /var/named/mydomain.org echo "" >> /var/named/mydomain.org echo "@ IN SOA dns.mydomain.org. info.mydomain.org. (" >> /var/named/mydomain.org echo " 2020021200" >> /var/named/mydomain.org echo " 3600" >> /var/named/mydomain.org echo " 1800" >> /var/named/mydomain.org echo " 604800" >> /var/named/mydomain.org echo " 86400)" >> /var/named/mydomain.org echo "@ IN NS dns.mydomain.org." >> /var/named/mydomain.org echo "dns IN A 192.168.2.3" >> /var/named/mydomain.org echo "server1 IN A 192.168.2.11" >> /var/named/mydomain.org echo "server2 IN A 192.168.2.12" >> /var/named/mydomain.org echo "[Unit]" > /etc/systemd/system/dnsconf.service echo "Description=Configure selinux for named" >> /etc/systemd/system/dnsconf.service echo "After=firewalld.service" >> /etc/systemd/system/dnsconf.service echo "" >> /etc/systemd/system/dnsconf.service echo "[Service]" >> /etc/systemd/system/dnsconf.service echo "Type=oneshot" >> /etc/systemd/system/dnsconf.service echo "ExecStart=/bin/firewall-cmd --add-port=53/tcp" >> /etc/systemd/system/dnsconf.service echo "ExecStart=/bin/firewall-cmd --add-port=53/tcp --permanent" >> /etc/systemd/system/dnsconf.service echo "ExecStart=/bin/firewall-cmd --add-port=53/udp" >> /etc/systemd/system/dnsconf.service echo "ExecStart=/bin/firewall-cmd --add-port=53/udp --permanent" >> /etc/systemd/system/dnsconf.service echo "ExecStart=rm -f /etc/systemd/system/dnsconf.service" >> /etc/systemd/system/dnsconf.service echo "ExecStart=rm -f /etc/systemd/system/multi-user.target.wants/dnsconf.service" >> /etc/systemd/system/dnsconf.service echo "" >> /etc/systemd/system/dnsconf.service echo "[Install]" >> /etc/systemd/system/dnsconf.service echo "WantedBy=multi-user.target" >> /etc/systemd/system/dnsconf.service ln -s /etc/systemd/system/dnsconf.service /etc/systemd/system/multi-user.target.wants/dnsconf.service ln -s /etc/systemd/system/named.service /etc/systemd/system/multi-user.target.wants/named.service sync %end %anaconda pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty %end |