At this point you can install a client unattended. The client will boot but you can't yet control which services will be running on that client. In this section we will configure services which we will use to configure the machine after it has been installed.
Some admins put a lot of effort into their kickstart files, having everything configured in the post section. In our farm we'll keep the kickstart fairly minimal and use other services such as puppet, augeas and func to configure the machine. By doing things this way, we ensure that we can keep systems configured without reinstalling them.
In order to install these services, we need to make our own yum repository that we can invoke at install time. We will do this in the next section.
making your own repo, a comps.xml a group, using yum groupinstall make your own packages. sign them, key signing. about making packages in an appendix using yum, automatic for workstations, you control the repos.
[root@server0 ~]# useradd signer
[root@server0 ~]# passwd signer
Changing password for user signer.
New UNIX password:
Retype new UNIX password:
passwd: all authentication tokens updated successfully.
[root@server0 ~]# su - signer
[signer@server0 ~]$ gpg --gen-key
gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
Please select what kind of key you want:
(1) DSA and Elgamal (default)
(2) DSA (sign only)
(5) RSA (sign only)
Your selection? 5
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
0 = key does not expire
= key expires in n days
w = key expires in n weeks
m = key expires in n months
y = key expires in n years
Key is valid for? (0) 10y
Key expires at Sun 21 Jul 2019 04:32:53 PM EDT
Is this correct? (y/N) y
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) "
Real name: Repository Signer
Email address: signer@example.com
Comment: Example Com
You selected this USER-ID:
"Repository Signer (Example Com) "
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
...+++++
..........+++++
gpg: key 44CB93FD marked as ultimately trusted
public and secret key created and signed.
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2019-07-21
pub 2048R/44CB93FD 2009-07-23 [expires: 2019-07-21]
Key fingerprint = BD7F E3E1 3403 3F35 5DA7 C8AF CC08 B6BA 44CB 93FD
uid Repository Signer (Example Com)
Note that this key cannot be used for encryption. You may want to use
the command "--edit-key" to generate a subkey for this purpose.
Next, export the newly created key to a file.
[signer@server0 ~]$ gpg --export -a "Repository Signer (Example Com)Now, import the key into the rpm database to verify everything is working correctly." >RPM-GPG-KEY-example.com
[root@server0 install]# rpm -q gpg-pubkey-* gpg-pubkey-f51839ac-46362566 gpg-pubkey-b2980b13-3c1d0597 [root@server0 install]# rpm --import ~signer/RPM-GPG-KEY-example.com [root@server0 install]# rpm -q gpg-pubkey-* gpg-pubkey-f51839ac-46362566 gpg-pubkey-b2980b13-3c1d0597 gpg-pubkey-44cb93fd-4a68c9c4
| puppet puppet-server facter ruby-shadow |
augeas augeas-libs ruby-augeas |
func certmaster smolt python-ctypes python-paste python-simplejson |
Now, you'll need to either download the src rpm or binary rpms for the packages you wish to redistribute to your clients (Make sure you can legally redistribute these rpms). For our purposes we will need the func, puppet, facter and augeas rpms. We will download them from the epel repository. Using lftp is one of the quickest ways to find all the right files and download them. First we'll setup the directories for our repository, then we'll start downloading.
[root@server0 ~]# cd /var/www/html/install [root@server0 install]# mkdir -p Local/SRPMS Local/i386 Local/x86_64 [root@server0 install]# cd Local [root@server0 Local]# lftp http://download.fedora.redhat.com/pub/epel/5/x86_64/ cd ok, cwd=/pub/epel/5/x86_64 lftp download.fedora.redhat.com:/pub/epel/5/x86_64> get puppet-0.24.8-1.el5.1.noarch.rpm 554952 bytes transferred lftp download.fedora.redhat.com:/pub/epel/5/x86_64> get puppet-server-0.24.8-1.el5.1.noarch.rpm 26918 bytes transferred lftp download.fedora.redhat.com:/pub/epel/5/x86_64> get func-0.24-1.el5.noarch.rpm 282228 bytes transferred ... lftp download.fedora.redhat.com:/pub/epel/5/x86_64> quit [root@server0 Local]# ls augeas-0.5.1-1.el5.x86_64.rpm puppet-server-0.24.8-1.el5.1.noarch.rpm augeas-libs-0.5.1-1.el5.x86_64.rpm ruby-augeas-0.2.0-1.el5.x86_64.rpm ... python-simplejson-2.0.3-2.el5.x86_64.rpm
At this point we have our rpms downloaded, we need to place them in the appropriate directories, and also sign them (they are signed by the epel repo at this point). We'll sign them first, this requires giving the signer user permission to write in the Local directory and setting the .rpmmacros file in signers home directory to use the correct signing key.
[root@server0 Local]# rpm -K augeas-0.5.1-1.el5.x86_64.rpm augeas-0.5.1-1.el5.x86_64.rpm: (SHA1) DSA sha1 md5 (GPG) NOT OK (MISSING KEYS: GPG#217521f6) [root@server0 Local]# chown -R signer:signer . [root@server0 Local]# su - signer [signer@server0 ~]$ gpg --list-keys /home/signer/.gnupg/pubring.gpg ------------------------------- pub 2048R/44CB93FD 2009-07-23 [expires: 2019-07-21] uid Repository Signer (Example Com)[signer@server0 ~]$ cat < .rpmmacros > %_signature gpg > %_gpg_name Repository Signer (Example Com) [signer@server0 ~]$ cd /var/www/html/install/Local [signer@server0 Local]$ rpm --resign *.rpm Enter pass phrase: Pass phrase is good. augeas-0.5.1-1.el5.x86_64.rpm: augeas-libs-0.5.1-1.el5.x86_64.rpm: facter-1.5.5-1.el5.noarch.rpm: ... [signer@server0 Local]$ rpm -K augeas-0.5.1-1.el5.x86_64.rpm augeas-0.5.1-1.el5.x86_64.rpm: rsa sha1 (md5) pgp md5 OK> EOF
Now that the rpms are signed by our key, we can move the rpms to the correct directories. If disk space it not at a premium, just copy the noarch rpms into both the i386 and the x86_64 directories. If you are concerned about usage (like me), hard link the noarch file in i386 to the one in x86_64 (they are the same file anyway).
[signer@server0 Local]$ mv *x86_64.rpm x86_64 [signer@server0 Local]$ for file in *noarch.rpm > do > mv $file x86_64 > ln x86_64/$file i386 > done
<comps>
<!-- <meta> -->
<!-- Meta information will go here eventually -->
<!-- </meta> -->
<group>
<id>localclient</id>
<name>Local Client</name>
<default>true</default>
<description>Default RPMS from Local Clients</description>
<uservisible>true</uservisible>
<packagelist>
<packagereq type="default">puppet</packagereq>
<packagereq type="default">ruby-augeas</packagereq>
<packagereq type="default">facter</packagereq>
<packagereq type="default">func</packagereq>
</packagelist>
</group>
</comps>
[root@server0 install]# yum -y install createrepo Setting up Install Process Parsing package install arguments Resolving Dependencies --> Running transaction check ---> Package createrepo.noarch 0:0.4.4-2.fc6 set to be updated --> Finished Dependency Resolution Dependencies Resolved ======================================================================================================== Package Arch Version Repository Size ======================================================================================================== Installing: createrepo noarch 0.4.4-2.fc6 Install_Server_Client 37 k Transaction Summary ======================================================================================================== Install 1 Package(s) Update 0 Package(s) Remove 0 Package(s) Total download size: 37 k Downloading Packages: Running rpm_check_debug Running Transaction Test Finished Transaction Test Transaction Test Succeeded Running Transaction Installing : createrepo [1/1] Installed: createrepo.noarch 0:0.4.4-2.fc6 Complete! [root@server0 install]# cd Local [root@server0 Local]# mv comps.xml x86_64 [root@server0 Local]# ln x86_64/comps.xml i386 [root@server0 Local]# createrepo -g comps.xml x86_64 9/9 - augeas-0.5.1-1.el5.x86_64.rpm Saving Primary metadata Saving file lists metadata Saving other metadata [root@server0 Local]# createrepo -g comps.xml i386 5/5 - func-0.24-1.el5.noarch.rpm Saving Primary metadata Saving file lists metadata Saving other metadataNow that our repo files are in place, we need to create an rpm for our repository so we can install it with kickstart or we can install a repo file and add the key to rpm using kickstart. Creating an rpm is probably cleaner. If you want to test this repo at this point, you can add the following file to /etc/yum.repos.d, and copy the RPM-GPG-KEY-example.com that we created earlier into /etc/pki/rpm-gpg (actually, since we already imported the key with rpm --import, yum shouldn't complain about keys at this point, but copying the file is a good idea).
[root@server0 yum.repos.d]# cp ~signer/RPM-GPG-KEY-example.com /etc/pki/rpm-gpg/ [root@server0 yum.repos.d]# cat local.repo [Local] name=Local RPMS $releasever - $basearch baseurl=file:///var/www/html/install/Local/$basearch gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-example.com enabled=1 [root@server0 yum.repos.d]# yum search augeas Local | 1.1 kB 00:00 primary.xml.gz | 2.8 kB 00:00 Local 7/7 =========================================== Matched: augeas ============================================ augeas.x86_64 : A library for changing configuration files augeas-libs.x86_64 : Libraries for augeas ruby-augeas.x86_64 : Ruby bindings for Augeas [root@server0 yum.repos.d]# yum grouplist |grep Client Local Client
A somewhat better way to do it is to make a repository rpm that install the file and the key. To make such an rpm, create a working directory in signer's home directory
[root@server0 ~]# yum -y install rpm-build
[root@server0 ~]# su - signer
[signer@server0 ~]$ mkdir -p src/RPMS src/SPECS src/BUILD src/SRPMS
[signer@server0 ~]$ echo "%_topdir /home/signer/src" >> ~/.rpmmacros
[signer@server0 ~]$ cd src/SPECS
[signer@server0 SPECS]$ cat Example.com-local.spec
Summary: yum Local repository
Name: Example.com-Local
Version: 1
Release: 1
Group: System Environment/Base
License: GPL
BuildRoot: %{_tmppath}/%{name}-root
BuildArch: noarch
%description
This rpm contains the yum Example.com Local repository
%prep
%build
%install
mkdir -p $RPM_BUILD_ROOT/etc/yum.repos.d/
cat > $RPM_BUILD_ROOT/etc/yum.repos.d/local-%{version}-local.repo < $RPM_BUILD_ROOT/etc/pki/rpm-gpg/RPM-GPG-KEY-example.com <
- initial release
[signer@server0 SPECS]$ rpmbuild -ba Example.com-local.spec
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ mkdir -p /var/tmp/Example.com-Local-root/etc/yum.repos.d/
+ cat
+ mkdir -p /var/tmp/Example.com-Local-root/etc/pki/rpm-gpg/
+ cat
+ exit 0
Processing files: Example.com-Local-1-1
Requires(interp): /bin/sh /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(post): /bin/sh
Requires(postun): /bin/sh
Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/tmp/Example.com-Local-root
Wrote: /home/signer/src/SRPMS/Example.com-Local-1-1.src.rpm
Wrote: /home/signer/src/RPMS/noarch/Example.com-Local-1-1.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.10406
+ umask 022
+ cd /home/signer/src/BUILD
+ rm -rf /var/tmp/Example.com-Local-root
+ exit 0
[signer@server0 SPECS]$ cd ../
[signer@server0 src]$ cp RPMS/noarch/Example.com-Local-1-1.noarch.rpm SRPMS/Example.com-Local-1-1.src.rpm /var/www/html/install/Local
Now we need to move the rpms into the correct directories and rerun createrepo. We should script this at this point. Here is a simple script (modified from the one we use) that checks if rpms are signed and then links them into the appropriate directory. By linking first, we can make multiple links and then remove the copy in the current directory.
[signer@server0 Local]$ mkdir ~/bin
[signer@server0 Local]$ cat ~/bin/update_repo
#!/bin/sh
COMPS=comps.xml
# determine the architecture of the rpm (noarch x86_64 i386...)
rpm_arch() {
echo $1 | awk -F'.' '{NF=NF-1; print $NF}'
}
# build a list of rpms to move
for i in $@ *.rpm; do
if [ -f "$i" ]; then
case $i in
*rpm)
if rpm -K $i | awk '/pgp/ && /OK/ && !/NOT OK/' &>/dev/null; then
RPMLIST="$RPMLIST $i"
else
echo "ERROR: rpm $i is NOT SIGNED"
exit 1
fi
;;
*)
echo "ERROR: $i is not an rpm"
exit 1
;;
esac
else
if [ "XXX$i" != "XXX*.rpm" ]; then
echo "ERROR: $i is not a file"
exit 1
fi
fi
done
echo $RPMLIST
if [ -d i386 -a -d x86_64 -a -d SRPMS ]; then
for i in $RPMLIST; do
ARCH=`rpm_arch $i`
case $ARCH in
src)
ARCH=SRPMS
;;
i386|i486|i586|i686)
ARCH=i386
;;
x86_64)
;;
noarch)
ARCH="i386 x86_64"
;;
*)
ARCH=unknown
echo "$i unknown architecture"
;;
esac
ERROR=""
if [ "$ARCH" != "unknown" ]; then
for DESTARCH in $ARCH
do
if [ -e $DESTARCH/$i ]; then
echo "$i already exists in $DESTARCH"
ERROR=1
else
echo "linking $i into $DESTARCH"
ln $i $DESTARCH
fi
done
if [ -z "$ERROR" ]; then
# linking was successful, remove file
rm -f $i
else
echo "ERROR: could not link $i"
fi
fi
done
echo "Running createrepo now"
for ARCH in i386 x86_64
do
createrepo -g $COMPS $ARCH
done
else
echo "ERROR: required directories not found (i386 x86_64 SRPMS)"
fi
[signer@server0 Local]$ chmod 755 ~/bin/update_repo
[signer@server0 Local]$ ~/bin/update_repo
ERROR: rpm Example.com-Local-1-1.noarch.rpm is NOT SIGNED
We forgot to sign the rpms we just built. Sign them now.
[signer@server0 Local]$ rpm --addsign *rpm Enter pass phrase: Pass phrase is good. Example.com-Local-1-1.noarch.rpm: gpg: WARNING: standard input reopened gpg: WARNING: standard input reopened Example.com-Local-1-1.src.rpm: gpg: WARNING: standard input reopened gpg: WARNING: standard input reopenedNow try that update again
[signer@server0 Local]$ ~/bin/update_repo Example.com-Local-1-1.noarch.rpm Example.com-Local-1-1.src.rpm linking Example.com-Local-1-1.noarch.rpm into i386 linking Example.com-Local-1-1.noarch.rpm into x86_64 linking Example.com-Local-1-1.src.rpm into SRPMS Running createrepo now 4/4 - func-0.24-1.el5.noarch.rpm Saving Primary metadata Saving file lists metadata Saving other metadata 8/8 - augeas-0.5.1-1.el5.x86_64.rpm Saving Primary metadata Saving file lists metadata Saving other metadataNow we have our rpm signed and in our repo and can install it at install time using kickstart.
repo --name=Local --baseurl=http://server0.example.com/install/Local/x86_64We will want our group to be installed as well as our repository rpm, here is how we do that.
@localclientNow after installation our client will have the following packages installed from our repository
[root@client15 ~]# rpm -q puppet ruby-augeas func puppet-0.24.8-1.el5.1 ruby-augeas-0.2.0-1.el5 func-0.24-1.el5Now that we have our extra rpms installed, we can move on to configuring puppet.
puppet is made by Reductive Labs and released under the GNU Public License. It is a system to manage configuration files and services. It is written in ruby.
There are several products out there that do the same job as puppet, the main competitor is Cfengine. There is also the open source version of Red Hat satellite server spacewalk. All these systems have their various benefits and pitfalls. I find puppet to be simpler to configure and very easy to extend. One possible pitfall is that it is written in Ruby, and I have almost no experience in ruby. That said, it's a very simple language to read to quite easy to follow (unlike PERL).
To configure puppet, you install the puppet-server rpm or build from source. The rpms are available for centos, RHEL (via EPEL) and Fedora. In the previous section we downloaded the puppet rpms from EPEL and installed them in our local repository.
The steps to configure puppet are
[root@server0 ~]# chkconfig puppetmaster on [root@server0 ~]# cd /etc/puppet [root@server0 puppet]# ls fileserver.conf manifests puppet.conf [root@server0 puppet]# ls manifests [root@server0 puppet]#Both the server and client versions use the /etc/puppet directory. Both are configured from puppet.conf. The default configuration of puppet.conf will be sufficient at this point, but the manifests directory is completely empty. This is possibly the biggest complain with puppet, you start with a blank slate. It is up to you to populate the manifest directory. The only file you will need is site.pp in that directory. The site.pp file names all the other files you wish to include in your manifest.
The puppetmaster listens on port 8140, so we'll need to add that port to our firewall
[root@server0 ~]# iptables -I RH-Firewall-1-INPUT -p tcp -m state --state NEW,ESTABLISHED,RELATED --dport 8140 -j ACCEPT [root@server0 ~]# iptables-save >/etc/sysconfig/iptablesWe cannot start the puppetmaster yet, we have an empty manifests directory, we'll start building the contents of that directory now.
There are many types predefined, for a complete list of all types and their usage, see the documentation. Common types used are:
To create a new class, use the class keyword. As a simple example, we'll create a class which creates a file in root's home directory called hello, with the word "world" in it.
class test {
file {"/root/hello":
content => "world",
mode => 644,
owner => root,
group => root
}
}
To have this class applied to a node, we need to assign the class to the node with the node and include keywords.
node client15 {
include test
}
Put these definitions in site.pp and start puppetmaster.
[root@server0 manifests]# cat site.pp
class test {
file {"/root/hello":
content => "world",
mode => 644,
owner => root,
group => root
}
}
node client15 {
include test
}
[root@server0 manifests]# service puppetmaster start
Starting puppetmaster: [ OK ]
Now that we have a minimal site.pp installed, we can configure our client and test the configuration. We'll configure clients from kickstart later, but to test the install at this point, login to client15 and execute puppet manually.
[root@client15 ~]# puppetd --no-daemonize --server server0.example.com --test --no-splay info: Creating a new certificate request for client15.example.com info: Creating a new SSL key at /var/lib/puppet/ssl/private_keys/client15.example.com.pem warning: peer certificate won't be verified in this SSL session notice: Did not receive certificate notice: Set to run 'one time'; exiting with no certificateOur client successfully connected to the puppetmaster but it failed to retrieve a catalog (what puppet calls the collection of actions to perform on the client). This is because our ssl key was not signed by the puppetmaster. Back on the puppetmaster, we'll sign the key for client15 and then try our test again.
[root@server0 ssl]# puppetca --list client15.example.com [root@server0 ssl]# puppetca --sign client15.example.com Signed client15.example.comBack on client 15
[root@client15 ~]# puppetd --no-daemonize --server server0.example.com --test --no-splay
warning: peer certificate won't be verified in this SSL session
notice: Got signed certificate
info: Caching catalog at /var/lib/puppet/localconfig.yaml
notice: Starting catalog run
notice: //Node[client15]/test/File[/root/hello]/content: defined 'content' as '{md5}7d793037a0760186574b0282f2f435e7'
notice: //Node[client15]/test/File[/root/hello]/owner: defined 'owner' as 'root'
notice: //Node[client15]/test/File[/root/hello]/group: defined 'group' as 'root'
notice: //Node[client15]/test/File[/root/hello]/mode: defined 'mode' as '644'
info: Creating state file /var/lib/puppet/state/state.yaml
notice: Finished catalog run in 0.03 seconds
[root@client15 ~]# cat /root/hello
world[root@client15 ~]#
Now that we have verified that puppet is working, we'll make a proper site.pp file and some useful classes.
# site.pp import "functions.pp" # define nodes import "nodes.pp" # define classes import "base.pp"
define remotefile($owner = root, $group= root, $mode, $server = "server0.example.com", $cls="base", $backup = false, $recurse = false) {
file {
$name:
mode => $mode,
owner => $owner,
group => $group,
backup => $backup,
source => "puppet://$server/$cls/$name"
}
}
The name of the function is remotefile, since we imported functions.pp in our site.pp, we can use remotefile in any other file we include from site.pp. The file type in puppet can have a remote location that is given in the source option. Our function defines a default location of puppet://server0.example.com/base/$name where name is the name of the file. We also set the owner and group to root by default. Without using this function, copying an /etc/resolv.conf file from our puppetmaster to a client would look like this:
file {"/etc/resolv.conf":
mode => 644,
owner => root,
group => root,
backup => false
source => "puppet://server0.example.com/base/etc/resolv.conf"
}
Using the function we can slim this down to:
remotefile { "/etc/resolv.conf": mode=> 644 }
Next we will define our nodes and a default class for puppetmaster to apply to the nodes.
node default {
include base
}
With this defined, we'll create another manifest for the class base.
[root@server0 manifests]# su - signer [signer@server0 ~]$ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/home/signer/.ssh/id_rsa): Created directory '/home/signer/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/signer/.ssh/id_rsa. Your public key has been saved in /home/signer/.ssh/id_rsa.pub. The key fingerprint is: 35:b7:84:6d:34:8c:76:9a:8d:7c:3e:4a:e8:c1:1e:fd signer@server0.example.com [signer@server0 ~]$ cat .ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr9rnu0jbSPipuZI2umz/v73jeRTjxlX9D7cHSFIaJUUShFSelFUfojkjl4ri4m4qc40icArMa4NMGZ9d3y+ZqMqeIPZVtJKEqkn2E9GJS36N13H75DwVPv4KE2oLR9Zk4T8HovLr50tWbJr5/G6VfwwybR3q6HdJSO7liAKmrJwFokev1fsmiZQX+rADL8XB+gZ/9FsFIi4F4YKsLGQz78CSf/jZ71qNC5Y4HniVQDv6RmZp+koHT6hOPKTuUD/VOWXHxoLc9c6ypkeSMaINvNHDvmUsbp+rNppiZPKnsDFoh3fL4h5pFKJ1DAYjOdnhLKJgwLzmBq7qfYpd/PEw2Q== signer@server0.example.com [signer@server0 ~]$ exit [root@server0 manifests]# cat base.pp class base { remotefile { "/etc/sysconfig/puppet": mode => 644 } service { puppet: ensure => true, enable => true, hasrestart => true } ssh_authorized_key { "signer": ensure => present, type => "ssh-rsa", key => "AAAAB3NzaC1yc2EAAAABIwAAAQEAr9rnu0jbSPipuZI2umz/v73jeRTjxlX9D7cHSFIaJUUShFSelFUfojkjl4ri4m4qc40icArMa4NMGZ9d3y+ZqMqeIPZVtJKEqkn2E9GJS36N13H75DwVPv4KE2oLR9Zk4T8HovLr50tWbJr5/G6VfwwybR3q6HdJSO7liAKmrJwFokev1fsmiZQX+rADL8XB+gZ/9FsFIi4F4YKsLGQz78CSf/jZ71qNC5Y4HniVQDv6RmZp+koHT6hOPKTuUD/VOWXHxoLc9c6ypkeSMaINvNHDvmUsbp+rNppiZPKnsDFoh3fL4h5pFKJ1DAYjOdnhLKJgwLzmBq7qfYpd/PEw2Q==", name => "signer@example.com", target => "/root/.ssh/authorized_keys" } } [root@server0 manifests]#Now we have to create /etc/sysconfig/puppet that we referenced in our call to remotefile.
[root@server0 puppet]# pushd /var/lib/puppet/files /var/lib/puppet/files /etc/puppet [root@server0 puppet]# mkdir -p files/base/etc/sysconfig [root@server0 puppet]# mkdir facts [root@server0 puppet]# cd files/base/etc/sysconfig [root@server0 sysconfig]# cat <Next, we'll configure the puppet fileserver to serve out files that are stored in /var/lib/puppet/files/basepuppet > PUPPET_SERVER=server0.example.com > PUPPET_EXTRA_OPTS=--factsync > EOF [root@server0 files]# popd /etc/puppet
[root@server0 puppet]# cat fileserver.conf [base] path /var/lib/puppet/files/base allow *.example.com allow 192.168.0.0/24 [facts] path /var/lib/puppet/facts allow *.example.com allow 192.168.0.0/24Now we have defined the base and facts shares. The facts share is used by facter, something we'll talk about later, but we might as well configure it's file share while we are here. At this point we can restart the puppetmaster, but when new clients connect, they have to wait for us to manually sign their keys. We'll configure puppet to automatically sign keys using autosign.conf
[root@server0 puppet]# cat autosign.conf *.example.com 192.168.0.0/24
At this point new clients can connect and (provided they are in example.com or our subnet) they will have their keys automatically signed. They will all be placed in the base class by default. Our next step is to go back to our kickstart and have puppet configured with kickstart.
%post chvt 3 echo "executing post install" echo hostname for puppet is $HOSTNAME puppetd --fqdn=$HOSTNAME --test --no-splay --server=server0.example.com --onetime --verbose --factsync echo "type enter to continue" read enter_key chvt 1After installing your machine should change to virtual terminal 3 (chvt 3) and echo "executing post install" followed by the output from puppet
info: Retrieving facts
info: Caching catalog at /var/lib/puppet/localconfig.yaml
notice: Starting catalog run
notice: //Node[default]/base/Ssh_authorized_key[signer]/ensure: created
1,11c1,2
< # The puppetmaster server
< #PUPPET_SERVER=puppet
<
< # If you wish to specify the port to connect to do so here
< #PUPPET_PORT=8140
<
< # Where to log to. Specify syslog to send log messages to the system log.
< #PUPPET_LOG=/var/log/puppet/puppet.log
<
< # You may specify other parameters to the puppet client here
< #PUPPET_EXTRA_OPTS=--waitforcert=500
---
> PUPPET_SERVER=server0.example.com
> PUPPET_EXTRA_OPTS=--factsync
notice: //Node[default]/base/Remotefile[/etc/sysconfig/puppet]/File[/etc/sysconfig/puppet]/source: replacing from source puppet://server0.example.com/base//etc/sysconfig/puppet with contents {md5}87dd2effcdc742da03df3ab010c03436
notice: //Node[default]/base/Service[puppet]/enable: enable changed 'false' to 'true'
notice: Finished catalog run in 0.23 seconds
type enter to continue
%post chvt 3 echo "executing post install" cat >/etc/sysconfig/puppet <<EOF PUPPET_SERVER=server0.example.com PUPPET_EXTRA_OPTS=--factsync EOF chkconfig puppet on echo hostname for puppet is $HOSTNAME puppetd --fqdn=$HOSTNAME --test --no-splay --server=server0.example.com --onetime --verbose --factsync echo "type enter to continue" read enter_key chvt 1Using this method, if the puppetmaster is down when this client goes to execute puppetd, it will fail but puppet will still be configured and will work when the machine is rebooted. I prefer to use Method 2 just in case.
At this point your clients are installing from scratch and getting configured by puppet automatically from kickstart. The bulk of the work is done, what you now need to do is go through your machine configurations and translate all the changes into puppet. This may take a while, but in the end it is well worth it. There may be some changes that are difficult to translate into puppet, for those hard to make changes that require some advanced sed/awk work, augeas may be the answer. We'll look at that in the next section.
For making changes on the fly (not waiting for puppet clients to check-in) you will need to configure func (or use cluster ssh or something similar). Now, on to augeas.
Augeas manipulates files through a system of lenses. Lenses abstract configuration files into configuration parameters that can be manipulated using get and set commands in augeas. The first example I like to work with is /etc/hosts. First we'll install augeas on client15 then we'll make a new entry in /etc/hosts on client15.
[root@client15 ~]# yum install augeas Loading "installonlyn" plugin Loading "rhnplugin" plugin Setting up Install Process Setting up repositories Local 100% |=========================| 1.1 kB 00:00 Reading repository metadata in from local files ... Installed: augeas.x86_64 0:0.5.1-1.el5 Complete! [root@client15 ~]# cat /etc/hosts # Do not remove the following line, or various programs # that require network functionality will fail. 127.0.0.1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 192.168.0.31 client15.example.com client15 [root@client15 ~]# augtool -b augtool> ls /files/etc/hosts #comment[1] = Do not remove the following line, or various programs #comment[2] = that require network functionality will fail. 1/ = (none) 2/ = (none) 3/ = (none) augtool> ls /files/etc/hosts/1/ ipaddr = 127.0.0.1 canonical = localhost.localdomain alias = localhost augtool> ls /files/etc/hosts/2/ ipaddr = ::1 canonical = localhost6.localdomain6 alias = localhost6 augtool> ls /files/etc/hosts/3/ ipaddr = 192.168.0.31 canonical = client15.example.com alias = client15 augtool> set /files/etc/hosts/4/ipaddr 192.168.0.1 augtool> set /files/etc/hosts/4/canonical server0.example.com augtool> set /files/etc/hosts/4/alias[1] server0 augtool> set /files/etc/hosts/4/alias[2] puppet.example.com augtool> save Saved 1 file(s) augtool> quit [root@client15 ~]# cat /etc/hosts # Do not remove the following line, or various programs # that require network functionality will fail. 127.0.0.1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 192.168.0.31 client15.example.com client15 192.168.0.1 server0.example.com server0 puppet.example.com [root@client15 ~]#
There are several lenses provided with augeas for manipulating different types of configuration files. The best way to work with augeas is to provide yourself a testing area and use the environment variable AUGEAS_ROOT to specify your "sandbox" to play in. It is important that the directory structure remain the same in your sandbox as it would be in /etc, file locations are used by augeas to apply the correct lens.
[root@client15 ~]# mkdir augeas-play [root@client15 ~]# export AUGEAS_ROOT=/root/augeas-play [root@client15 ~]# cp /etc/ssh/sshd_config augeas-play/ [root@client15 ~]# augtool -b augtool> ls /files/sshd_config augtool> quit [root@client15 ~]# cd augeas-play/ [root@client15 augeas-play]# mkdir -p etc/ssh [root@client15 augeas-play]# mv sshd_config etc/ssh/ [root@client15 augeas-play]# augtool -b augtool> ls /files/etc/ssh/sshd_config #comment[1] = $OpenBSD: sshd_config,v 1.73 2005/12/06 22:38:28 reyk Exp $ #comment[2] = This is the sshd server system-wide configuration file. See #comment[3] = sshd_config(5) for more information. #comment[4] = This sshd was compiled with PATH=/usr/local/bin:/bin:/usr/bin #comment[5] = The strategy used for options in the default sshd_config shipped with #comment[6] = OpenSSH is to specify options with their default value where #comment[7] = possible, but leave them commented. Uncommented options change a #comment[8] = default value. #comment[9] = Port 22 #comment[10] = Protocol 2,1 Protocol = 2 #comment[11] = AddressFamily any #comment[12] = ListenAddress 0.0.0.0 ... Subsystem/ = (none) augtool> quitAfter familiarizing yourself with augeas and making some changes to files in the sandbox, it's time to incorporate those changes into puppet using puppet's augeas plugin.
augeas{ "server0":
context => "/files/etc/hosts",
changes => [
"set 4/ipaddr 192.168.0.1",
"set 4/canonical server0.example.com",
"set 4/alias[1] server0",
"set 4/alias[2] puppet-augeas.example.com",
],
}
[root@client15 augeas-play]# puppetd --fqdn=$HOSTNAME --test --no-splay --server=server0.example.com --onetime --verbose --factsync info: Retrieving facts info: Caching catalog at /var/lib/puppet/localconfig.yaml notice: Starting catalog run notice: //Node[default]/base/Augeas[server0]/returns: executed successfully notice: Finished catalog run in 1.87 seconds [root@client15 augeas-play]# cat /etc/hosts # Do not remove the following line, or various programs # that require network functionality will fail. 127.0.0.1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 192.168.0.31 client15.example.com client15 192.168.0.1 server0.example.com server0 puppet-augeas.example.com [root@client15 augeas-play]#We replaced the alias puppet.example.com with puppet-augeas.example.com using puppet-augeas. Trying to make this change with a combination of sed or awk would be possible, but with augeas it is much more clear what we are trying to achieve and it is safer also.
For a more concrete example, we'll modify ssh to deny password access. We'll use the puppet nofity system to have ssh restart after we make the change with augeas.
Added to base.pp
service { sshd: ensure => true, enable => true, hasrestart => true }
augeas{ "ssh":
context => "/files/etc/ssh/sshd_config",
changes => [
"set PasswordAuthentication no"
],
notify => Service["sshd"]
}
Now when we run puppet again sshd_config will be updated which will cause sshd to be restarted (triggered).
[root@client15 augeas-play]# puppetd --fqdn=$HOSTNAME --test --no-splay --server=server0.example.com --onetime --verbose --factsync info: Retrieving facts info: Caching catalog at /var/lib/puppet/localconfig.yaml notice: Starting catalog run notice: //Node[default]/base/Augeas[ssh]/returns: executed successfully info: //Node[default]/base/Augeas[ssh]: Scheduling refresh of Service[sshd] notice: //Node[default]/base/Service[sshd]: Triggering 'refresh' from 1 dependencies notice: Finished catalog run in 3.04 seconds [root@client15 augeas-play]# exit Connection to client15.example.com closed. [root@server0 manifests]# ssh root@client15.example.com Permission denied (publickey,gssapi-with-mic).Using a combination of kickstart, puppet and puppet-augeas, you can configure just about every change you need to make on a machine. But for those occasions where you need to make the change immediately, there is a solution, func. We'll talk about that next.
We already downloaded and installed func on our clients using kickstart. We can now install fun on server0 and push the configuration with puppet.
[root@server0 ~]# yum install func Setting up Install Process Parsing package install arguments Resolving Dependencies --> Running transaction check ---> Package func.noarch 0:0.24-1.el5 set to be updated --> Processing Dependency: certmaster >= 0.24 for package: func --> Processing Dependency: python-simplejson for package: func --> Processing Dependency: pyOpenSSL for package: func --> Running transaction check ... Installed: func.noarch 0:0.24-1.el5 Dependency Installed: certmaster.noarch 0:0.24-1.el5 pyOpenSSL.x86_64 0:0.6-1.p24.7.2.2 python-simplejson.x86_64 0:2.0.3-2.el5 Complete! [root@server0 ~]# chkconfig certmaster on [root@server0 ~]# service certmaster start Starting certmaster daemon:Certmaster listens on port 51235, we'll need to add that port to our firewall.
[root@server0 ~]# iptables -I RH-Firewall-1-INPUT -p tcp -m state --state NEW,ESTABLISHED,RELATED --dport 51235 -j ACCEPT [root@server0 ~]# cd /etc/sysconfig [root@server0 sysconfig]# iptables-save >iptablesNow, let's add the configuration for func to puppet and restart the puppetmaster (since we will need to change site.pp).
[root@server0 etc]# cd /var/lib/puppet/files/base/etc
[root@server0 etc]# mkdir certmaster
[root@server0 etc]# cd certmaster/
[root@server0 certmaster]# vi minion.conf
[root@server0 certmaster]# cat minion.conf
# configuration for minions
[main]
certmaster = server0.example.com
certmaster_port = 51235
log_level = DEBUG
cert_dir = /etc/pki/certmaster
[root@server0 certmaster]# cd /etc/puppet/manifests/
[root@server0 manifests]# vi func.pp
[root@server0 manifests]# cat func.pp
class func {
remotefile { "/etc/certmaster/minion.conf": mode => 644 }
service { funcd: ensure => true, enable => true, hasrestart => true }
}
[root@server0 manifests]# vi site.pp
[root@server0 manifests]# cat site.pp
# site.pp
import "functions.pp"
# define nodes
import "nodes.pp"
# define classes
import "base.pp"
import "func.pp"
[root@server0 manifests]# vi nodes.pp
[root@server0 manifests]# cat nodes.pp
node default {
include base
include func
}
[root@server0 manifests]# service puppetmaster restart
Stopping puppetmaster: [ OK ]
Starting puppetmaster: [ OK ]
Now on our client, if we restart puppet, we can see the change propagated (we could also wait and the change would be propagated at the next check-in.
[root@client15 ~]# cd /etc/certmaster
[root@client15 certmaster]# puppetd --fqdn=$HOSTNAME --test --no-splay --server=server0.example.com --onetime --verbose --factsync
info: Retrieving facts
info: Caching catalog at /var/lib/puppet/localconfig.yaml
notice: Starting catalog run
4c4
< certmaster = certmaster
---
> certmaster = server0.example.com
8d7
<
notice: //Node[default]/func/Remotefile[/etc/certmaster/minion.conf]/File[/etc/certmaster/minion.conf]/source: replacing from source puppet://server0.example.com/base//etc/certmaster/minion.conf with contents {md5}7c1ae4564f05e3f9ca50633c89d2a63b
notice: //Node[default]/func/Service[funcd]/ensure: ensure changed 'stopped' to 'running'
notice: Finished catalog run in 2.67 seconds
[root@client15 certmaster]# cat minion.conf
# configuration for minions
[main]
certmaster = server0.example.com
certmaster_port = 51235
log_level = DEBUG
cert_dir = /etc/pki/certmaster
Now, back on server0 we'll need to sign client15's certificate.
[root@server0 ~]# certmaster-ca --list client15.example.com [root@server0 ~]# certmaster-ca --sign client15.example.com /var/lib/certmaster/certmaster/csrs/client15.example.com.csr signed - cert located at /var/lib/certmaster/certmaster/certs/client15.example.com.cert [root@server0 ~]# certmaster-ca --list No certificates to signAt this point out client has connected to our server and requested a certificate, we then signed that certificate. We can now ask the client to do something.
[root@server0 certmaster]# func client15.example.com call hardware info
{'client15.example.com': ['REMOTE_ERROR',
'socket.error',
"(113, 'No route to host')",
' File "/usr/lib/python2.4/site-packages/func/overlord/client.py", line 433, in process_server\n retval = getattr(conn, meth)(*args[:])\n File "/usr/lib64/python2.4/xmlrpclib.py", line 1096, in __call__\n return self.__send(self.__name, args)\n File "/usr/lib64/python2.4/xmlrpclib.py", line 1383, in __request\n verbose=self.__verbose\n File "/usr/lib64/python2.4/xmlrpclib.py", line 1129, in request\n self.send_content(h, request_body)\n File "/usr/lib64/python2.4/xmlrpclib.py", line 1243, in send_content\n connection.endheaders()\n File "/usr/lib64/python2.4/httplib.py", line 804, in endheaders\n self._send_output()\n File "/usr/lib64/python2.4/httplib.py", line 685, in _send_output\n self.send(msg)\n File "/usr/lib64/python2.4/httplib.py", line 652, in send\n self.connect()\n File "/usr/lib/python2.4/site-packages/certmaster/SSLCommon.py", line 106, in connect\n self.sock.connect((self.host, self.port))\n File "", line 1, in connect\n']}=
Almost, not quite, we need to open the port on the firewall on the client so we can talk to the funcd running on client15. We'll do this with puppet, there is an iptables module available. We'll just use service and remotefile in this example (We'll also change the default policy from REJECT to DROP)
[root@server0 ~]# cd /var/lib/puppet/files/base/etc/sysconfig
[root@server0 sysconfig]# vi iptables
[root@server0 sysconfig]# cat iptables
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:Example.com-1-INPUT - [0:0]
-A INPUT -j Example.com-1-INPUT
-A FORWARD -j Example.com-1-INPUT
-A Example.com-1-INPUT -i lo -j ACCEPT
-A Example.com-1-INPUT -p icmp --icmp-type any -j ACCEPT
-A Example.com-1-INPUT -p 50 -j ACCEPT
-A Example.com-1-INPUT -p 51 -j ACCEPT
-A Example.com-1-INPUT -p udp --dport 5353 -d 224.0.0.251 -j ACCEPT
-A Example.com-1-INPUT -p udp -m udp --dport 631 -j ACCEPT
-A Example.com-1-INPUT -p tcp -m tcp --dport 631 -j ACCEPT
-A Example.com-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A Example.com-1-INPUT -m state --state NEW -m tcp -p tcp --dport 51234 -j ACCEPT
-A Example.com-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A Example.com-1-INPUT -j DROP
COMMIT
[root@server0 sysconfig]# cd /etc/puppet/manifests
[root@server0 manifests]# vi func.pp
[root@server0 manifests]# cat func.pp
class func {
remotefile { "/etc/certmaster/minion.conf": mode => 644 }
service { funcd: ensure => true, enable => true, hasrestart => true }
remotefile { "/etc/sysconfig/iptables": mode => 644,
notify => Service["iptables"] }
service { iptables: ensure => true, enable => true, hasrestart => true }
}
Now, back on client15, we again could wait for the change to be propagated, but we'll just trigger it.
[root@client15 ~]# puppetd --fqdn=$HOSTNAME --test --no-splay --server=server0.example.com --onetime --verbose --factsync info: Retrieving facts info: Caching catalog at /var/lib/puppet/localconfig.yaml notice: Starting catalog run 1,2d0 < # Firewall configuration written by system-config-securitylevel < # Manual customization of this file is not recommended. 7,19c5,18 ... notice: //Node[default]/func/Service[iptables]/ensure: ensure changed 'stopped' to 'running' notice: //Node[default]/func/Service[iptables]: Triggering 'refresh' from 2 dependencies notice: Finished catalog run in 2.55 seconds [root@client15 ~]#Back on server0, we'll try our func call again.
[root@server0 ~]# func client15.example.com call hardware info
{'client15.example.com': {'bogomips': '7187.63',
'cpuModel': 'Intel(R) Pentium(R) 4 CPU 3.60GHz',
'cpuSpeed': '3590',
'cpuVendor': 'GenuineIntel',
'defaultRunlevel': '3',
...
'systemSwap': '8191',
'systemVendor': 'Dell Inc.'}}
At this point you could configure certmaster to automatically sign keys as we did for puppet. Anything we need to do immediately, we can now do in func. If you want to use func for inventory purposes, you'll need to also download a git rpm and install that on server0. If you try to use func-inventory without git, it will work, but changes to the inventory won't be tracked.
[root@server0 ~]# func-inventory git-core is not installed, so no change tracking is available. use --no-git or, better, just install it. [root@server0 ~]# func-inventory --no-gitThe inventory files will be stored in /var/lib/func/inventory.