]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/openssl_keys.py
f9a7f7edecca96a86fb24b864ffd653ca82d0bcd
2 Generates and installs a signed SSL certificate.
8 from teuthology
import misc
9 from teuthology
.exceptions
import ConfigError
10 from teuthology
.orchestra
import run
11 from teuthology
.task
import Task
13 log
= logging
.getLogger(__name__
)
15 class OpenSSLKeys(Task
):
18 Generates and installs a signed SSL certificate.
20 To create a self-signed certificate:
24 root: # results in root.key and root.crt
26 # [required] make the private key and certificate available in this client's test directory
29 # common name, defaults to `hostname`. chained certificates must not share a common name
32 # private key type for -newkey, defaults to rsa:2048
35 # install the certificate as trusted on these clients:
36 install: [client.0, client.1]
39 To create a certificate signed by a ca certificate:
42 root: (self-signed certificate as above)
48 # use another ssl certificate (by 'name') as the certificate authority
49 ca: root # --CAkey=root.key -CA=root.crt
51 # embed the private key in the certificate file
55 def __init__(self
, ctx
, config
):
56 super(OpenSSLKeys
, self
).__init
__(ctx
, config
)
61 # global dictionary allows other tasks to look up certificate paths
62 if not hasattr(self
.ctx
, 'ssl_certificates'):
63 self
.ctx
.ssl_certificates
= {}
65 # use testdir/ca as a working directory
66 self
.cadir
= '/'.join((misc
.get_testdir(self
.ctx
), 'ca'))
67 # make sure self-signed certs get added first, they don't have 'ca' field
68 configs
= sorted(self
.config
.items(), key
=lambda x
: 'ca' in x
[1])
69 for name
, config
in configs
:
70 # names must be unique to avoid clobbering each others files
71 if name
in self
.ctx
.ssl_certificates
:
72 raise ConfigError('ssl: duplicate certificate name {}'.format(name
))
74 # create the key and certificate
75 cert
= self
.create_cert(name
, config
)
77 self
.ctx
.ssl_certificates
[name
] = cert
78 self
.certs
.append(cert
)
80 # install as trusted on the requested clients
81 for client
in config
.get('install', []):
82 installed
= self
.install_cert(cert
, client
)
83 self
.installed
.append(installed
)
87 Clean up any created/installed certificate files.
89 for cert
in self
.certs
:
90 self
.remove_cert(cert
)
92 for installed
in self
.installed
:
93 self
.uninstall_cert(installed
)
95 def create_cert(self
, name
, config
):
97 Create a certificate with the given configuration.
99 cert
= argparse
.Namespace()
101 cert
.key_type
= config
.get('key-type', 'rsa:2048')
103 cert
.client
= config
.get('client', None)
105 raise ConfigError('ssl: missing required field "client"')
107 (cert
.remote
,) = self
.ctx
.cluster
.only(cert
.client
).remotes
.keys()
109 cert
.remote
.run(args
=['mkdir', '-p', self
.cadir
])
111 cert
.key
= f
'{self.cadir}/{cert.name}.key'
112 cert
.certificate
= f
'{self.cadir}/{cert.name}.crt'
115 add_san_default
= False
116 cn
= config
.get('cn', '')
118 cn
= cert
.remote
.hostname
119 add_san_default
= True
120 if config
.get('add-san', add_san_default
):
121 ext
= f
'{self.cadir}/{cert.name}.ext'
122 san_ext
= ['-extfile', ext
]
124 # provide the common name in -subj to avoid the openssl command prompts
125 subject
= f
'/CN={cn}'
127 # if a ca certificate is provided, use it to sign the new certificate
128 ca
= config
.get('ca', None)
130 # the ca certificate must have been created by a prior ssl task
131 ca_cert
= self
.ctx
.ssl_certificates
.get(ca
, None)
133 raise ConfigError(f
'ssl: ca {ca} not found for certificate {cert.name}')
135 csr
= f
'{self.cadir}/{cert.name}.csr'
136 srl
= f
'{self.cadir}/{ca_cert.name}.srl'
137 remove_files
= ['rm', csr
, srl
]
139 # these commands are run on the ca certificate's client because
140 # they need access to its private key and cert
142 # generate a private key and signing request
143 ca_cert
.remote
.run(args
=['openssl', 'req', '-nodes',
144 '-newkey', cert
.key_type
, '-keyout', cert
.key
,
145 '-out', csr
, '-subj', subject
])
148 remove_files
.append(ext
)
149 ca_cert
.remote
.write_file(path
=ext
,
150 data
='subjectAltName = DNS:{},IP:{}'.format(
152 config
.get('ip', cert
.remote
.ip_address
)))
154 # create the signed certificate
155 ca_cert
.remote
.run(args
=['openssl', 'x509', '-req', '-in', csr
,
156 '-CA', ca_cert
.certificate
, '-CAkey', ca_cert
.key
, '-CAcreateserial',
157 '-out', cert
.certificate
, '-days', '365', '-sha256'] + san_ext
)
159 ca_cert
.remote
.run(args
=remove_files
) # clean up the signing request and serial
161 # verify the new certificate against its ca cert
162 ca_cert
.remote
.run(args
=['openssl', 'verify',
163 '-CAfile', ca_cert
.certificate
, cert
.certificate
])
165 if cert
.remote
!= ca_cert
.remote
:
166 # copy to remote client
167 self
.remote_copy_file(ca_cert
.remote
, cert
.certificate
, cert
.remote
, cert
.certificate
)
168 self
.remote_copy_file(ca_cert
.remote
, cert
.key
, cert
.remote
, cert
.key
)
169 # clean up the local copies
170 ca_cert
.remote
.run(args
=['rm', cert
.certificate
, cert
.key
])
171 # verify the remote certificate (requires ca to be in its trusted ca certificate store)
172 cert
.remote
.run(args
=['openssl', 'verify', cert
.certificate
])
174 # otherwise, generate a private key and use it to self-sign a new certificate
175 cert
.remote
.run(args
=['openssl', 'req', '-x509', '-nodes',
176 '-newkey', cert
.key_type
, '-keyout', cert
.key
,
177 '-days', '365', '-out', cert
.certificate
, '-subj', subject
])
179 if config
.get('embed-key', False):
180 # append the private key to the certificate file
181 cert
.remote
.run(args
=['cat', cert
.key
, run
.Raw('>>'), cert
.certificate
])
185 def remove_cert(self
, cert
):
187 Delete all of the files associated with the given certificate.
189 # remove the private key and certificate
190 cert
.remote
.run(args
=['rm', '-f', cert
.certificate
, cert
.key
])
192 # remove ca subdirectory if it's empty
193 cert
.remote
.run(args
=['rmdir', '--ignore-fail-on-non-empty', self
.cadir
])
195 def install_cert(self
, cert
, client
):
197 Install as a trusted ca certificate on the given client.
199 (remote
,) = self
.ctx
.cluster
.only(client
).remotes
.keys()
201 installed
= argparse
.Namespace()
202 installed
.remote
= remote
204 if remote
.os
.package_type
== 'deb':
205 installed
.path
= '/usr/local/share/ca-certificates/{}.crt'.format(cert
.name
)
206 installed
.command
= ['sudo', 'update-ca-certificates']
208 installed
.path
= '/usr/share/pki/ca-trust-source/anchors/{}.crt'.format(cert
.name
)
209 installed
.command
= ['sudo', 'update-ca-trust']
212 if remote
!= cert
.remote
:
213 # copy into remote cadir (with mkdir if necessary)
214 remote
.run(args
=['mkdir', '-p', self
.cadir
])
215 self
.remote_copy_file(cert
.remote
, cert
.certificate
, remote
, cert
.certificate
)
216 cp_or_mv
= 'mv' # move this remote copy into the certificate store
218 # install into certificate store as root
219 remote
.run(args
=['sudo', cp_or_mv
, cert
.certificate
, installed
.path
])
220 remote
.run(args
=installed
.command
)
224 def uninstall_cert(self
, installed
):
226 Uninstall a certificate from the trusted certificate store.
228 installed
.remote
.run(args
=['sudo', 'rm', installed
.path
])
229 installed
.remote
.run(args
=installed
.command
)
231 def remote_copy_file(self
, from_remote
, from_path
, to_remote
, to_path
):
233 Copies a file from one remote to another.
235 The remotes don't have public-key auth for 'scp' or misc.copy_file(),
236 so this copies through an intermediate local tmp file.
238 log
.info('copying from {}:{} to {}:{}...'.format(from_remote
, from_path
, to_remote
, to_path
))
239 local_path
= from_remote
.get_file(from_path
)
241 to_remote
.put_file(local_path
, to_path
)
243 os
.remove(local_path
)