mirror of https://github.com/ceph/ceph
520 lines
19 KiB
Python
520 lines
19 KiB
Python
"""
|
|
Deploy and configure Barbican for Teuthology
|
|
"""
|
|
import argparse
|
|
import contextlib
|
|
import logging
|
|
import http
|
|
import json
|
|
import time
|
|
import math
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
from teuthology import misc as teuthology
|
|
from teuthology import contextutil
|
|
from teuthology.orchestra import run
|
|
from teuthology.exceptions import ConfigError
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def download(ctx, config):
|
|
"""
|
|
Download the Barbican from github.
|
|
Remove downloaded file upon exit.
|
|
|
|
The context passed in should be identical to the context
|
|
passed in to the main task.
|
|
"""
|
|
assert isinstance(config, dict)
|
|
log.info('Downloading barbican...')
|
|
testdir = teuthology.get_testdir(ctx)
|
|
for (client, cconf) in config.items():
|
|
branch = cconf.get('force-branch', 'master')
|
|
log.info("Using branch '%s' for barbican", branch)
|
|
|
|
sha1 = cconf.get('sha1')
|
|
log.info('sha1=%s', sha1)
|
|
|
|
ctx.cluster.only(client).run(
|
|
args=[
|
|
'bash', '-l'
|
|
],
|
|
)
|
|
ctx.cluster.only(client).run(
|
|
args=[
|
|
'git', 'clone',
|
|
'-b', branch,
|
|
'https://github.com/openstack/barbican.git',
|
|
'{tdir}/barbican'.format(tdir=testdir),
|
|
],
|
|
)
|
|
if sha1 is not None:
|
|
ctx.cluster.only(client).run(
|
|
args=[
|
|
'cd', '{tdir}/barbican'.format(tdir=testdir),
|
|
run.Raw('&&'),
|
|
'git', 'reset', '--hard', sha1,
|
|
],
|
|
)
|
|
try:
|
|
yield
|
|
finally:
|
|
log.info('Removing barbican...')
|
|
testdir = teuthology.get_testdir(ctx)
|
|
for client in config:
|
|
ctx.cluster.only(client).run(
|
|
args=[
|
|
'rm',
|
|
'-rf',
|
|
'{tdir}/barbican'.format(tdir=testdir),
|
|
],
|
|
)
|
|
|
|
def get_barbican_dir(ctx):
|
|
return '{tdir}/barbican'.format(tdir=teuthology.get_testdir(ctx))
|
|
|
|
def run_in_barbican_dir(ctx, client, args):
|
|
ctx.cluster.only(client).run(
|
|
args=['cd', get_barbican_dir(ctx), run.Raw('&&'), ] + args,
|
|
)
|
|
|
|
def run_in_barbican_venv(ctx, client, args):
|
|
run_in_barbican_dir(ctx, client,
|
|
['.',
|
|
'.barbicanenv/bin/activate',
|
|
run.Raw('&&')
|
|
] + args)
|
|
|
|
@contextlib.contextmanager
|
|
def setup_venv(ctx, config):
|
|
"""
|
|
Setup the virtualenv for Barbican using pip.
|
|
"""
|
|
assert isinstance(config, dict)
|
|
log.info('Setting up virtualenv for barbican...')
|
|
for (client, _) in config.items():
|
|
run_in_barbican_dir(ctx, client, ['virtualenv', '.barbicanenv'])
|
|
run_in_barbican_venv(ctx, client, ['pip', 'install', 'pytz', '-e', get_barbican_dir(ctx)])
|
|
yield
|
|
|
|
def assign_ports(ctx, config, initial_port):
|
|
"""
|
|
Assign port numbers starting from @initial_port
|
|
"""
|
|
port = initial_port
|
|
role_endpoints = {}
|
|
for remote, roles_for_host in ctx.cluster.remotes.items():
|
|
for role in roles_for_host:
|
|
if role in config:
|
|
role_endpoints[role] = (remote.name.split('@')[1], port)
|
|
port += 1
|
|
|
|
return role_endpoints
|
|
|
|
def set_authtoken_params(ctx, cclient, cconfig):
|
|
section_config_list = cconfig['keystone_authtoken'].items()
|
|
for config in section_config_list:
|
|
(name, val) = config
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['sed', '-i',
|
|
'/[[]filter:authtoken]/{p;s##'+'{} = {}'.format(name, val)+'#;}',
|
|
'etc/barbican/barbican-api-paste.ini'])
|
|
|
|
keystone_role = cconfig.get('use-keystone-role', None)
|
|
public_host, public_port = ctx.keystone.public_endpoints[keystone_role]
|
|
url = 'http://{host}:{port}/v3'.format(host=public_host,
|
|
port=public_port)
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['sed', '-i',
|
|
'/[[]filter:authtoken]/{p;s##'+'auth_uri = {}'.format(url)+'#;}',
|
|
'etc/barbican/barbican-api-paste.ini'])
|
|
admin_host, admin_port = ctx.keystone.admin_endpoints[keystone_role]
|
|
admin_url = 'http://{host}:{port}/v3'.format(host=admin_host,
|
|
port=admin_port)
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['sed', '-i',
|
|
'/[[]filter:authtoken]/{p;s##'+'auth_url = {}'.format(admin_url)+'#;}',
|
|
'etc/barbican/barbican-api-paste.ini'])
|
|
|
|
def fix_barbican_api_paste(ctx, cclient):
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['sed', '-i', '-n',
|
|
'/\\[pipeline:barbican_api]/ {p;n; /^pipeline =/ '+
|
|
'{ s/.*/pipeline = unauthenticated-context apiapp/;p;d } } ; p',
|
|
'./etc/barbican/barbican-api-paste.ini'])
|
|
|
|
def fix_barbican_api(ctx, cclient):
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['sed', '-i',
|
|
'/prop_dir =/ s#etc/barbican#{}/etc/barbican#'.format(get_barbican_dir(ctx)),
|
|
'bin/barbican-api'])
|
|
|
|
def copy_policy_json(ctx, cclient):
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['cp',
|
|
get_barbican_dir(ctx)+'/etc/barbican/policy.json',
|
|
get_barbican_dir(ctx)])
|
|
|
|
def create_barbican_conf(ctx, cclient):
|
|
barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
|
|
barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
|
|
port=barbican_port)
|
|
log.info("barbican url=%s", barbican_url)
|
|
|
|
run_in_barbican_dir(ctx, cclient,
|
|
['bash', '-c',
|
|
'echo -n -e "[DEFAULT]\nhost_href=' + barbican_url + '\n" ' + \
|
|
'>barbican.conf'])
|
|
|
|
@contextlib.contextmanager
|
|
def configure_barbican(ctx, config):
|
|
"""
|
|
Configure barbican paste-api and barbican-api.
|
|
"""
|
|
assert isinstance(config, dict)
|
|
(cclient, cconfig) = next(iter(config.items()))
|
|
|
|
keystone_role = cconfig.get('use-keystone-role', None)
|
|
if keystone_role is None:
|
|
raise ConfigError('use-keystone-role not defined in barbican task')
|
|
|
|
set_authtoken_params(ctx, cclient, cconfig)
|
|
fix_barbican_api(ctx, cclient)
|
|
fix_barbican_api_paste(ctx, cclient)
|
|
copy_policy_json(ctx, cclient)
|
|
create_barbican_conf(ctx, cclient)
|
|
try:
|
|
yield
|
|
finally:
|
|
pass
|
|
|
|
@contextlib.contextmanager
|
|
def run_barbican(ctx, config):
|
|
assert isinstance(config, dict)
|
|
log.info('Running barbican...')
|
|
|
|
for (client, _) in config.items():
|
|
(remote,) = ctx.cluster.only(client).remotes.keys()
|
|
cluster_name, _, client_id = teuthology.split_role(client)
|
|
|
|
# start the public endpoint
|
|
client_public_with_id = 'barbican.public' + '.' + client_id
|
|
|
|
run_cmd = ['cd', get_barbican_dir(ctx), run.Raw('&&'),
|
|
'.', '.barbicanenv/bin/activate', run.Raw('&&'),
|
|
'HOME={}'.format(get_barbican_dir(ctx)), run.Raw('&&'),
|
|
'bin/barbican-api',
|
|
run.Raw('& { read; kill %1; }')]
|
|
#run.Raw('1>/dev/null')
|
|
|
|
run_cmd = 'cd ' + get_barbican_dir(ctx) + ' && ' + \
|
|
'. .barbicanenv/bin/activate && ' + \
|
|
'HOME={}'.format(get_barbican_dir(ctx)) + ' && ' + \
|
|
'exec bin/barbican-api & { read; kill %1; }'
|
|
|
|
ctx.daemons.add_daemon(
|
|
remote, 'barbican', client_public_with_id,
|
|
cluster=cluster_name,
|
|
args=['bash', '-c', run_cmd],
|
|
logger=log.getChild(client),
|
|
stdin=run.PIPE,
|
|
cwd=get_barbican_dir(ctx),
|
|
wait=False,
|
|
check_status=False,
|
|
)
|
|
|
|
# sleep driven synchronization
|
|
run_in_barbican_venv(ctx, client, ['sleep', '15'])
|
|
try:
|
|
yield
|
|
finally:
|
|
log.info('Stopping Barbican instance')
|
|
ctx.daemons.get_daemon('barbican', client_public_with_id,
|
|
cluster_name).stop()
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def create_secrets(ctx, config):
|
|
"""
|
|
Create a main and an alternate s3 user.
|
|
"""
|
|
assert isinstance(config, dict)
|
|
(cclient, cconfig) = next(iter(config.items()))
|
|
|
|
rgw_user = cconfig['rgw_user']
|
|
|
|
keystone_role = cconfig.get('use-keystone-role', None)
|
|
keystone_host, keystone_port = ctx.keystone.public_endpoints[keystone_role]
|
|
barbican_host, barbican_port = ctx.barbican.endpoints[cclient]
|
|
barbican_url = 'http://{host}:{port}'.format(host=barbican_host,
|
|
port=barbican_port)
|
|
log.info("barbican_url=%s", barbican_url)
|
|
#fetching user_id of user that gets secrets for radosgw
|
|
token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
|
|
token_req.request(
|
|
'POST',
|
|
'/v3/auth/tokens',
|
|
headers={'Content-Type':'application/json'},
|
|
body=json.dumps({
|
|
"auth": {
|
|
"identity": {
|
|
"methods": ["password"],
|
|
"password": {
|
|
"user": {
|
|
"domain": {"id": "default"},
|
|
"name": rgw_user["username"],
|
|
"password": rgw_user["password"]
|
|
}
|
|
}
|
|
},
|
|
"scope": {
|
|
"project": {
|
|
"domain": {"id": "default"},
|
|
"name": rgw_user["tenantName"]
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
rgw_access_user_resp = token_req.getresponse()
|
|
if not (rgw_access_user_resp.status >= 200 and
|
|
rgw_access_user_resp.status < 300):
|
|
raise Exception("Cannot authenticate user "+rgw_user["username"]+" for secret creation")
|
|
# baru_resp = json.loads(baru_req.data)
|
|
rgw_access_user_data = json.loads(rgw_access_user_resp.read().decode())
|
|
rgw_user_id = rgw_access_user_data['token']['user']['id']
|
|
if 'secrets' in cconfig:
|
|
for secret in cconfig['secrets']:
|
|
if 'name' not in secret:
|
|
raise ConfigError('barbican.secrets must have "name" field')
|
|
if 'base64' not in secret:
|
|
raise ConfigError('barbican.secrets must have "base64" field')
|
|
if 'tenantName' not in secret:
|
|
raise ConfigError('barbican.secrets must have "tenantName" field')
|
|
if 'username' not in secret:
|
|
raise ConfigError('barbican.secrets must have "username" field')
|
|
if 'password' not in secret:
|
|
raise ConfigError('barbican.secrets must have "password" field')
|
|
|
|
token_req = http.client.HTTPConnection(keystone_host, keystone_port, timeout=30)
|
|
token_req.request(
|
|
'POST',
|
|
'/v3/auth/tokens',
|
|
headers={'Content-Type':'application/json'},
|
|
body=json.dumps({
|
|
"auth": {
|
|
"identity": {
|
|
"methods": ["password"],
|
|
"password": {
|
|
"user": {
|
|
"domain": {"id": "default"},
|
|
"name": secret["username"],
|
|
"password": secret["password"]
|
|
}
|
|
}
|
|
},
|
|
"scope": {
|
|
"project": {
|
|
"domain": {"id": "default"},
|
|
"name": secret["tenantName"]
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
token_resp = token_req.getresponse()
|
|
if not (token_resp.status >= 200 and
|
|
token_resp.status < 300):
|
|
raise Exception("Cannot authenticate user "+secret["username"]+" for secret creation")
|
|
|
|
expire = time.time() + 5400 # now + 90m
|
|
(expire_fract,dummy) = math.modf(expire)
|
|
expire_format = "%%FT%%T.%06d" % (round(expire_fract*1000000))
|
|
expiration = time.strftime(expire_format, time.gmtime(expire))
|
|
token_id = token_resp.getheader('x-subject-token')
|
|
|
|
key1_json = json.dumps(
|
|
{
|
|
"name": secret['name'],
|
|
"expiration": expiration,
|
|
"algorithm": "aes",
|
|
"bit_length": 256,
|
|
"mode": "cbc",
|
|
"payload": secret['base64'],
|
|
"payload_content_type": "application/octet-stream",
|
|
"payload_content_encoding": "base64"
|
|
})
|
|
|
|
sec_req = http.client.HTTPConnection(barbican_host, barbican_port, timeout=30)
|
|
try:
|
|
sec_req.request(
|
|
'POST',
|
|
'/v1/secrets',
|
|
headers={'Content-Type': 'application/json',
|
|
'Accept': '*/*',
|
|
'X-Auth-Token': token_id},
|
|
body=key1_json
|
|
)
|
|
except:
|
|
log.info("catched exception!")
|
|
run_in_barbican_venv(ctx, cclient, ['sleep', '900'])
|
|
|
|
barbican_sec_resp = sec_req.getresponse()
|
|
if not (barbican_sec_resp.status >= 200 and
|
|
barbican_sec_resp.status < 300):
|
|
raise Exception("Cannot create secret")
|
|
barbican_data = json.loads(barbican_sec_resp.read().decode())
|
|
if 'secret_ref' not in barbican_data:
|
|
raise ValueError("Malformed secret creation response")
|
|
secret_ref = barbican_data["secret_ref"]
|
|
log.info("secret_ref=%s", secret_ref)
|
|
secret_url_parsed = urlparse(secret_ref)
|
|
acl_json = json.dumps(
|
|
{
|
|
"read": {
|
|
"users": [rgw_user_id],
|
|
"project-access": True
|
|
}
|
|
})
|
|
acl_req = http.client.HTTPConnection(secret_url_parsed.netloc, timeout=30)
|
|
acl_req.request(
|
|
'PUT',
|
|
secret_url_parsed.path+'/acl',
|
|
headers={'Content-Type': 'application/json',
|
|
'Accept': '*/*',
|
|
'X-Auth-Token': token_id},
|
|
body=acl_json
|
|
)
|
|
barbican_acl_resp = acl_req.getresponse()
|
|
if not (barbican_acl_resp.status >= 200 and
|
|
barbican_acl_resp.status < 300):
|
|
raise Exception("Cannot set ACL for secret")
|
|
|
|
key = {'id': secret_ref.split('secrets/')[1], 'payload': secret['base64']}
|
|
ctx.barbican.keys[secret['name']] = key
|
|
|
|
run_in_barbican_venv(ctx, cclient, ['sleep', '3'])
|
|
try:
|
|
yield
|
|
finally:
|
|
pass
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def task(ctx, config):
|
|
"""
|
|
Deploy and configure Keystone
|
|
|
|
Example of configuration:
|
|
|
|
tasks:
|
|
- local_cluster:
|
|
cluster_path: /home/adam/ceph-1/build
|
|
- local_rgw:
|
|
- tox: [ client.0 ]
|
|
- keystone:
|
|
client.0:
|
|
sha1: 17.0.0.0rc2
|
|
force-branch: master
|
|
projects:
|
|
- name: rgwcrypt
|
|
description: Encryption Tenant
|
|
- name: barbican
|
|
description: Barbican
|
|
- name: s3
|
|
description: S3 project
|
|
users:
|
|
- name: rgwcrypt-user
|
|
password: rgwcrypt-pass
|
|
project: rgwcrypt
|
|
- name: barbican-user
|
|
password: barbican-pass
|
|
project: barbican
|
|
- name: s3-user
|
|
password: s3-pass
|
|
project: s3
|
|
roles: [ name: Member, name: creator ]
|
|
role-mappings:
|
|
- name: Member
|
|
user: rgwcrypt-user
|
|
project: rgwcrypt
|
|
- name: admin
|
|
user: barbican-user
|
|
project: barbican
|
|
- name: creator
|
|
user: s3-user
|
|
project: s3
|
|
services:
|
|
- name: keystone
|
|
type: identity
|
|
description: Keystone Identity Service
|
|
- barbican:
|
|
client.0:
|
|
force-branch: master
|
|
use-keystone-role: client.0
|
|
keystone_authtoken:
|
|
auth_plugin: password
|
|
username: barbican-user
|
|
password: barbican-pass
|
|
user_domain_name: Default
|
|
rgw_user:
|
|
tenantName: rgwcrypt
|
|
username: rgwcrypt-user
|
|
password: rgwcrypt-pass
|
|
secrets:
|
|
- name: my-key-1
|
|
base64: a2V5MS5GcWVxKzhzTGNLaGtzQkg5NGVpb1FKcFpGb2c=
|
|
tenantName: s3
|
|
username: s3-user
|
|
password: s3-pass
|
|
- name: my-key-2
|
|
base64: a2V5Mi5yNUNNMGFzMVdIUVZxcCt5NGVmVGlQQ1k4YWg=
|
|
tenantName: s3
|
|
username: s3-user
|
|
password: s3-pass
|
|
- s3tests:
|
|
client.0:
|
|
force-branch: master
|
|
kms_key: my-key-1
|
|
- rgw:
|
|
client.0:
|
|
use-keystone-role: client.0
|
|
use-barbican-role: client.0
|
|
"""
|
|
assert config is None or isinstance(config, list) \
|
|
or isinstance(config, dict), \
|
|
"task keystone only supports a list or dictionary for configuration"
|
|
all_clients = ['client.{id}'.format(id=id_)
|
|
for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
|
|
if config is None:
|
|
config = all_clients
|
|
if isinstance(config, list):
|
|
config = dict.fromkeys(config)
|
|
|
|
overrides = ctx.config.get('overrides', {})
|
|
# merge each client section, not the top level.
|
|
for client in config.keys():
|
|
if not config[client]:
|
|
config[client] = {}
|
|
teuthology.deep_merge(config[client], overrides.get('barbican', {}))
|
|
|
|
log.debug('Barbican config is %s', config)
|
|
|
|
if not hasattr(ctx, 'keystone'):
|
|
raise ConfigError('barbican must run after the keystone task')
|
|
|
|
|
|
ctx.barbican = argparse.Namespace()
|
|
ctx.barbican.endpoints = assign_ports(ctx, config, 9311)
|
|
ctx.barbican.keys = {}
|
|
|
|
with contextutil.nested(
|
|
lambda: download(ctx=ctx, config=config),
|
|
lambda: setup_venv(ctx=ctx, config=config),
|
|
lambda: configure_barbican(ctx=ctx, config=config),
|
|
lambda: run_barbican(ctx=ctx, config=config),
|
|
lambda: create_secrets(ctx=ctx, config=config),
|
|
):
|
|
yield
|