]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/pykmip.py
2 Deploy and configure PyKMIP for Teuthology
11 from io
import BytesIO
12 from teuthology
.orchestra
.daemon
import DaemonGroup
13 from teuthology
.orchestra
.remote
import Remote
17 from teuthology
import misc
as teuthology
18 from teuthology
import contextutil
19 from teuthology
.orchestra
import run
20 from teuthology
.packaging
import install_package
21 from teuthology
.packaging
import remove_package
22 from teuthology
.exceptions
import ConfigError
23 from tasks
.util
import get_remote_for_role
25 log
= logging
.getLogger(__name__
)
28 def get_pykmip_dir(ctx
):
29 return '{tdir}/pykmip'.format(tdir
=teuthology
.get_testdir(ctx
))
31 def run_in_pykmip_dir(ctx
, client
, args
, **kwargs
):
32 (remote
,) = [client
] if isinstance(client
,Remote
) else ctx
.cluster
.only(client
).remotes
.keys()
34 args
=['cd', get_pykmip_dir(ctx
), run
.Raw('&&'), ] + args
,
38 def run_in_pykmip_venv(ctx
, client
, args
, **kwargs
):
39 return run_in_pykmip_dir(ctx
, client
,
40 args
= ['.', '.pykmipenv/bin/activate',
44 @contextlib.contextmanager
45 def download(ctx
, config
):
47 Download PyKMIP from github.
48 Remove downloaded file upon exit.
50 The context passed in should be identical to the context
51 passed in to the main task.
53 assert isinstance(config
, dict)
54 log
.info('Downloading pykmip...')
55 pykmipdir
= get_pykmip_dir(ctx
)
57 for (client
, cconf
) in config
.items():
58 branch
= cconf
.get('force-branch', 'master')
59 repo
= cconf
.get('force-repo', 'https://github.com/OpenKMIP/PyKMIP')
60 sha1
= cconf
.get('sha1')
61 log
.info("Using branch '%s' for pykmip", branch
)
62 log
.info('sha1=%s', sha1
)
64 ctx
.cluster
.only(client
).run(
66 'git', 'clone', '-b', branch
, repo
,
71 run_in_pykmip_dir(ctx
, client
, [
72 'git', 'reset', '--hard', sha1
,
78 log
.info('Removing pykmip...')
80 ctx
.cluster
.only(client
).run(
81 args
=[ 'rm', '-rf', pykmipdir
],
84 _bindep_txt
= """# should be part of PyKMIP
85 libffi-dev [platform:dpkg]
86 libffi-devel [platform:rpm]
87 libssl-dev [platform:dpkg]
88 openssl-devel [platform:redhat]
89 libopenssl-devel [platform:suse]
90 libsqlite3-dev [platform:dpkg]
91 sqlite-devel [platform:rpm]
92 python-dev [platform:dpkg]
93 python-devel [(platform:redhat platform:base-py2)]
94 python3-dev [platform:dpkg]
95 python3-devel [(platform:redhat platform:base-py3) platform:suse]
96 python3 [platform:suse]
99 @contextlib.contextmanager
100 def install_packages(ctx
, config
):
102 Download the packaged dependencies of PyKMIP.
103 Remove install packages upon exit.
105 The context passed in should be identical to the context
106 passed in to the main task.
108 assert isinstance(config
, dict)
109 log
.info('Installing system dependenies for PyKMIP...')
112 for (client
, _
) in config
.items():
113 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
114 # use bindep to read which dependencies we need from temp/bindep.txt
115 fd
, local_temp_path
= tempfile
.mkstemp(suffix
='.txt',
117 os
.write(fd
, _bindep_txt
.encode())
119 fd
, remote_temp_path
= tempfile
.mkstemp(suffix
='.txt',
122 remote
.put_file(local_temp_path
, remote_temp_path
)
123 os
.remove(local_temp_path
)
124 run_in_pykmip_venv(ctx
, remote
, ['pip', 'install', 'bindep'])
125 r
= run_in_pykmip_venv(ctx
, remote
,
126 ['bindep', '--brief', '--file', remote_temp_path
],
128 check_status
=False) # returns 1 on success?
129 packages
[client
] = r
.stdout
.getvalue().decode().splitlines()
130 for dep
in packages
[client
]:
131 install_package(dep
, remote
)
135 log
.info('Removing system dependencies of PyKMIP...')
137 for (client
, _
) in config
.items():
138 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
139 for dep
in packages
[client
]:
140 remove_package(dep
, remote
)
142 @contextlib.contextmanager
143 def setup_venv(ctx
, config
):
145 Setup the virtualenv for PyKMIP using pip.
147 assert isinstance(config
, dict)
148 log
.info('Setting up virtualenv for pykmip...')
149 for (client
, _
) in config
.items():
150 run_in_pykmip_dir(ctx
, client
, ['python3', '-m', 'venv', '.pykmipenv'])
151 run_in_pykmip_venv(ctx
, client
, ['pip', 'install', '--upgrade', 'pip'])
152 run_in_pykmip_venv(ctx
, client
, ['pip', 'install', 'pytz', '-e', get_pykmip_dir(ctx
)])
155 def assign_ports(ctx
, config
, initial_port
):
157 Assign port numbers starting from @initial_port
161 for remote
, roles_for_host
in ctx
.cluster
.remotes
.items():
162 for role
in roles_for_host
:
164 r
= get_remote_for_role(ctx
, role
)
165 role_endpoints
[role
] = r
.ip_address
, port
, r
.hostname
168 return role_endpoints
170 def copy_policy_json(ctx
, cclient
, cconfig
):
171 run_in_pykmip_dir(ctx
, cclient
,
173 get_pykmip_dir(ctx
)+'/examples/policy.json',
174 get_pykmip_dir(ctx
)])
176 _pykmip_configuration
= """# configuration for pykmip
180 certificate_path={servercert}
184 policy_path={confdir}
185 enable_tls_client_auth=False
187 TLS_RSA_WITH_AES_128_CBC_SHA256
188 TLS_RSA_WITH_AES_256_CBC_SHA256
189 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
191 database_path={confdir}/pykmip.sqlite
195 certfile={clientcert}
198 ssl_version=PROTOCOL_TLSv1_2
201 def create_pykmip_conf(ctx
, cclient
, cconfig
):
202 log
.info('#0 cclient={} cconfig={}'.format(pprint
.pformat(cclient
),pprint
.pformat(cconfig
)))
203 (remote
,) = ctx
.cluster
.only(cclient
).remotes
.keys()
204 pykmip_ipaddr
, pykmip_port
, pykmip_hostname
= ctx
.pykmip
.endpoints
[cclient
]
205 log
.info('#1 ip,p,h {} {} {}'.format(pykmip_ipaddr
, pykmip_port
, pykmip_hostname
))
206 clientca
= cconfig
.get('clientca', None)
207 log
.info('#2 clientca {}'.format(clientca
))
209 servercert
= cconfig
.get('servercert', None)
210 log
.info('#3 servercert {}'.format(servercert
))
211 servercert
= ctx
.ssl_certificates
.get(servercert
)
212 log
.info('#4 servercert {}'.format(servercert
))
214 clientcert
= cconfig
.get('clientcert', None)
215 log
.info('#3 clientcert {}'.format(clientcert
))
216 clientcert
= ctx
.ssl_certificates
.get(clientcert
)
217 log
.info('#4 clientcert {}'.format(clientcert
))
218 clientca
= ctx
.ssl_certificates
.get(clientca
)
219 log
.info('#5 clientca {}'.format(clientca
))
220 if servercert
!= None:
221 serverkey
= servercert
.key
222 servercert
= servercert
.certificate
223 log
.info('#6 serverkey {} servercert {}'.format(serverkey
, servercert
))
224 if clientcert
!= None:
225 clientkey
= clientcert
.key
226 clientcert
= clientcert
.certificate
227 log
.info('#6 clientkey {} clientcert {}'.format(clientkey
, clientcert
))
229 clientca
= clientca
.certificate
230 log
.info('#7 clientca {}'.format(clientca
))
231 if servercert
== None or clientca
== None or serverkey
== None:
232 log
.info('#8 clientca {} serverkey {} servercert {}'.format(clientca
, serverkey
, servercert
))
233 raise ConfigError('pykmip: Missing/bad servercert or clientca')
234 pykmipdir
= get_pykmip_dir(ctx
)
235 kmip_conf
= _pykmip_configuration
.format(
236 ipaddr
=pykmip_ipaddr
,
239 hostname
=pykmip_hostname
,
242 clientcert
=clientcert
,
244 servercert
=servercert
246 fd
, local_temp_path
= tempfile
.mkstemp(suffix
='.conf',
248 os
.write(fd
, kmip_conf
.encode())
250 remote
.put_file(local_temp_path
, pykmipdir
+'/pykmip.conf')
251 os
.remove(local_temp_path
)
253 @contextlib.contextmanager
254 def configure_pykmip(ctx
, config
):
256 Configure pykmip paste-api and pykmip-api.
258 assert isinstance(config
, dict)
259 (cclient
, cconfig
) = next(iter(config
.items()))
261 copy_policy_json(ctx
, cclient
, cconfig
)
262 create_pykmip_conf(ctx
, cclient
, cconfig
)
268 def has_ceph_task(tasks
):
270 for name
, conf
in task
.items():
275 @contextlib.contextmanager
276 def run_pykmip(ctx
, config
):
277 assert isinstance(config
, dict)
278 if hasattr(ctx
, 'daemons'):
280 elif has_ceph_task(ctx
.config
['tasks']):
281 log
.info('Delay start pykmip so ceph can do once-only daemon logic')
287 ctx
.daemons
= DaemonGroup()
288 log
.info('Running pykmip...')
290 pykmipdir
= get_pykmip_dir(ctx
)
292 for (client
, _
) in config
.items():
293 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
294 cluster_name
, _
, client_id
= teuthology
.split_role(client
)
296 # start the public endpoint
297 client_public_with_id
= 'pykmip.public' + '.' + client_id
299 run_cmd
= 'cd ' + pykmipdir
+ ' && ' + \
300 '. .pykmipenv/bin/activate && ' + \
301 'HOME={}'.format(pykmipdir
) + ' && ' + \
302 'exec pykmip-server -f pykmip.conf -l ' + \
303 pykmipdir
+ '/pykmip.log & { read; kill %1; }'
305 ctx
.daemons
.add_daemon(
306 remote
, 'pykmip', client_public_with_id
,
307 cluster
=cluster_name
,
308 args
=['bash', '-c', run_cmd
],
309 logger
=log
.getChild(client
),
316 # sleep driven synchronization
321 log
.info('Stopping PyKMIP instance')
322 ctx
.daemons
.get_daemon('pykmip', client_public_with_id
,
325 make_keys_template
= """
326 from kmip.pie import client
327 from kmip import enums
331 from io import BytesIO
333 c = client.ProxyKmipClient(config_file="{replace-with-config-file-path}")
336 for kwargs in {replace-with-secrets}:
339 enums.CryptographicAlgorithm.AES,
341 operation_policy_name='default',
342 cryptographic_usage_mask=[
343 enums.CryptographicUsageMask.ENCRYPT,
344 enums.CryptographicUsageMask.DECRYPT
349 attrs = c.get_attributes(uid=key_id)
352 r[str(a.attribute_name)] = str(a.attribute_value)
354 print(json.dumps(rl))
357 @contextlib.contextmanager
358 def create_secrets(ctx
, config
):
360 Create and activate any requested keys in kmip
362 assert isinstance(config
, dict)
364 pykmipdir
= get_pykmip_dir(ctx
)
365 pykmip_conf_path
= pykmipdir
+ '/pykmip.conf'
366 my_output
= BytesIO()
367 for (client
,cconf
) in config
.items():
368 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
369 secrets
=cconf
.get('secrets')
371 secrets_json
= json
.dumps(cconf
['secrets'])
372 make_keys
= make_keys_template \
373 .replace("{replace-with-secrets}",secrets_json
) \
374 .replace("{replace-with-config-file-path}",pykmip_conf_path
)
376 remote
.run(args
=[run
.Raw('. cephtest/pykmip/.pykmipenv/bin/activate;' \
377 + 'python')], stdin
=make_keys
, stdout
= my_output
)
378 ctx
.pykmip
.keys
[client
] = json
.loads(my_output
.getvalue().decode())
384 @contextlib.contextmanager
385 def task(ctx
, config
):
387 Deploy and configure PyKMIP
389 Example of configuration:
396 rgw crypt s3 kms backend: kmip
397 rgw crypt kmip ca path: /home/ubuntu/cephtest/ca/kmiproot.crt
398 rgw crypt kmip client cert: /home/ubuntu/cephtest/ca/kmip-client.crt
399 rgw crypt kmip client key: /home/ubuntu/cephtest/ca/kmip-client.key
400 rgw crypt kmip kms key template: pykmip-$keyid
418 servercert: kmip-server
419 clientcert: kmip-client
425 use-pykmip-role: client.0
430 assert config
is None or isinstance(config
, list) \
431 or isinstance(config
, dict), \
432 "task pykmip only supports a list or dictionary for configuration"
433 all_clients
= ['client.{id}'.format(id=id_
)
434 for id_
in teuthology
.all_roles_of_type(ctx
.cluster
, 'client')]
437 if isinstance(config
, list):
438 config
= dict.fromkeys(config
)
440 overrides
= ctx
.config
.get('overrides', {})
441 # merge each client section, not the top level.
442 for client
in config
.keys():
443 if not config
[client
]:
445 teuthology
.deep_merge(config
[client
], overrides
.get('pykmip', {}))
447 log
.debug('PyKMIP config is %s', config
)
449 if not hasattr(ctx
, 'ssl_certificates'):
450 raise ConfigError('pykmip must run after the openssl_keys task')
453 ctx
.pykmip
= argparse
.Namespace()
454 ctx
.pykmip
.endpoints
= assign_ports(ctx
, config
, 5696)
457 with contextutil
.nested(
458 lambda: download(ctx
=ctx
, config
=config
),
459 lambda: setup_venv(ctx
=ctx
, config
=config
),
460 lambda: install_packages(ctx
=ctx
, config
=config
),
461 lambda: configure_pykmip(ctx
=ctx
, config
=config
),
462 lambda: run_pykmip(ctx
=ctx
, config
=config
),
463 lambda: create_secrets(ctx
=ctx
, config
=config
),