In this module you will learn about
|
The idea of voluntary cooperation used by cfengine places restrictions on how files can be copied between hosts. CFEngine allows only `pull' (download) but not `push' (upload). This means you cannot force a cfagent to perform an operation against local policy but only request resources. There is no loss of flexibility in this choice, but a big increase in security.
CFEngine does not allow one to copy from one system to another system (push) since it amounts to forcing the system to perform an operation against it will. However, push is not totally eliminated from cfengine. CFEngine supports a kind of push which consistent with the idea of voluntary cooperation. If you want to copy from your local system to a remote machine you will need to place an order requesting the remote machine to copy the data for you. When the remote machine receives the order it will evaluate itself to see if it wants to handle the request. To allow remote copying between two systems each of the system must explicitly grant access before the operation can take place.
The following example illustrates how this could be done. We will consider three scenarios namely client server communication, peer to peer copying and simulation of push.
#client server copy #server side cfservd.conf control: domain = ( mydomain.com ) clients = ( 192.10.10 ) #peer subnet AllowConnectionsFrom = ( $(clients) ) #allow clients to pull data AllowMultipleConnectionsFrom = ( $(clients) ) TrustKeysFrom = ( $(clients) ) admit: /master/cfengine/inputs $(clients)
Example 1 set up the permission to allow a cfengine client to pull data from a cfengine server. The domain specifies the existing domain. The line 3 defines clients subnet. This means any client in this subnet will be allowed to pull data from the server. The next step is to write the client policy files to copy data from the server.
Example 2 is the client policy file.
Example 2 #client side policy file, cfagent.conf control: domain = ( mydomain.com ) serverip = ( 192.10.10.1 ) #server ip address master = ( /home/mark/inputs ) actionsequence = ( copy ) copy: /master/cfengine/inputs server=$(serverip) dest=(master) recurse=inf trustkey=on
Example 2 allows the client to copy or pull from the /master/cfengine/inputs to /home/mark/inputs on the local machine. This will succeed because the client is part of the subnet allow to pull from the server. To test the setup we need to run cfservd on the server side and cfagent on the client side.
We consider the second scenario. For the second scenario we want both machines to pull from each other. This means both machines should have cfservd.conf files and also a policy files that pull the data from each other. We will now label the systems peer1 and peer2. Suppose peer 1 has ip 192.10.10.1 and peer 2 has an ip 192.10.10.2, we can write the policy files as follows:
#Peer 1 #server side cfservd.conf control: domain = ( mydomain.com ) clients = ( 192.10.10 ) #peer subnet AllowConnectionsFrom = ( $(clients) ) #allow clients to pull data AllowMultipleConnectionsFrom = ( $(clients) ) TrustKeysFrom = ( $(clients) ) admit: #allow clients to pull data from only the inputs directory /master/cfengine/inputs $(clients)
#client side policy file, cfagent.conf control: domain = ( mydomain.com ) serverip = ( 192.10.10.2 ) #server ip address master = ( /home/eben/inputs ) actionsequence = ( copy ) copy: /master/cfengine/inputs server=$(serverip) dest=(master) recurse=inf trustkey=on
# Peer 2 #server side cfservd.conf control: domain = ( mydomain.com ) clients = ( 192.10.10 ) #peer subnet AllowConnectionsFrom = ( $(clients) ) #allow clients to pull data AllowMultipleConnectionsFrom = ( $(clients) ) TrustKeysFrom = ( $(clients) ) admit: /master/cfengine/inputs $(clients)
#This file should be kept in /var/cfengine/inputs #client side policy file, cfagent.conf control: domain = ( mydomain.com ) serverip = ( 192.10.10.1 ) #server ip address master = ( /home/mark/inputs ) actionsequence = ( copy ) copy: /master/cfengine/inputs server=$(serverip) dest=(master) recurse=inf trustkey=on
The only changes made were to have the two policy files on both machines and interchanging the IPs of the client configuration files so that they can pull from appropriate the server.
We now consider the third scenario. The third scenario will allow us to simulate push by using cfrun without breaking the rules of voluntary cooperation.
We start by modifying the cfservd.conf file. We need to modify the cfservd.conf to include the cfrun command. The cfrunCommand specifies which cfengine agent should be run when cfrun request is received. The cfservd.conf is modified as follows:
#cfservd.conf for the puller control: domain = ( mydomain.com ) clients = ( 192.10.10 ) #peer subnet cfrunCommand = ( /var/cfengine/bin/cfagent ) AllowConnectionsFrom = ( $(clients) ) #allow clients to pull data AllowMultipleConnectionsFrom = ( $(clients) ) TrustKeysFrom = ( $(clients) ) classes: hostlist = ( IPRange(192.10.10.0/24) ) admit: /master/cfengine/inputs $(clients) /var/cfengine/bin/cfagent hostlist
We include
cfrunCommand = ( /var/cfengine/bin/cfagent )
Here we specify that cfagent
should be called when cfrun
request is
received. On the same server machine we have to write the pull
configuration file in the cfagent.conf since cfrunCommand reads
cfagent.conf by default.
#cfagent.conf for puller control: domain = ( mydomain.com ) serverip = ( 192.10.10.2 ) #server ip address master = ( /home/mark/inputs ) actionsequence = ( copy ) copy: /master/cfengine/inputs server=$(serverip) dest=(master) recurse=inf trustkey=on
#Pusher #cfservd.conf for pusher control: domain = ( mydomain.com ) clients = ( 192.10.10 ) #peer subnet AllowConnectionsFrom = ( $(clients) ) #allow clients to pull data AllowMultipleConnectionsFrom = ( $(clients) ) TrustKeysFrom = ( $(clients) ) admit: /master/cfengine/inputs $(clients)
The client will just run cfservd.conf which will grant access to
remote machine to pull data. In addition the client needs cfrun.hosts
file. The cfrun.hosts must contain the IP or the name of the machine
we are requesting to pull our data. We can now run the cfrun command
to parse the policy file on all the hosts listed in cfrun.hosts
file. The cfrun.hosts file should be placed in /var/cfengine/inputs
and should contain 192.10.10.1
. We can also use the optional host
list instead of cfrun.hosts files.
|
Authentication is about making sure you are who you say you are. Traditionally, there are two main approaches: a trusted third party (arbiter of the truth) approach and the challenge approach. The trust third party allows us to use a third party that the two individuals who want to authenticate each other trust to take the decision, as usually done in e-commerce site. The challenge approach allows the individuals to decide whether to trust each other rather than using a third party. CFEngine uses the challenge approach. Its model is based in the Secure Shell (OpenSSH). It allows two machines to authenticate each other by the machine deciding whether to accept the offered keys. For key exchange between client and server, the server has to decide if it will trust the client by using the TrustKeysFrom directive. The TrustKeysFrom directive allows the server to accept keys from one or more machines.
On the client side the client has to also specify if it will trust key
from the server by using the trustkey directive. The trustkey
directive allows a client to decide whether to accept keys from a
server. The cfengine authentication model is base on ssh scheme,
however unlike ssh, cfengine authentication is not interactive and the
keys are generated by cfkey
program instead of ssh key-gen
program. The ssh interaction is accomplished in cfengine using the
trustkey directive. Once the keys have been exchange the trustkey=on
is no longer effective. The cfkey when run generate the cfengine key
and store it in /var/cfengine/ppkeys subdirectory.
The server side configuration
control: domain = ( mydomain.com ) clients = ( 192.10.10 ) #peer subnet AllowConnectionsFrom = ( $(clients) ) #allow clients to pull data AllowMultipleConnectionsFrom = ( $(clients) ) TrustKeysFrom = ( $(clients) ) admit: /master/cfengine/inputs $(clients) #allow clients to pull data from only the inputs directory
The client side configuration:
#client side policy file, cfagent.conf control: domain = ( mydomain.com ) serverip = ( 192.10.10.1 ) #server ip address master = ( /home/mark/inputs ) actionsequence = ( copy ) copy: /master/cfengine/inputs server=$(serverip) dest=(master) recurse=inf trustkey=on #end
CFEngine uses the public private key pairs generated by cfkey to encrypt data during file transfer. CFEngine uses RSA encryption algorithm to encrypt information.
The encrypt
directive is used to specify encryption in cfengine. To
encrypt file transmission all you have to do is to include the
encrypt=on
or encrypt=true
in the policy file.
#cfagent.conf control: domain = ( mydomain.com ) serverip = ( 192.10.10.2 ) #server ip address master = ( /home/mark/inputs ) actionsequence = ( copy ) copy: /master/cfengine/inputs server=$(serverip) dest=(master) recurse=inf trustkey=on encrypt=on
CFEngine has debugging tools that help to troubleshooting. You can run cfengine in debug mode by using the options dn where n range from nothing to 2. So we have -d0,-d1,-d2 and -d3. These options will generate outputs which enable one to debug error in the policy file. -d1 includes parsing information but d2 does not include passing information. -d0 means all debugging information and -d3 is for summary parsing information.
Command example
cfagent -d2
When setting up cfservd, you might see the error message
Apr 9 11:22:27 host.ex.org cfservd[613]: Host authentication failed or access denied
This means that cfservd cannot or will not authenticate the connection from your client machine. The message is generic. It is deliberately non-specific so that anyone attempting to attack or exploit the service will not be given information which might be useful to them. There is a simple checklist for curing this problem:
TrustKeysFrom = ( ) AllowUsers = ( ) AllowMultipleConnections = ( )
Remember that you can run both cfagent
and cfservd
in
debugging mode to see how the authentication takes place:
cfagent -d2 cfservd -d2
Cfagent reports "access denied" regardless of the nature of the error,
to avoid giving away information which might be used by an
attacker. To find out the real reason for a denial, use debugging mode
-d2
.
To use cfrun
, you start by creating a configuration file called
cfrun.hosts in /var/cfengine/inputs. In its simplest
form, it simply contains a list of host names (one per line) that are
to be contacted. If there are special options that you would like to
set, globally or for a particular host, they can also be placed in the
file. For example, the domain name is needed if you are going to use
unqualified host names and your name resolution mechanism does not
append a default domain.
Here is an example file:
domain=cfengine.org access=mark,aeleen outputdir=/tmp/cfoutput maxchild=20 hostnamekeys=true gudinne wallace:3333 ... include=cfrun.external.hosts
Here, we define a list of allowed users, a directory for placing the output of cfrun
queries,
a maximum number of children to spawn
for parallel connections, and required that hosts use key-based authentication. We also list two hosts (one
with an alternate communication port),
as well as including an external file listing more.
If the outputdir
directive is in place, cfrun automatically uses a
parallelized batch method of communication.
The cfrun
command itself has the following form:
cfrun - local options [host list] -- remote options -- remote classes
If not host list is included, then all of the hosts in cfrun.hosts are contacted.
Since cfrun
addresses remote hosts from a local host, there is
an ambiguity in whether options are intended for the cfrun
command itself, or whether they are meant to be passed on to the agent
on the remote hosts. To clarify this distinction, the arguments are
organized as follows:
cfrun
on the local host.
cfagent
(actually to the
command defined in cfrunCommand
. Note, however, that it is not
possible to send the -f
option to the remote agent, to ask it
to run a different policy file. This option is stripped by the server
on receipt to prevent an unauthorized attempt to change policy.
cfservd
service, and specify
classes which must be satisfied by the remote host in order to invoke the
remote command.
Here are some examples, all of which use the host list in cfrun.hosts:
If you are using dynamic addressing for hosts, cfengine will struggle to justify its trust in the public keys it sees. To make sense of public keys one requires both a key and an independent identity to tie it to. Once a key has been trusted, the key alone is (in principle) sufficient identity (just as a fingerprint is good enough to represent you, once it has been registered).
When IP addresses change, cfservd
loses its coupling between address
and key identity and so it has to start re-evaluating. There are two cases
to consider, and the default behavior is like this:
cfservd
, because for instance
the key was changed. In this case cfservd complains that the keys do not match
and refuses to authenticate you.
2item A host with an unknown IP address but a known key. In this case,
cfengine will look for a key file bound to the new address and will
not find one, so cfservd treats the key as unknown and normal trust
rules for accepting a new key apply.
This can cause a problem for hosts with IPV4 addresses given by DHCP,
since the address-key binding is only temporary. cfservd
works around this by allowing you do define
a new access list called
DynamicAddresses = ( 192.240.1 )
If a connecting address is unknown, but lies in this range, cfservd
will look in a database to see if the key itself is known, bound to a
different address. If the key is found, cfservd
trusts the
binding and re-binds the key to the new IP address. If the key is not
known, then (again) normal trust rules apply for accepting a new key.
Servers should always have fixed IP addresses in general, otherwise you have no idea to whom you are connecting.
Public key exchange is a subtle idea. The issues are not difficult to understand, but they are seldom expressed very clearly.
The principle of a public key system is to associate an identity with a key, which can be freely made public knowledge. The difficulty is not in distributing the key, but in being certain who is really the owner of a key that you have received.
Imagine you wanted to know the identity of a stranger whom you have never seen before. What proof would you accept of his or her identity? A name tag? A letter of reference? A DNA test? Of course, an evil identical twin would be able to pass all of these tests and still fool you.
It is important to understand that trust in identity is something we always grant in an entirely personal way. There is always a chance of being wrong: cryptographic methods do not eliminate this doubt (although they sometimes are presented as doing so). Trust is risk, and you simply have to live with it.
There are two common models of trust management:
Key exchange in cfengine is very similar to that in the secure shell. However, cfengine uses separate keys
from ssh
that are generated using
the cfkey
program.
Cfagent is not an interactive program, so when it first receives a key, the decision as to whether to accept it must be made non-interactively. The natural solution is to make this decision part of policy. There are two methods for accomplishing this:
trustkey=on
to a remote copy
command to accept the named remote-server's key.
TrustKeysFrom
directive. If a server IP address is in
this list, it will be accepted.
Once a key has been accepted on
trust, it is trusted for ever, or until you revoke the key by
explicitly deleting the key from /var/cfengine/ppkeys.
So once the key has been exchanged, the trustkey
option has no effect.
Thus, on the client side it does not matter if you leave
trustkey
options in place. On the server side, however, remember
that the trust rule applies to many hosts, some of which
you might not have considered. Always take care with trust issues.
Though the actual security implication of using cfengine is minimal we still need to be aware of possible risk and how we can protect ourselves. CFEngine uses autonomous agents which are complete on their own. Each agent manages it check sum database without the support of external server. This means if someone tampers with the database he could breach cfengine security. One of the solutions to this is to keep cfengine database at a secure location on the system or take a backup of the database.
We could also use neighborhood watch approach where databases of agents are distributed to the neighbors which can then be compared to the original agent’s database for consistency.
CFEngine depends on the policy configuration file for its operations. If this file is changed by an attacker, cfengine will implement the policy of the attacker. It is therefore important to ensure that cfengine configuration file is secured. CFEngine also runs with open port 5308 and therefore it is important to protect the port from external attack. It may be advisable to block external access to the port.
CFEngine authentication depends on public and private key pairs. The cfengine public keys transfer can be done implicitly or explicitly. It is important to ensure accountable key distribution.
Buffer overflow attacks are extremely unlikely in cfengine by design. The likelihood of a bug in cfengine should be compared to the likelihood of a bug existing in the firewall itself.
Denial of service attacks can be mitigated by careful configuration
(see separate FAQ item). cfservd
reads a fixed number of bytes
from the input stream before deciding whether to drop a connection
from a remote host, so it is not possible to buffer overflow attack
before rejection of an invalid host IP.
Another possibility is to use a standard VPN to the inside of the firewall. That way one is concerned first and foremost with the vulnerabilities of the VPN software. Doesn't opening the firewall compromise the integrity of the policy information by allowing an attacker the chance to alter it? The cfengine security model, as well as the design of the server, disallows the uploading of information. No message sent over the cfengine channel can alter data on the server. (This assumes that buffer overflows are impossible.)
Assuming that buffer overflow attacks and DOS attacks are highly improbable, the main worry with opening a port is that intruders will be able to gain access to unauthorized data. If the firewall is configured to open only connections from the policy mirror, then an attacker must spoof the IP of the policy attacker. This requires access to another host in the DMZ and is non-trivial. However, suppose the attacker succeeds then the worst he/she can do is to download information that is available to the policy-mirror. But that information is already available in the DMZ since the data have been exported as part of the policy, thus there is no breach of security. (Security must be understood to be a breach of the terms of policy that has been decided.)
If an attacker gains root access to the mirror, he/she will be able to affect the policy distributed to any host in the DMZ. The policy-mirror has no access to alter any information on the policy source host. Note that this is consistent with the firewall security model of trusted/untrusted regions. The firewall does not mitigate the responsibility of security every host in a network regardless of which side of the firewall it is connected.
Some users want to use cfengine's remote copying mechanism through a firewall, in particular to update the cfengine policy on hosts inside a DMZ (so-called de-militarized zone). Firewalls are often shrouded in myth and mystery. It is important to see the firewall security model together with the cfengine security model. Amongst the difficulties one faces, the firewall administrator is not often the same as the cfengine administrator and does not trust anyone or anything. You might have to convince this person to make changes that help you out, so it is important to understand the consequences of your security strategy.
Any piece of software that traverses a firewall can, in principle, weaken the security of the barrier. On the other hand, a strong piece or software might have better security than the firewall itself. Consider the example in the figure;
We label the regions inside and outside of the firewall as the “secure area" and “Demilitarized Zone" for convenience. It should be understood that the areas inside a firewall is not necessarily secure in any sense of the word unless the firewall configuration is understood together with all other security measures.
Our problem is to copy files from the “secure” source machine to hosts in the DMZ, in order to send them their configuration policy updates. There are two ways of getting files through the firewall:
One of the
main aims of a firewall is to prevent hosts outside the secure area
from opening connections to hosts in the secure area. If we want
cfagent
processes on the outside of the firewall to receive updated policies
from the inside of the firewall, information has to traverse the
firewall.
Cfengine's trust model is fundamentally at odds with the external firewall concept. CFEngine says: “I am my own boss. I don't trust anyone to push me data.” The firewall says: “I only trust things that are behind me.” The firewall thinks it is being secure if it pushes data from behind itself to the DMZ. CFEngine thinks it is being secure if it makes the decision to pull the data autonomously, without any orders from some potentially unknown machine. One of these mechanisms has to give if firewalls are to co-exist with cfengine.
From the firewall's viewpoint, push and pull are different: a push requires only an outgoing connection from a trusted source to an untrusted destination; a pull necessarily requires an untrusted connection being opened to a trusted server within the secure area. For some firewall administrators, the latter is simply unacceptable (because they are conditioned to trust their firewall). But it is important to evaluate the actual risk. We have a few observations about the latter to offer at this point:
You can compromise by creating a policy mirror in the DMZ. This is the recommended way to copy files, so that normal cfengine pull methods can then be used by all other hosts in the DMZ, using the mirror as their source. The policy mirror host should be as secure as possible, with preferably few or no other services running that might allow an attacker to compromise it. In this configuration, you are using the mirror host as an envoi of the secure region in the DMZ.
Any method of pushing a new version of policy can be chosen in principle: CVS, FTP, RSYNC, SCP. The security disadvantage of the push method is that it opens a port on the policy-mirror, and therefore the same vulnerability is now present on the mirror, except that now you have to trust the security of another piece of software too. Since this is not a cfengine port, no guarantees can be made about what access attackers will get to the mirror host.
Suppose you are allowed to open a hole in your firewall to a single policy host on the inside. To distribute files to hosts that are outside the firewall it is only necessary to open a single tunnel through the firewall from the policy-mirror to the cfengine service port on the source machine. Connections from any other host will still be denied by the firewall. This minimizes the risk of any problems caused by attackers.
To open a tunnel through the firewall, you need to alter the filter rules. A firewall blocks access at the network level. Configuring the opening of a single port is straightforward. We present some sample rules below, but make sure you seek the guidance of an expert if necessary.
Cisco IOS rules look like this
ip access-group 100 in access-list 100 permit tcp mirror host source eq 5308 access-list 100 deny ip any any
Linux iptables
rules might look something like this:
iptables -N newchain iptables -A newchain -p tcp -s mirror-ip 5308 -j ACCEPT iptables -A newchain -j DENY
Once a new copy of the policy is downloaded by cfengine to the policy mirror, other clients in the DMZ can download that copy from the mirror. The security of other hosts in the DMZ is dependent on the security of the policy mirror.
CFEngine can be centralized or decentralized depending on the requirement and the choice of the user. The centralized infrastructure consists of policy server which will store the policy configuration files for distribution; the rest of the hosts will pull their policy from this centralized server. The merit of centralized administration are that it aids consistency by providing single point of decision making, change of policy is easy since it is done at one place, backup and version control are also convenient. However, centralized administration could hinder local customization; it is inappropriate for privacy and security if we have completely independent departments or services which needs to coexist and work together.
Message digests are supposed to be unbreakable, tamperproof technologies, but of course everything can be broken by a sufficiently determined attacker. Suppose someone wanted to edit a file and alter the cfengine checksum database to cover their tracks. If they had broken into your system, this is potentially easy to do. How can we detect whether this has happened or not?
A simple solution to this problem is to use another checksum-based operation to copy the database to a completely different host. By using a copy operation based on a checksum value, we can also remotely detect a change in the checksum database itself.
Consider the following code:
# Neighbourhood watch control: allpeers = ( SelectPartitionNeighbours(/path/hostlist,#,random,4) ) copy: /var/cfengine/checksum_digests.db dest=/safekeep/chkdb_$(this) type=checksum server=$(allpeers) inform=true # warn of copy backup=timestamp define=tampering alert: tampering:: 'Digest tampering detected on a peer'
It works by building a list of neighbours for each host. The function SelectPartitionNeighbours
can be used for this. Using a file which contains a list of all hosts running cfengine (e.g. the cfrun.hosts file), we create a list of hosts to copy databases . Each host in the network therefore takes on the responsibility to watch over its neighbours.
The copy rule attempts to copy the database to some file in a safekeeping directory. We label the destination file with $(this) which becomes the name of the server from which the file was collected. Finally, we backup any successful copies using a timestamp to retain a complete record of all changes on the remote host. Each time a change is detected, a copy will be kept of the old. The rule contains triggers to issue alerts and warnings too just to make sure the message will be heard.
In theory, all four neighbours should signal this change. If an attacker had detailed knowledge of the system, he or she might be able to subvert one or two of these before the change was detected, but it is unlikely that all four could be covered up. At any rate, this approach maximizes the chances of change detection.
Finally, in order to make this copy, you must, of course, grant access to the database in cfservd.conf.
# cfservd.conf admit: any:: /var/cfengine/checksum_digests.db mydomain.tld
Let us now consider what happens if an attacker changes a file an edits the checksum database. Each of the four hosts that has been designated a neighbour will attempt to update their own copy of the database. If the database has been tampered with, they will detect a change in the md5 checksums of the remote copy versus the original. The file will therefore be copied.
It is not a big problem that others have a copy of your checksum database. They cannot see the contents of your files from this. A possibly greater problem is that this configuration will unleash an avalanche of messages if a change is detected. This makes messages visible at least.
|