Multiple Certificate Authorities

Version 2 (Anonymous, 03/13/2010 08:01 pm)

1 1
# NOTE
2 1
3 1
This procedure is currently a work in progress and as such does not
4 1
completely work with puppet. The certificate authority setup
5 1
procedure has been well tested, but there's an issue with the
6 1
bundle of certificates used to authenticate remote connections to
7 1
the puppet master server. Work is underway to sort the final issues
8 1
out.
9 1
10 1
WIP - 25 Jan 2010 - eric
11 1
12 1
# Multiple Certificate Authorities
13 1
14 1
All client/server communication is secured using standard SSL x509
15 1
cryptographic certificates. In the default configuration of puppet,
16 1
the master server acts as a certificate authority which signs
17 1
client certificate signing requests (CSR's). The certificate of
18 1
this authority is a self-signed certificate, and all authentication
19 1
is performed using signed certificates.
20 1
21 1
Author: Jeff McCune Date: 5 March 2007
22 1
23 1
# Motivation
24 1
25 1
As a configuration grows, the need to bring multiple masters online
26 1
may emerge. Leveraging the x509 public key infrastructure, we're
27 1
able to adapt a single master puppet deployment to multiple
28 1
masters, each acting as their own certificate authority, and each
29 1
mutually trusting each other.
30 1
31 1
# Procedure
32 1
33 1
We'll create an example PKI:
34 1
35 1
I highly recommend creating all of this on encrypted storage. If
36 1
you're using a Mac, this can easily be accomplished with:
37 1
38 1
    hdiutil create CA.sparseimage -encryption -size 64M -fs "HFS+J" -volname CA
39 1
40 1
4 CA's:
41 1
42 1
-   CA0: Root Authority
43 1
-   CA1: Signing Authority
44 1
-   CA2: Puppet Master 1 (host specific CA)
45 1
-   CA3: Puppet Master 2 (host specific CA)
46 1
47 1
Discussion: The Root CA is long-lived and rarely used. It should
48 1
only be used to sign the certificate of subsidiary CA's within the
49 1
organization. It's private key should be locked tightly away in a
50 1
vault and never used, except to revoke compromised CA private
51 1
keys.
52 1
53 1
The Signing CA should also only be used to sign off on other CA
54 1
certificates. This allows us to use it whenever needed, and revoke
55 1
it using Root CA if it's ever lost.
56 1
57 1
The Puppet Master CA's are both signed by Signing CA1. They will
58 1
sign requests from clients running puppetd.
59 1
60 1
In one configuration, servers could check a ca-bundle which
61 1
includes only the chain leading to the certificate which signs
62 1
client certificates (CA2 in the list above). This ensures only
63 1
clients with valid certificates from a specific CA are able to
64 1
connect to the service. Likewise, clients will only trust the chain
65 1
leading to the certificate used to sign the server's SSL
66 1
certificate (CA3), which ensures clients will only trust servers
67 1
and not other clients.
68 1
69 1
Alternatively, CA2 and CA3 can be load-balanced puppetmasters which
70 1
can each verify clients issued by the other server. It's important
71 1
to keep distinct certificates per issuer so you don't get duplicate
72 1
(issuer,serial number) tuples which would prevent revocation from
73 1
working cleanly.
74 1
75 1
Setup: Environment variables are our friends:
76 1
77 1
    SERVER1="mw430mb"
78 1
    SERVER2="manage"
79 1
    DOMAIN="math.ohio-state.edu"
80 1
81 1
Make directories for each CA:
82 1
83 1
    mkdir 01_RootCA 02_SigningCA 10_${SERVER1}.${DOMAIN}_CA 11_${SERVER2}.${DOMAIN}_CA
84 1
    curl -O http://sial.org/howto/openssl/ca/Makefile
85 1
    curl -O http://sial.org/howto/openssl/ca/openssl.cnf
86 1
    cp openssl.cnf openssl.cnf.orig
87 1
88 1
Now, edit openssl.cnf to include valid subject information for each
89 1
CA.
90 1
91 1
Here's how I set things in openssl.cnf:
92 1
93 1
    [ root_ca_distinguished_name ]
94 1
    commonName = OSU Math Root Authority
95 1
    countryName = US
96 1
    stateOrProvinceName = Ohio
97 1
    localityName = Columbus
98 1
    0.organizationName = The Ohio State University
99 1
    organizationalUnitName = Department of Mathematics
100 1
101 1
In order to match the certificate extensions Puppet creates, you
102 1
also need to set the following attributes in the [v3\_ca] section.
103 1
These settings make the CA:TRUE bits critical extensions, and flag
104 1
the certificate as only usable for signing other certificates and
105 1
revocation lists. You'll want to set these extensions for all of
106 1
the CA's we create.
107 1
108 1
Just replace sial's v3\_ca section with this section:
109 1
110 1
    [ v3_ca ]
111 1
    ## CA Extensions
112 1
    # PKIX recommendations.
113 1
    subjectKeyIdentifier=hash
114 1
    authorityKeyIdentifier=keyid,issuer:always
115 1
    basicConstraints = critical,CA:true
116 1
    keyUsage = keyCertSign, cRLSign
117 1
118 1
Copy the initial configuration file to each of the 4 CA
119 1
directories:
120 1
121 1
    cp openssl.cnf Makefile 01_*CA*/
122 1
    cp openssl.cnf Makefile 02_*CA*/
123 1
    cp openssl.cnf Makefile 10_${SERVER1}.${DOMAIN}_CA/
124 1
    cp openssl.cnf Makefile 11_${SERVER2}.${DOMAIN}_CA/
125 1
126 1
I then leave everything except the commonName and nsCaRevocationUrl
127 1
field (in the v3\_req section) the same, and edit the other 3 to
128 1
descriptive CN fields:
129 1
130 1
    commonName = OSU Math Root Authority
131 1
    commonName = OSU Math Signing Authority
132 1
    commonName = server1.math.ohio-state.edu
133 1
    commonName = server2.math.ohio-state.edu
134 1
135 1
For modern OpenSSL binaries (0.9.8a or higher) you'll need to
136 1
modify the '-newkey' parameter in the Makefile as its comment
137 1
describes -- specify '-newkey rsa:2048' to explicitly set a bit
138 1
length
139 1
140 1
Also, before you create the root ca, you might want to create a
141 1
certificate that lives longer than 5 years. Edit the Makefile for
142 1
the Root CA and change the openssl command in the init section to
143 1
9125 days or what not.
144 1
145 1
Then, run "make init" in each of the 4 directories. This creates
146 1
\*self signed\* CA certificates. We need to have each CA sign off
147 1
on the one below it next.
148 1
149 1
If you look at the RootCA certificate, it's self signed, and this
150 1
is good. Note **CA:TRUE** under Basic Constraints and the Issuer is
151 1
itself.
152 1
153 1
    openssl x509 -in ca-cert.pem -text -noout
154 1
155 1
## Tier 2 CA (The Day to Day Signing CA)
156 1
157 1
We'll sign the Signing CA's certificate with the Root CA's private
158 1
key, replacing the self-signed certificate initially created with
159 1
make init.
160 1
161 1
We need to create a new Certificate Signing Request (CSR), and
162 1
create a cert from the CSR using the Root CA's private key. Here's
163 1
how:
164 1
165 1
    # Generate the new CSR
166 1
    cd 02_*CA*/
167 1
    openssl req -new -nodes -key private/ca-key.pem -config openssl.cnf -out ca.csr
168 1
    
169 1
    # Move the CSR from the Sub CA to the Root CA folder
170 1
    mv ca.csr ../01_*CA*
171 1
    
172 1
    # Sign the CSR
173 1
    cd ../01_*CA*/
174 1
    openssl ca -config openssl.cnf -extfile openssl.cnf -extensions v3_ca -in ca.csr -out ca.cert
175 1
    rm -f ca.csr
176 1
    mv ca.cert ../02_SubCA1/ca-cert.pem
177 1
178 1
Note the v3\_ca extension listed on the command line. This is very
179 1
important and what tells the Root CA to treat this request as one
180 1
from another CA rather than a request for a SSL client or server.
181 1
The key difference is that a CA certificate is used to identify
182 1
other things, while a SSL certificate use used solely to identify
183 1
the owner of it's private key.
184 1
185 1
Now, if you notice at the top of 02\_SubCA1/ca-cert.pem, the Issuer
186 1
is RootCA. We now have a certificate chain.
187 1
188 1
Ensure X509v3 **Basic Constraints: CA:TRUE** is set in the
189 1
ca-cert.pem file, if it's not, you just have a standard SSL
190 1
certificate and not a Certificate Authority certificate. SSL
191 1
certificates cannot be trusted as a certification authority and
192 1
will not work as expected. If you don't have CA:TRUE, make sure
193 1
you're using -extensions v3\_ca when signing the certificate.
194 1
195 1
Also ensure the v3 extensions
196 1
**Subject Key Identifier and Authority Key Identifier** are set
197 1
properly. These aid in the construction of trust chains, which is
198 1
exactly what we're setting up.
199 1
200 1
## Tier 3 CA's
201 1
202 1
Next, we'll repeat this process with the Client and Server CA,
203 1
using the Signing CA to sign their CSR's, rather than use the Root
204 1
CA. The Root CA directory should now be moved to a USB stick and
205 1
removed or better yet, printed out and locked in a vault. The PKI
206 1
philosophy is that the Root CA's private key shouldn't ever be used
207 1
again, unless a revocation of the Signing certificate is needed.
208 1
209 1
Create our bundle by concatenating the ca-cert.pem files into a
210 1
ca-bundle.crt file.
211 1
212 1
    cat {01_*CA*,02_*CA*,10_*CA*,11_*CA*}/ca-cert.pem >> ca-bundle.pem
213 1
214 1
If you have an existing self signed certificate, you may want to
215 1
add it to the bundle of trusted authorities now. For example, with
216 1
puppet:
217 1
218 1
    openssl x509 -in /var/lib/puppet/ca/ca_crt.pem -text >> ca-bundle.pem
219 1
220 1
# Host SSL certificates
221 1
222 1
Phew, now we've got all of our CA's setup. But we still don't have
223 1
anything suitable for encrypting network traffic, we only have keys
224 1
suitable for certifying other keys.
225 1
226 1
I make another folder specifically for host SSL data. This means
227 1
we'll use a separate private key and certificate for SSL use than
228 1
the data we already have for the CA.
229 1
230 1
    mkdir 20_ssl_host_certs
231 1
    cd 20_ssl_host_certs
232 1
    HOST="foo.math.ohio-state.edu"
233 1
    openssl req -new -nodes -newkey rsa:2048 -keyout "$HOST".key.pem -config openssl.cnf -out "$HOST".csr
234 1
235 1
I create an openssl.cnf file to keep in the 20\_ssl\_host\_certs
236 1
directory. It has everything filled out except the CN field, which
237 1
gets pulled from the HOST environment variable and looks like so:
238 1
239 1
    HOME = .
240 1
    RANDFILE = $ENV::HOME/.rnd
241 1
    
242 1
    [ req ]
243 1
    default_bits = 2048
244 1
    default_keyfile = private_key.pem
245 1
    distinguished_name = req_distinguished_name
246 1
    string_mask = nombstr
247 1
    req_extensions = v3_req # The extensions to add to a certificate request
248 1
    
249 1
    [ req_distinguished_name ]
250 1
    countryName = Country Name (2 letter code)
251 1
    countryName_default = US
252 1
    countryName_min = 2
253 1
    countryName_max = 2
254 1
    
255 1
    stateOrProvinceName = State or Province Name (full name)
256 1
    stateOrProvinceName_default = Ohio
257 1
    
258 1
    localityName = Locality Name (eg, city)
259 1
    localityName_default = Columbus
260 1
    
261 1
    0.organizationName = Organization Name (eg, company)
262 1
    0.organizationName_default = The Ohio State University
263 1
    organizationalUnitName = Unit Name (e.g. Department)
264 1
    organizationalUnitName_default = Department of Mathematics
265 1
    
266 1
    commonName = Common Name (e.g. the FQDN of the host)
267 1
    commonName_default = $ENV::HOST
268 1
    commonName_max = 64
269 1
    
270 1
    [ v3_req ]
271 1
    # Extensions to add to a certificate request
272 1
    basicConstraints = CA:FALSE
273 1
    keyUsage = nonRepudiation, digitalSignature, keyEncipherment
274 1
275 1
Take the CSR generated from the private key we just created and
276 1
move it into the CA directory:
277 1
278 1
    # For example;
279 1
    mv manage.math.ohio-state.edu.csr ../11_manage.math.ohio-state.edu_CA/
280 1
    cd ../11_manage.math.ohio-state.edu_CA/
281 1
282 1
Finally, sign the host SSL certificate with the CA's private key.
283 1
We're using a Tier-3 (host specific CA) for this purpose
284 1
285 1
    cd ../11_manage.math.ohio-state.edu_CA/
286 1
    make sign
287 1
288 1
Then, move the certificate back into the host SSL directory.
289 1
290 1
    mv manage.math.ohio-state.edu.cert ../20_ssl_host_certs/
291 1
292 1
You now have a private key and certificate signed by a trusted
293 1
authority suitable for SSL client/server use.
294 1
295 1
Here's how we can check the certificates:
296 1
297 1
    # First host's SSL cert
298 1
    openssl verify -verbose -purpose sslclient -CAfile ../ca-trusted-certs.pem mw430mb.math.ohio-state.edu.cert
299 1
    # Second host's SSL cert
300 1
    openssl verify -verbose -purpose sslclient -CAfile ../ca-trusted-certs.pem manage.math.ohio-state.edu.cert
301 1
302 1
## Useful OpenSSL commands
303 1
304 1
To extract a PEM encoded public key from a certificate:
305 1
306 1
    openssl x509 -pubkey -outform pem -noout < ca-cert.pem > ca-pub.pem
307 1
308 1
# Configure the Puppet Master
309 1
310 1
You'll need to configure the SSL server piece of the Puppet Master
311 1
to trust certificates signed by the authorities we just created.
312 1
313 1
    [certificates]
314 1
      localcacert = $ssldir/trusted-ca-bundle.pem
315 1
      hostprivkey = $ssldir/host_key.pem
316 1
      hostcert = $ssldir/host_cert.pem
317 1
      hostpubkey = $publickeydir/host_pub.pem
318 1
319 1
Note that trusted-ca-bundle.pem is the ca-bundle.pem file we
320 1
created above. This is also known as the Root Certificate Store, or
321 1
Trust Root, etc...
322 1
323 1
Next, we'll need to configure the Certificate Authority piece of
324 1
the Puppet Master to use the CA object we've created.
325 1
326 1
    [ca]
327 1
      cacert = $cadir/ca-cert.pem
328 1
      cakey = $cadir/ca-key.pem
329 1
      capub = $cadir/ca-pub.pem
330 1
331 1
That should do the trick.
332 1
333 1
# Troubleshooting
334 1
335 1
If you run into issues setting up this chain of trust, the
336 1
following commands will greatly help you.
337 1
338 1
To ensure you have your certificate correctly setup, eliminate
339 1
Puppet from the equation entirely. We're able to setup a SSL client
340 1
and server using only the openssl command:
341 1
342 1
On the master host:
343 1
344 1
    openssl s_server -Verify 10 -cert host_cert.pem -key host_key.pem -CAfile ca-bundle.pem
345 1
346 1
On the client host:
347 1
348 1
    cd /var/lib/puppet
349 1
    openssl s_client -connect puppet:4433 -verify 10 \
350 1
      -CAfile ssl/certs/ca.pem \
351 1
      -cert ssl/certs/$(hostname).pem \
352 1
      -key /Support/etc/puppet/ssl/private_keys/client_priv.pem
353 1
354 1
You may need to change CAfile ssl/certs/ca.pem to the path of your
355 1
ca-bundle.pem.
356 1
357 1
If you send anything via standard input to the client, it should
358 1
display on standard output of the server.