]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/vault.py
2 Deploy and configure Vault for Teuthology
11 from six
.moves
import http_client
12 from six
.moves
.urllib
.parse
import urljoin
14 from teuthology
import misc
as teuthology
15 from teuthology
import contextutil
16 from teuthology
.orchestra
import run
17 from teuthology
.exceptions
import ConfigError
20 log
= logging
.getLogger(__name__
)
23 def assign_ports(ctx
, config
, initial_port
):
25 Assign port numbers starting from @initial_port
29 for remote
, roles_for_host
in ctx
.cluster
.remotes
.items():
30 for role
in roles_for_host
:
32 role_endpoints
[role
] = (remote
.name
.split('@')[1], port
)
38 @contextlib.contextmanager
39 def download(ctx
, config
):
41 Download Vault Release from Hashicopr website.
42 Remove downloaded file upon exit.
44 assert isinstance(config
, dict)
45 log
.info('Downloading Vault...')
46 testdir
= teuthology
.get_testdir(ctx
)
48 for (client
, cconf
) in config
.items():
49 install_url
= cconf
.get('install_url')
50 install_sha256
= cconf
.get('install_sha256')
51 if not install_url
or not install_sha256
:
52 raise ConfigError("Missing Vault install_url and/or install_sha256")
53 install_zip
= path
.join(testdir
, 'vault.zip')
54 install_dir
= path
.join(testdir
, 'vault')
56 log
.info('Downloading Vault...')
57 ctx
.cluster
.only(client
).run(
58 args
=['curl', '-L', install_url
, '-o', install_zip
])
60 log
.info('Verifying SHA256 signature...')
61 ctx
.cluster
.only(client
).run(
62 args
=['echo', ' '.join([install_sha256
, install_zip
]), run
.Raw('|'),
63 'sha256sum', '--check', '--status'])
65 log
.info('Extracting vault...')
66 ctx
.cluster
.only(client
).run(args
=['mkdir', '-p', install_dir
])
67 # Using python in case unzip is not installed on hosts
68 ctx
.cluster
.only(client
).run(
69 args
=['python', '-m', 'zipfile', '-e', install_zip
, install_dir
])
74 log
.info('Removing Vault...')
75 testdir
= teuthology
.get_testdir(ctx
)
77 ctx
.cluster
.only(client
).run(
78 args
=['rm', '-rf', install_dir
, install_zip
])
81 def get_vault_dir(ctx
):
82 return '{tdir}/vault'.format(tdir
=teuthology
.get_testdir(ctx
))
85 @contextlib.contextmanager
86 def run_vault(ctx
, config
):
87 assert isinstance(config
, dict)
89 for (client
, cconf
) in config
.items():
90 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
91 cluster_name
, _
, client_id
= teuthology
.split_role(client
)
93 _
, port
= ctx
.vault
.endpoints
[client
]
94 listen_addr
= "0.0.0.0:{}".format(port
)
96 root_token
= ctx
.vault
.root_token
= cconf
.get('root_token', 'root')
98 log
.info("Starting Vault listening on %s ...", listen_addr
)
101 '-dev-listen-address={}'.format(listen_addr
),
102 '-dev-no-store-token',
103 '-dev-root-token-id={}'.format(root_token
)
106 cmd
= "chmod +x {vdir}/vault && {vdir}/vault server {vargs}".format(vdir
=get_vault_dir(ctx
), vargs
=" ".join(v_params
))
108 ctx
.daemons
.add_daemon(
109 remote
, 'vault', client_id
,
110 cluster
=cluster_name
,
111 args
=['bash', '-c', cmd
, run
.Raw('& { read; kill %1; }')],
112 logger
=log
.getChild(client
),
114 cwd
=get_vault_dir(ctx
),
122 log
.info('Stopping Vault instance')
123 ctx
.daemons
.get_daemon('vault', client_id
, cluster_name
).stop()
126 @contextlib.contextmanager
127 def setup_vault(ctx
, config
):
129 Mount Transit or KV version 2 secrets engine
131 (cclient
, cconfig
) = next(iter(config
.items()))
132 engine
= cconfig
.get('engine')
135 log
.info('Mounting kv version 2 secrets engine')
136 mount_path
= '/v1/sys/mounts/kv'
143 elif engine
== 'transit':
144 log
.info('Mounting transit secrets engine')
145 mount_path
= '/v1/sys/mounts/transit'
150 raise Exception("Unknown or missing secrets engine")
152 send_req(ctx
, cconfig
, cclient
, mount_path
, json
.dumps(data
))
156 def send_req(ctx
, cconfig
, client
, path
, body
, method
='POST'):
157 host
, port
= ctx
.vault
.endpoints
[client
]
158 req
= http_client
.HTTPConnection(host
, port
, timeout
=30)
159 token
= cconfig
.get('root_token', 'atoken')
160 log
.info("Send request to Vault: %s:%s at %s with token: %s", host
, port
, path
, token
)
161 headers
= {'X-Vault-Token': token
}
162 req
.request(method
, path
, headers
=headers
, body
=body
)
163 resp
= req
.getresponse()
164 log
.info(resp
.read())
165 if not (resp
.status
>= 200 and resp
.status
< 300):
166 raise Exception("Request to Vault server failed with status %d" % resp
.status
)
170 @contextlib.contextmanager
171 def create_secrets(ctx
, config
):
172 (cclient
, cconfig
) = next(iter(config
.items()))
173 engine
= cconfig
.get('engine')
174 prefix
= cconfig
.get('prefix')
175 secrets
= cconfig
.get('secrets')
177 raise ConfigError("No secrets specified, please specify some.")
179 for secret
in secrets
:
181 path
= secret
['path']
183 raise ConfigError('Missing "path" field in secret')
189 "key": secret
['secret']
193 raise ConfigError('Missing "secret" field in secret')
194 elif engine
== 'transit':
195 data
= {"exportable": "true"}
197 raise Exception("Unknown or missing secrets engine")
199 send_req(ctx
, cconfig
, cclient
, urljoin(prefix
, path
), json
.dumps(data
))
201 log
.info("secrets created")
205 @contextlib.contextmanager
206 def task(ctx
, config
):
208 Deploy and configure Vault
210 Example of configuration:
216 root_token: test_root_token
220 - path: kv/teuthology/key_a
221 secret: YmluCmJvb3N0CmJvb3N0LWJ1aWxkCmNlcGguY29uZgo=
222 - path: kv/teuthology/key_b
223 secret: aWIKTWFrZWZpbGUKbWFuCm91dApzcmMKVGVzdGluZwo=
225 all_clients
= ['client.{id}'.format(id=id_
)
226 for id_
in teuthology
.all_roles_of_type(ctx
.cluster
, 'client')]
229 if isinstance(config
, list):
230 config
= dict.fromkeys(config
)
232 overrides
= ctx
.config
.get('overrides', {})
233 # merge each client section, not the top level.
234 for client
in config
.keys():
235 if not config
[client
]:
237 teuthology
.deep_merge(config
[client
], overrides
.get('vault', {}))
239 log
.debug('Vault config is %s', config
)
241 ctx
.vault
= argparse
.Namespace()
242 ctx
.vault
.endpoints
= assign_ports(ctx
, config
, 8200)
243 ctx
.vault
.root_token
= None
244 ctx
.vault
.prefix
= config
[client
].get('prefix')
245 ctx
.vault
.engine
= config
[client
].get('engine')
247 with contextutil
.nested(
248 lambda: download(ctx
=ctx
, config
=config
),
249 lambda: run_vault(ctx
=ctx
, config
=config
),
250 lambda: setup_vault(ctx
=ctx
, config
=config
),
251 lambda: create_secrets(ctx
=ctx
, config
=config
)