From 607a8536ad2b2a2703483c626b808ea9315242c1 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 13 Mar 2024 12:38:14 +0100 Subject: [PATCH 1/9] Secure bootstrap: Switched to command blocks Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index d7a88b1ca..add029acc 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -21,7 +21,9 @@ exchange of keys between *client* and *hub*, can either happen manually or automatically. Usually this step is automated as a dead-simple "bootstrap" procedure: -`cf-agent --bootstrap $HUB_IP` +```command +cf-agent --bootstrap $HUB_IP +``` It is presumed that during this first key exchange, *the network is trusted*, and no attacker will hijack the connection. After @@ -52,8 +54,8 @@ you wish to trust. Copy the hubs public key (`/var/cfengine/ppkeys/localhost.pub`) to the agent you wish to bootstrap. And install it using `cf-key`. -```console -[root@host001]# cf-key --trust-key /path/to/hubs/key.pub +```command +cf-key --trust-key /path/to/hubs/key.pub ``` **Note:** If you are using [protocol_version `1` or `classic`][Components#protocol_version] @@ -65,17 +67,16 @@ For example: notice: Establishing trust might be incomplete. For completeness, use --trust-key IPADDR:filename ``` -Next copy the hosts public key (`/var/cfengine/ppkeys/localhost.pub`) to the hub -and install it using `cf-key`. +Next copy the hosts public key (`/var/cfengine/ppkeys/localhost.pub`) to the hub and install it using `cf-key`. -```console -[root@hub]# cf-key --trust-key /path/to/host001/key.pub +```command +cf-key --trust-key /path/to/host001/key.pub ``` Now that the hosts trust each other we can bootstrap the host to the hub. -```console -[root@host001]# cf-agent --trust-server no --bootstrap $HUB +```command +cf-agent --trust-server no --bootstrap $HUB ``` ## Manually establishing trust @@ -83,8 +84,8 @@ Now that the hosts trust each other we can bootstrap the host to the hub. Get the hub's key and fingerprint, we'll them when configuring the host to trust the hub: -```console -[root@hub]# HUB_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub +```command +HUB_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub ``` ### On each client we deploy @@ -94,20 +95,20 @@ We will perform a *manual bootstrap*. * Get the client's key and fingerprint, we'll need it later when establishing trust on the hub: - ```console - [root@host001]# CLIENT_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub` + ```command + CLIENT_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub` ``` * Write the policy hub's IP address to `policy_server.dat`: - ```console - [root@host001]# echo $HUB_IP > /var/cfengine/policy_server.dat + ```command + echo $HUB_IP > /var/cfengine/policy_server.dat ``` * Put the hub's key into the client's trusted keys: - ```console - [root@host001]# scp $HUB_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${HUB_KEY}.pub + ```command + scp $HUB_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${HUB_KEY}.pub ``` ### Install the clients public key on the hub @@ -115,6 +116,6 @@ We will perform a *manual bootstrap*. * Put the client's key into the hub's trusted keys. So on the hub, run: - ```console - [root@hub]# scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub + ```command + scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub ``` From 047061b8ca488812bece5448ffe5165f75c1ef46 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 13 Mar 2024 12:40:30 +0100 Subject: [PATCH 2/9] Secure bootstrap: Removed unnecessary bullet point list formatting Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index add029acc..6f855749b 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -42,9 +42,8 @@ We must change the policy we're distributing to fully locked-down settings. So after we have set-up our hub (using the standard procedure of `cf-agent --bootstrap $HUB_IP`) we take care of the following: -* `cf-serverd` must never accept a connection from a client presenting an - untrusted key. [Disable automatic key trust][Masterfiles Policy Framework#Automatic bootstrap - Trusting keys from new hosts with trustkeysfrom] - by providing an empty list for `default:def.trustkeysfrom`. +`cf-serverd` must never accept a connection from a client presenting an untrusted key. +[Disable automatic key trust][Masterfiles Policy Framework#Automatic bootstrap - Trusting keys from new hosts with trustkeysfrom] by providing an empty list for `default:def.trustkeysfrom`. ## Bootstrap without automatically trusting @@ -92,30 +91,30 @@ HUB_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub We will perform a *manual bootstrap*. -* Get the client's key and fingerprint, we'll need it later when establishing - trust on the hub: +Get the client's key and fingerprint, we'll need it later when establishing +trust on the hub: - ```command - CLIENT_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub` - ``` +```command +CLIENT_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub` +``` -* Write the policy hub's IP address to `policy_server.dat`: +Write the policy hub's IP address to `policy_server.dat`: - ```command - echo $HUB_IP > /var/cfengine/policy_server.dat - ``` +```command +echo $HUB_IP > /var/cfengine/policy_server.dat +``` -* Put the hub's key into the client's trusted keys: +Put the hub's key into the client's trusted keys: - ```command - scp $HUB_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${HUB_KEY}.pub - ``` +```command +scp $HUB_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${HUB_KEY}.pub +``` ### Install the clients public key on the hub -* Put the client's key into the hub's trusted keys. So - on the hub, run: +Put the client's key into the hub's trusted keys. So +on the hub, run: - ```command - scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub - ``` +```command +scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub +``` From 7abe94c8d16fb5fc35db99060fe961af1c7bb40d Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 13 Mar 2024 12:45:02 +0100 Subject: [PATCH 3/9] Secure bootstrap: Reformatted to use one sentence per line style Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index 6f855749b..43a55a75b 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -5,65 +5,55 @@ published: true sorting: 20 --- -This guide presumes that you already have CFEngine properly installed -and running on the policy hub, the machine that distributes the policy -to all the clients. It also presumes that CFEngine is installed, but not -yet configured, on a number of clients. +This guide presumes that you already have CFEngine properly installed and running on the policy hub, the machine that distributes the policy to all the clients. +It also presumes that CFEngine is installed, but not yet configured, on a number of clients. -We present a step-by-step procedure to securely bootstrapping a -number of servers (referred to as *clients*) to the policy hub, over a -possibly unsafe network. +We present a step-by-step procedure to securely bootstrapping a number of servers (referred to as *clients*) to the policy hub, over a possibly unsafe network. ## Introduction -CFEngine's trust model is based on the secure exchange of keys. This -exchange of keys between *client* and *hub*, can either happen manually -or automatically. Usually this step is automated as a dead-simple -"bootstrap" procedure: +CFEngine's trust model is based on the secure exchange of keys. +This exchange of keys between *client* and *hub*, can either happen manually or automatically. +Usually this step is automated as a dead-simple "bootstrap" procedure: ```command cf-agent --bootstrap $HUB_IP ``` -It is presumed that during this first key exchange, *the network is -trusted*, and no attacker will hijack the connection. After +It is presumed that during this first key exchange, *the network is trusted*, and no attacker will hijack the connection. +After "bootstrapping" is complete, the node can be deployed in the open internet, and all connections are considered secure. -However there are cases where initial CFEngine deployment is happening -over an insecure network, for example the Internet. In such cases we -already have a secure channel to the clients, usually ssh, and we use -this channel to *manually establish trust* from the hub to the clients -and vice-versa. +However there are cases where initial CFEngine deployment is happening over an insecure network, for example the Internet. +In such cases we already have a secure channel to the clients, usually ssh, and we use this channel to *manually establish trust* from the hub to the clients and vice-versa. ## Locking down the policy server -We must change the policy we're distributing to fully locked-down -settings. So after we have set-up our hub (using the standard procedure -of `cf-agent --bootstrap $HUB_IP`) we take care of the following: +We must change the policy we're distributing to fully locked-down settings. +So after we have set-up our hub (using the standard procedure of `cf-agent --bootstrap $HUB_IP`) we take care of the following: `cf-serverd` must never accept a connection from a client presenting an untrusted key. [Disable automatic key trust][Masterfiles Policy Framework#Automatic bootstrap - Trusting keys from new hosts with trustkeysfrom] by providing an empty list for `default:def.trustkeysfrom`. ## Bootstrap without automatically trusting -In order to securely bootstrap a host you must have the public key of the host -you wish to trust. +In order to securely bootstrap a host you must have the public key of the host you wish to trust. -Copy the hubs public key (`/var/cfengine/ppkeys/localhost.pub`) to the agent you -wish to bootstrap. And install it using `cf-key`. +Copy the hubs public key (`/var/cfengine/ppkeys/localhost.pub`) to the agent you wish to bootstrap. +And install it using `cf-key`. ```command cf-key --trust-key /path/to/hubs/key.pub ``` -**Note:** If you are using [protocol_version `1` or `classic`][Components#protocol_version] -you need to supply an IP address before the path to the key. +**Note:** If you are using [protocol_version `1` or `classic`][Components#protocol_version] you need to supply an IP address before the path to the key. For example: ``` -notice: Establishing trust might be incomplete. For completeness, use --trust-key IPADDR:filename +notice: Establishing trust might be incomplete. +For completeness, use --trust-key IPADDR:filename ``` Next copy the hosts public key (`/var/cfengine/ppkeys/localhost.pub`) to the hub and install it using `cf-key`. @@ -80,8 +70,7 @@ cf-agent --trust-server no --bootstrap $HUB ## Manually establishing trust -Get the hub's key and fingerprint, we'll them when configuring the host to trust -the hub: +Get the hub's key and fingerprint, we'll them when configuring the host to trust the hub: ```command HUB_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub @@ -91,8 +80,7 @@ HUB_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub We will perform a *manual bootstrap*. -Get the client's key and fingerprint, we'll need it later when establishing -trust on the hub: +Get the client's key and fingerprint, we'll need it later when establishing trust on the hub: ```command CLIENT_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub` @@ -112,8 +100,8 @@ scp $HUB_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${HUB_K ### Install the clients public key on the hub -Put the client's key into the hub's trusted keys. So -on the hub, run: +Put the client's key into the hub's trusted keys. +So on the hub, run: ```command scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub From b40db17fff1298e18ecdac603ec24a3a6ad12e70 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 13 Mar 2024 13:11:20 +0100 Subject: [PATCH 4/9] Secure bootstrap: Added bootstrap command to the end Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index 43a55a75b..a55795ef8 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -106,3 +106,18 @@ So on the hub, run: ```command scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub ``` + +### Start the binaries + +Now that keys are distributed, trust is established. +We can run the normal bootstrap command with one crucial difference: +`--trust-server no` tells the agent to **not** automatically trust an unknown key on the other end. +This will start the normal CFEngine services (`cf-execd`, `cf-serverd`, etc.): + +``` +cf-agent --trust-server no --bootstrap $HUB_IP +``` + +When we connect to the hubs IP address, if there is another server answering, a potential [man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack), it will not work. +The agent on the client machine will refuse to communicate with the untrusted server. +This is the main reason (security benefit) of doing mutual authentication and secure key distribution. From f7f1b83f83690fd4efa1cc77fec5789c40930bf3 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 13 Mar 2024 14:12:33 +0100 Subject: [PATCH 5/9] Secure bootstrap: Rewrote first part about default configuration, introduction, disable automatic trust, etc. Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 94 +++++++++++++++---- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index a55795ef8..8dde7b21e 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -5,38 +5,92 @@ published: true sorting: 20 --- -This guide presumes that you already have CFEngine properly installed and running on the policy hub, the machine that distributes the policy to all the clients. -It also presumes that CFEngine is installed, but not yet configured, on a number of clients. - -We present a step-by-step procedure to securely bootstrapping a number of servers (referred to as *clients*) to the policy hub, over a possibly unsafe network. - -## Introduction +This guide assumes you already have a working CFEngine hub (installed and bootstrapped), and you have installed CFEngine on a client you want to securely connect to the hub (bootstrap). +See the [Getting started guide][Getting started] for an introduction to CFEngine and how to install it. CFEngine's trust model is based on the secure exchange of keys. -This exchange of keys between *client* and *hub*, can either happen manually or automatically. -Usually this step is automated as a dead-simple "bootstrap" procedure: +Usually, when getting started with CFEngine, this step is automated as a dead-simple "bootstrap" procedure: ```command cf-agent --bootstrap $HUB_IP ``` -It is presumed that during this first key exchange, *the network is trusted*, and no attacker will hijack the connection. -After -"bootstrapping" is complete, the node can be deployed in the open -internet, and all connections are considered secure. +CFEngine uses mutual authentication, so this trust goes in both directions. +Both the client and the hub refuse to communicate with an unknown, untrusted host. + +## Default configuration + +In the default configuration, the policy server (cf-serverd) on the hub machine trusts incoming connections from the same `/16` subnet. +This means that: + +* Bootstrapping new clients will work as long as the 2 first numbers in the IP address are identical ([IPv4 dot decimal representation](https://en.wikipedia.org/wiki/Dot-decimal_notation)) . + The hub and client mutually accept each other's keys, automatically. +* This applies to _all_ IP addresses within that range, not just the 1 IP address belonging to the client you are currently bootstrapping. +* The hub will keep accepting new clients from those IP addresses until you change the configuration. +* If you try to bootstrap a client where those 2 numbers in the IP address do not match the hub, it will fail. + +This situation, where the client and hub automatically transfer and trust each other's keys is called _automatic trust_ or _automatic bootstrap_. +When using automatic trust, it is presumed that during this first key exchange, *the network is trusted*, and no attacker will hijack the connection. +Below we will show ways to change the configuration and bootstrap your clients in more secure ways. +The goal here is to illustrate the different approaches, explaining what is needed and the implications of each. +In the end, you will not be running these commands manually, but rather putting them into a provisioning system. + +## Allowing only specific IP addresses / subnets + +In order to specify and limit which hosts (IP addresses) are considered trusted and allowed to connect and fetch policy files, you can put the trusted IP addresses and subnets into the `acl` variable: + +```json +[file=/var/cfengine/masterfiles/def.json] +{ + "variables": { + "default:def.acl": ["1.2.3.4", "4.3.2.1"] + } +} +``` -However there are cases where initial CFEngine deployment is happening over an insecure network, for example the Internet. -In such cases we already have a secure channel to the clients, usually ssh, and we use this channel to *manually establish trust* from the hub to the clients and vice-versa. +**Important:** Replace `1.2.3.4` with the IP address of your hub, `4.3.2.1` with the IP address of your client, and extend the list with any additional IP addresses / subnets. -## Locking down the policy server +If you are using CFEngine Build, you can use [this module](https://build.cfengine.com/modules/allow-hosts/), putting the IP addresses as module input, or add the json file above to your project. +(Save it as a file called `def.json` and do `cfbs add ./def.json`). -We must change the policy we're distributing to fully locked-down settings. -So after we have set-up our hub (using the standard procedure of `cf-agent --bootstrap $HUB_IP`) we take care of the following: +Once this is set, you are no longer using the default value explained above (the `/16` subnet). +This variable controls 3 different aspects: IP addresses allowed to connect, IP addresses to automatically trust keys from, and IP addresses allowed to fetch policy files. + +**Tip:** Setting the variable to `["0.0.0.0/0"]` will open up your hub to all IPv4 addresses, the entire internet. +This is generally not recommended, but can make sense if you disable automatic trust (shown below), need to support clients connecting from the public internet, and/or want to manage firewalling restrictions outside of CFEngine. + +## Disabling automatic trust - Locking down the policy server + +In all cases, it is recommended to disable automatic trust when you are not using it. +Either immediately after installation (if distributing keys through another channel, see below) or after you are done bootstrapping clients. +You can edit the augments file to achieve this: + +```json +[file=/var/cfengine/masterfiles/def.json] +{ + "variables": { + "default:def.trustkeysfrom": [] + } +} +``` + +If you are using CFEngine Build, you can achieve this by adding [this module](https://build.cfengine.com/modules/disable-automatic-key-trust/), or adding the json file above to your project. + +When combined with the variable above, you can create a very restricted setup: + +```json +[file=/var/cfengine/masterfiles/def.json] +{ + "variables": { + "default:def.acl": ["1.2.3.4", "4.3.2.1"], + "default:def.trustkeysfrom": [] + } +} +``` -`cf-serverd` must never accept a connection from a client presenting an untrusted key. -[Disable automatic key trust][Masterfiles Policy Framework#Automatic bootstrap - Trusting keys from new hosts with trustkeysfrom] by providing an empty list for `default:def.trustkeysfrom`. +Only those 2 IP addresses are allowed to connect, and they must use their existing keys, no new keys are automatically trusted. -## Bootstrap without automatically trusting +## Key distribution - boostrapping without automatically trusting In order to securely bootstrap a host you must have the public key of the host you wish to trust. From 30d18af4a88305828be1d11398a3d820a9ba8752 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Wed, 13 Mar 2024 16:21:49 +0100 Subject: [PATCH 6/9] Secure bootstrap: Added section on key location and generation Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index 8dde7b21e..24a1bb0f8 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -90,6 +90,34 @@ When combined with the variable above, you can create a very restricted setup: Only those 2 IP addresses are allowed to connect, and they must use their existing keys, no new keys are automatically trusted. +## Key location and generation + +If you are installing CFEngine using one of our official packages, keys are automatically generated and you can see them in the expected location: + +```command +sudo ls /var/cfengine/ppkeys +``` +```output + localhost.priv + localhost.pub +'root-SHA=caa398e50c6e6ad554ea90e1bd5e8fee269ca097df6ce0c86ce993be16f6f9e3.pub' +``` + +The keypair of the host itself is always in the `localhost.pub` and `localhost.priv` files. +Additional keypairs from the hosts CFEngine is talking to over the network are in the other `.pub` files. + +**Recommendation:** Don't copy, transfer, open, or share the private key (`localhost.priv`). +It is a secret - putting it in more places is not necessary and increases the chances it could be compromised. +When distributing keys for establishing trust, we are distributing the public keys (`.pub` files). + +If you are compiling CFEngine from source, or spawning a new VM based on an image / snapshot without keys inside, you can generate a new keypair: + +``` +sudo cf-key +``` + +**Tip:** When using "golden images" to spawn machines with CFEngine already installed, ensure the keys in `/var/cfengine/ppkeys` are deleted before generating the snapshot, and generate / insert keys during provisioning. + ## Key distribution - boostrapping without automatically trusting In order to securely bootstrap a host you must have the public key of the host you wish to trust. From 0f85a2bfbfda7b7bcaf70ef302107d63763d11ad Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Thu, 14 Mar 2024 14:25:57 +0100 Subject: [PATCH 7/9] Secure bootstrap: Rewrote section on key distribution Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 89 ++++++++++++------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index 24a1bb0f8..2eec917f1 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -12,7 +12,7 @@ CFEngine's trust model is based on the secure exchange of keys. Usually, when getting started with CFEngine, this step is automated as a dead-simple "bootstrap" procedure: ```command -cf-agent --bootstrap $HUB_IP +cf-agent --bootstrap ``` CFEngine uses mutual authentication, so this trust goes in both directions. @@ -120,76 +120,101 @@ sudo cf-key ## Key distribution - boostrapping without automatically trusting -In order to securely bootstrap a host you must have the public key of the host you wish to trust. +To securely bootstrap a host to a hub, without trusting the network (IP addresses), you need to copy the 2 public keys across some trusted channel. +Below we will be using SSH as the trusted channel, however the commands can easily be translated to however you are able to run commands and transfer files to your hosts. +(This could be via memory stick, a management interface or some other out-of-band management solution). +The same applies to passwordless sudo - we're using sudo commands without password prompts below, if you have configured password prompts for sudo, or another way you need to run privileged commands, please adjust accordingly. -Copy the hubs public key (`/var/cfengine/ppkeys/localhost.pub`) to the agent you wish to bootstrap. -And install it using `cf-key`. +Assuming you are sitting on a laptop / workstation, and have network and SSH access to both the client and the hub, first set up some variables for each of them: ```command -cf-key --trust-key /path/to/hubs/key.pub +BOOTSTRAP_IP="1.2.3.4" HUB_SSH="ubuntu@1.2.3.4" CLIENT_SSH="ubuntu@4.3.2.1" ``` -**Note:** If you are using [protocol_version `1` or `classic`][Components#protocol_version] you need to supply an IP address before the path to the key. +Edit the 3 variables according to your situation, they represent: -For example: +* `BOOTSTRAP_IP` - The IP address of the hub, which you want your client to bootstrap to (connect to). +* `HUB_SSH` - The username / IP combination you would use to connect to the hub with SSH. +* `CLIENT_SSH` - The username / IP combination you would use to connect to the hub with SSH. +### Trusting the client's key on the hub + +Inspect the key: + +```command +ssh "$CLIENT_SSH" "sudo cat /var/cfengine/ppkeys/localhost.pub" ``` -notice: Establishing trust might be incomplete. -For completeness, use --trust-key IPADDR:filename +```output +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAt93D8fb+M7HGZxsVo+FnOhnLM9E0QCr046N369jOeePY65lPOhAD +nlWlDPJrYqhnobEdnFr/uNp0ydqb1EASe4qjhQUDi1ujz5+T9dTwhZqUfx22RM6D +CLulbdoXwImPOCNi157UBRIwYVJ6527rv0/TlTpS9iUQVStg0YCBEasGRcQfX/bU +DKrL5Ei+ukJtSEx11NlZ9tRYNu22mJYPGGpNJ0FbiHvR+eu7mAuUZ1QeddcuYkGP +H5/eIe0uTGOmFLXb4gUQymNLJUjQqxoO2l6Km4UpGj61871gCiqMGVTvvZWFbo+g +1KR3RS6L/Gqv9U89msZTGQafpjFQyVbYnwIDAQAB +-----END RSA PUBLIC KEY----- ``` -Next copy the hosts public key (`/var/cfengine/ppkeys/localhost.pub`) to the hub and install it using `cf-key`. +It should have the format above, with `BEGIN RSA PUBLIC KEY`, the arbitrary data, and `END RSA PUBLIC KEY`. +When you're scripting / automating the copying of keys, you can add some checks for this. + +Download the key: ```command -cf-key --trust-key /path/to/host001/key.pub +ssh "$CLIENT_SSH" "sudo cat /var/cfengine/ppkeys/localhost.pub" > client.pub ``` -Now that the hosts trust each other we can bootstrap the host to the hub. +Upload it to the hub: ```command -cf-agent --trust-server no --bootstrap $HUB +scp ./client.pub "$HUB_SSH":client.pub ``` -## Manually establishing trust - -Get the hub's key and fingerprint, we'll them when configuring the host to trust the hub: +And use `cf-key` to trust the key: ```command -HUB_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub +ssh "$CLIENT_SSH" "sudo cf-key --trust-key client.pub" ``` -### On each client we deploy +### Trusting the hub's key on the client -We will perform a *manual bootstrap*. +Now, for the client we need to perform exactly the same steps: -Get the client's key and fingerprint, we'll need it later when establishing trust on the hub: +Inspect the key: ```command -CLIENT_KEY=`cf-key -p /var/cfengine/ppkeys/localhost.pub` +ssh "$HUB_SSH" "sudo cat /var/cfengine/ppkeys/localhost.pub" +``` +```output +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAt8Wti90sRjLiEhLbC5096nEhzV3fU0N4TrxiGPCb26KufavBrXGw +vmzTeJoWnIFFSn7OYU1g59U7s4aViZwqQ647opc0gZo2dVjDTRFW8lB4dmS7SjAe +t8NA3iXQigWY+45TbvPOalNHurhyrJ4g1+0ttdqwk/L1fVkK0u9wmHrgfo+UQR0D +9P96GWnPKyzVp5PdMmfX0Sm6kMBurawRYeiFCq3gqGtkc0rj3FHr1afrM+8egP9D +sWl43NmMlZ8B9Yt2bP0wdNsbXC7vouDZg8sIQVfvcxSkla+kGceGrEmNTDPuGFlx +VknPhmpjMJ7XhvaXXR1btu3/PLjGLDj6SwIDAQAB +-----END RSA PUBLIC KEY----- ``` -Write the policy hub's IP address to `policy_server.dat`: +Download the key: ```command -echo $HUB_IP > /var/cfengine/policy_server.dat +ssh "$HUB_SSH" "sudo cat /var/cfengine/ppkeys/localhost.pub" > hub.pub ``` -Put the hub's key into the client's trusted keys: +Upload it to the client: ```command -scp $HUB_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${HUB_KEY}.pub +scp ./hub.pub "$CLIENT_SSH":hub.pub ``` -### Install the clients public key on the hub - -Put the client's key into the hub's trusted keys. -So on the hub, run: +And use `cf-key` to trust the key: ```command -scp $CLIENT_IP:/var/cfengine/ppkeys/localhost.pub /var/cfengine/ppkeys/root-${CLIENT_KEY}.pub +ssh "$CLIENT_SSH" "sudo cf-key --trust-key hub.pub" ``` -### Start the binaries +### Start CFEngine on the client with a bootstrap command Now that keys are distributed, trust is established. We can run the normal bootstrap command with one crucial difference: @@ -197,7 +222,7 @@ We can run the normal bootstrap command with one crucial difference: This will start the normal CFEngine services (`cf-execd`, `cf-serverd`, etc.): ``` -cf-agent --trust-server no --bootstrap $HUB_IP +ssh "$CLIENT_SSH" "cf-agent --trust-server no --bootstrap $BOOTSTRAP_IP" ``` When we connect to the hubs IP address, if there is another server answering, a potential [man-in-the-middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack), it will not work. From 1d213d0016e63b5f030e22afc619353e974beace Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Thu, 14 Mar 2024 15:46:54 +0100 Subject: [PATCH 8/9] Secure bootstrap: Small fixups and improvements Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index 2eec917f1..ed7ef8886 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -9,18 +9,19 @@ This guide assumes you already have a working CFEngine hub (installed and bootst See the [Getting started guide][Getting started] for an introduction to CFEngine and how to install it. CFEngine's trust model is based on the secure exchange of keys. +Since it's using mutual authentication, this trust goes in both directions. +Both the client and the hub refuse to communicate with an unknown, untrusted host. Usually, when getting started with CFEngine, this step is automated as a dead-simple "bootstrap" procedure: ```command cf-agent --bootstrap ``` -CFEngine uses mutual authentication, so this trust goes in both directions. -Both the client and the hub refuse to communicate with an unknown, untrusted host. +However, this is in the default configuration, and there are several limitations and implications of this; ## Default configuration -In the default configuration, the policy server (cf-serverd) on the hub machine trusts incoming connections from the same `/16` subnet. +In the default configuration, the policy server (`cf-serverd`) on the hub machine trusts incoming connections from the same `/16` subnet. This means that: * Bootstrapping new clients will work as long as the 2 first numbers in the IP address are identical ([IPv4 dot decimal representation](https://en.wikipedia.org/wiki/Dot-decimal_notation)) . @@ -56,6 +57,17 @@ If you are using CFEngine Build, you can use [this module](https://build.cfengin Once this is set, you are no longer using the default value explained above (the `/16` subnet). This variable controls 3 different aspects: IP addresses allowed to connect, IP addresses to automatically trust keys from, and IP addresses allowed to fetch policy files. +At this point, you can run bootstrap on the client to the hub using automatic trust: + +```command +cf-agent --bootstrap 1.2.3.4 +``` + +If the IP addresses are correct, keys will be automatically exchanged, and hosts will start using encrypted communication over TLS, with mutual authentication. +At this point CFEngine works on your hub and client, even if they are not on the same `/16` subnet. +If this is your first time testing CFEngine, feel free to stop reading here and test the various features of Mission Portal, start writing policy, etc. +In the sections below, we will explore the security implications of this setup further, and show more secure approaches. + **Tip:** Setting the variable to `["0.0.0.0/0"]` will open up your hub to all IPv4 addresses, the entire internet. This is generally not recommended, but can make sense if you disable automatic trust (shown below), need to support clients connecting from the public internet, and/or want to manage firewalling restrictions outside of CFEngine. @@ -90,6 +102,12 @@ When combined with the variable above, you can create a very restricted setup: Only those 2 IP addresses are allowed to connect, and they must use their existing keys, no new keys are automatically trusted. +With what we've discussed up until now, we still need to _trust the network_ for limited periods of time, when we are bootstrapping new hosts. +(Assuming that we are really communicating with the host we intend to, and that there aren't additional malicious hosts connecting from the same IP addresses / subnets). +This is sometimes acceptable, especially if you are just testing CFEngine in a disposable and isolated environment. +However, in a production setup it is recommended to exchange keys in the most secure / trusted method available. +Below, we will show how. + ## Key location and generation If you are installing CFEngine using one of our official packages, keys are automatically generated and you can see them in the expected location: @@ -104,15 +122,16 @@ sudo ls /var/cfengine/ppkeys ``` The keypair of the host itself is always in the `localhost.pub` and `localhost.priv` files. -Additional keypairs from the hosts CFEngine is talking to over the network are in the other `.pub` files. +Additional public keys from the hosts CFEngine is talking to over the network are in the other `.pub` files. +The filename has a SHA checksum of the public key file - this is the CFEngine hosts unique ID (in Mission Portal, our API, PostgreSQL and LMDB databases, etc.). **Recommendation:** Don't copy, transfer, open, or share the private key (`localhost.priv`). It is a secret - putting it in more places is not necessary and increases the chances it could be compromised. When distributing keys for establishing trust, we are distributing the public keys (`.pub` files). -If you are compiling CFEngine from source, or spawning a new VM based on an image / snapshot without keys inside, you can generate a new keypair: +If you are compiling CFEngine from source, or spawning a new VM based on a snapshot without keys inside, you can generate a new keypair: -``` +```command sudo cf-key ``` @@ -133,7 +152,7 @@ BOOTSTRAP_IP="1.2.3.4" HUB_SSH="ubuntu@1.2.3.4" CLIENT_SSH="ubuntu@4.3.2.1" Edit the 3 variables according to your situation, they represent: -* `BOOTSTRAP_IP` - The IP address of the hub, which you want your client to bootstrap to (connect to). +* `BOOTSTRAP_IP` - The IP address of the hub, which you want `cf-agent` on the client to bootstrap to (connect to). * `HUB_SSH` - The username / IP combination you would use to connect to the hub with SSH. * `CLIENT_SSH` - The username / IP combination you would use to connect to the hub with SSH. @@ -221,7 +240,7 @@ We can run the normal bootstrap command with one crucial difference: `--trust-server no` tells the agent to **not** automatically trust an unknown key on the other end. This will start the normal CFEngine services (`cf-execd`, `cf-serverd`, etc.): -``` +```command ssh "$CLIENT_SSH" "cf-agent --trust-server no --bootstrap $BOOTSTRAP_IP" ``` From 80353b6511665a2bdbf1366333a2b4fbcd1f35c1 Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Thu, 14 Mar 2024 20:31:47 +0100 Subject: [PATCH 9/9] Secure bootstrap: Fixes based on Nick's review comments Signed-off-by: Ole Herman Schumacher Elgesem --- .../installation/secure-bootstrap.markdown | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/getting-started/installation/secure-bootstrap.markdown b/getting-started/installation/secure-bootstrap.markdown index ed7ef8886..56a564206 100644 --- a/getting-started/installation/secure-bootstrap.markdown +++ b/getting-started/installation/secure-bootstrap.markdown @@ -38,18 +38,18 @@ In the end, you will not be running these commands manually, but rather putting ## Allowing only specific IP addresses / subnets -In order to specify and limit which hosts (IP addresses) are considered trusted and allowed to connect and fetch policy files, you can put the trusted IP addresses and subnets into the `acl` variable: +In order to specify and limit which hosts (IP addresses) are considered trusted and allowed to connect and fetch policy files, you can put the trusted IP addresses and subnets into the ```acl``` variable: ```json [file=/var/cfengine/masterfiles/def.json] { "variables": { - "default:def.acl": ["1.2.3.4", "4.3.2.1"] + "default:def.acl": ["192.0.2.42", "198.51.100.7"] } } ``` -**Important:** Replace `1.2.3.4` with the IP address of your hub, `4.3.2.1` with the IP address of your client, and extend the list with any additional IP addresses / subnets. +**Important:** Replace `192.0.2.42` with the IP address of your hub, `198.51.100.7` with the IP address of your client, and extend the list with any additional IP addresses / subnets. If you are using CFEngine Build, you can use [this module](https://build.cfengine.com/modules/allow-hosts/), putting the IP addresses as module input, or add the json file above to your project. (Save it as a file called `def.json` and do `cfbs add ./def.json`). @@ -60,7 +60,7 @@ This variable controls 3 different aspects: IP addresses allowed to connect, IP At this point, you can run bootstrap on the client to the hub using automatic trust: ```command -cf-agent --bootstrap 1.2.3.4 +cf-agent --bootstrap 192.0.2.42 ``` If the IP addresses are correct, keys will be automatically exchanged, and hosts will start using encrypted communication over TLS, with mutual authentication. @@ -94,7 +94,7 @@ When combined with the variable above, you can create a very restricted setup: [file=/var/cfengine/masterfiles/def.json] { "variables": { - "default:def.acl": ["1.2.3.4", "4.3.2.1"], + "default:def.acl": ["192.0.2.42", "198.51.100.7"], "default:def.trustkeysfrom": [] } } @@ -147,7 +147,7 @@ The same applies to passwordless sudo - we're using sudo commands without passwo Assuming you are sitting on a laptop / workstation, and have network and SSH access to both the client and the hub, first set up some variables for each of them: ```command -BOOTSTRAP_IP="1.2.3.4" HUB_SSH="ubuntu@1.2.3.4" CLIENT_SSH="ubuntu@4.3.2.1" +BOOTSTRAP_IP="192.0.2.42" HUB_SSH="ubuntu@192.0.2.42" CLIENT_SSH="ubuntu@198.51.100.7" ``` Edit the 3 variables according to your situation, they represent: