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. |