
This removes executable permissions on modules not requiring it. Change-Id: I497ed4ec1818515e63d76597a8d7670c49644262
329 lines
13 KiB
Python
329 lines
13 KiB
Python
#!/usr/bin/env python
|
|
# Copyright 2010-2015 OpenStack LLC.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""
|
|
CLI interface for barbican management
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from barbican.cmd import pkcs11_kek_rewrap as pkcs11_rewrap
|
|
from barbican.common import config
|
|
from barbican.model import clean
|
|
from barbican.model.migration import commands
|
|
from barbican.plugin.crypto import pkcs11
|
|
import barbican.version
|
|
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
# Decorators for actions
|
|
def args(*args, **kwargs):
|
|
def _decorator(func):
|
|
func.__dict__.setdefault('args', []).insert(0, (args, kwargs))
|
|
return func
|
|
return _decorator
|
|
|
|
|
|
class DbCommands(object):
|
|
"""Class for managing barbican database"""
|
|
|
|
description = "Subcommands for managing barbican database"
|
|
|
|
clean_description = "Clean up soft deletions in the database"
|
|
|
|
@args('--db-url', '-d', metavar='<db-url>', dest='dburl',
|
|
help='barbican database URL')
|
|
@args('--min-days', '-m', metavar='<min-days>', dest='min_days', type=int,
|
|
default=90, help='minimum number of days to keep soft deletions. '
|
|
'default is %(default)s days.')
|
|
@args('--verbose', '-V', action='store_true', dest='verbose',
|
|
default=False, help='Show verbose information about the clean up.')
|
|
@args('--log-file', '-L', metavar='<log-file>', type=str, default=None,
|
|
dest='log_file', help='Set log file location. '
|
|
'Default value for log_file can be found in barbican.conf')
|
|
@args('--clean-unassociated-projects', '-p', action='store_true',
|
|
dest='do_clean_unassociated_projects', default=False,
|
|
help='Remove projects that have no '
|
|
'associated resources.')
|
|
@args('--soft-delete-expired-secrets', '-e', action='store_true',
|
|
dest='do_soft_delete_expired_secrets', default=False,
|
|
help='Soft delete secrets that are expired.')
|
|
def clean(self, dburl=None, min_days=None, verbose=None, log_file=None,
|
|
do_clean_unassociated_projects=None,
|
|
do_soft_delete_expired_secrets=None):
|
|
"""Clean soft deletions in the database"""
|
|
if dburl is None:
|
|
dburl = CONF.sql_connection
|
|
if log_file is None:
|
|
log_file = CONF.log_file
|
|
|
|
clean.clean_command(
|
|
sql_url=dburl,
|
|
min_num_days=min_days,
|
|
do_clean_unassociated_projects=do_clean_unassociated_projects,
|
|
do_soft_delete_expired_secrets=do_soft_delete_expired_secrets,
|
|
verbose=verbose,
|
|
log_file=log_file)
|
|
|
|
revision_description = "Create a new database version file"
|
|
|
|
@args('--db-url', '-d', metavar='<db-url>', dest='dburl',
|
|
help='barbican database URL')
|
|
@args('--message', '-m', metavar='<message>', default='DB change',
|
|
help='the message for the DB change')
|
|
@args('--autogenerate', action="store_true", dest='autogen',
|
|
default=False, help='autogenerate from models')
|
|
def revision(self, dburl=None, message=None, autogen=None):
|
|
"""Process the 'revision' Alembic command."""
|
|
if dburl is None:
|
|
commands.generate(autogenerate=autogen, message=str(message),
|
|
sql_url=CONF.sql_connection)
|
|
else:
|
|
commands.generate(autogenerate=autogen, message=str(message),
|
|
sql_url=str(dburl))
|
|
|
|
upgrade_description = "Upgrade to a future database version"
|
|
|
|
@args('--db-url', '-d', metavar='<db-url>', dest='dburl',
|
|
help='barbican database URL')
|
|
@args('--version', '-v', metavar='<version>', default='head',
|
|
help='the version to upgrade to, or else '
|
|
'the latest/head if not specified.')
|
|
def upgrade(self, dburl=None, version=None):
|
|
"""Process the 'upgrade' Alembic command."""
|
|
if dburl is None:
|
|
commands.upgrade(to_version=str(version),
|
|
sql_url=CONF.sql_connection)
|
|
else:
|
|
commands.upgrade(to_version=str(version), sql_url=str(dburl))
|
|
|
|
history_description = "Show database changset history"
|
|
|
|
@args('--db-url', '-d', metavar='<db-url>', dest='dburl',
|
|
help='barbican database URL')
|
|
@args('--verbose', '-V', action='store_true', dest='verbose',
|
|
default=False, help='Show full information about the revisions.')
|
|
def history(self, dburl=None, verbose=None):
|
|
if dburl is None:
|
|
commands.history(verbose, sql_url=CONF.sql_connection)
|
|
else:
|
|
commands.history(verbose, sql_url=str(dburl))
|
|
|
|
current_description = "Show current revision of database"
|
|
|
|
@args('--db-url', '-d', metavar='<db-url>', dest='dburl',
|
|
help='barbican database URL')
|
|
@args('--verbose', '-V', action='store_true', dest='verbose',
|
|
default=False, help='Show full information about the revisions.')
|
|
def current(self, dburl=None, verbose=None):
|
|
if dburl is None:
|
|
commands.current(verbose, sql_url=CONF.sql_connection)
|
|
else:
|
|
commands.current(verbose, sql_url=str(dburl))
|
|
|
|
|
|
class HSMCommands(object):
|
|
"""Class for managing HSM/pkcs11 plugin"""
|
|
|
|
description = "Subcommands for managing HSM/PKCS11"
|
|
|
|
gen_mkek_description = "Generates a new MKEK"
|
|
|
|
@args('--library-path', metavar='<library-path>', dest='libpath',
|
|
default='/usr/lib/libCryptoki2_64.so',
|
|
help='Path to vendor PKCS11 library')
|
|
@args('--slot-id', metavar='<slot-id>', dest='slotid', default=1,
|
|
help='HSM Slot id (Should correspond to a configured PKCS11 slot, \
|
|
default is 1)')
|
|
@args('--passphrase', metavar='<passphrase>', default=None, required=True,
|
|
help='Password to login to PKCS11 session')
|
|
@args('--label', '-L', metavar='<label>', default='primarymkek',
|
|
help='The label of the Master Key Encrypt Key')
|
|
@args('--length', '-l', metavar='<length>', default=32,
|
|
help='The length of the Master Key Encrypt Key (default is 32)')
|
|
def gen_mkek(self, passphrase, libpath=None, slotid=None, label=None,
|
|
length=None):
|
|
self._create_pkcs11_session(str(passphrase), str(libpath), int(slotid))
|
|
self._verify_label_does_not_exist(str(label), self.session)
|
|
self.pkcs11.generate_key(int(length), self.session, str(label),
|
|
encrypt=True, wrap=True, master_key=True)
|
|
self.pkcs11.return_session(self.session)
|
|
print ("MKEK successfully generated!")
|
|
|
|
gen_hmac_description = "Generates a new HMAC key"
|
|
|
|
@args('--library-path', metavar='<library-path>', dest='libpath',
|
|
default='/usr/lib/libCryptoki2_64.so',
|
|
help='Path to vendor PKCS11 library')
|
|
@args('--slot-id', metavar='<slot-id>', dest='slotid', default=1,
|
|
help='HSM Slot id (Should correspond to a configured PKCS11 slot, \
|
|
default is 1)')
|
|
@args('--passphrase', metavar='<passphrase>', default=None, required=True,
|
|
help='Password to login to PKCS11 session')
|
|
@args('--label', '-L', metavar='<label>', default='primarymkek',
|
|
help='The label of the Matser HMAC Key')
|
|
@args('--length', '-l', metavar='<length>', default=32,
|
|
help='The length of the Master HMAC Key (default is 32)')
|
|
def gen_hmac(self, passphrase, libpath=None, slotid=None, label=None,
|
|
length=None):
|
|
self._create_pkcs11_session(str(passphrase), str(libpath), int(slotid))
|
|
self._verify_label_does_not_exist(str(label), self.session)
|
|
self.pkcs11.generate_key(int(length), self.session, str(label),
|
|
sign=True, master_key=True)
|
|
self.pkcs11.return_session(self.session)
|
|
print ("HMAC successfully generated!")
|
|
|
|
rewrap_pkek_description = "Re-wrap project MKEKs"
|
|
|
|
@args('--dry-run', action="store_true", dest='dryrun', default=False,
|
|
help='Displays changes that will be made (Non-destructive)')
|
|
def rewrap_pkek(self, dryrun=None):
|
|
rewrapper = pkcs11_rewrap.KekRewrap(pkcs11_rewrap.CONF)
|
|
rewrapper.execute(dryrun)
|
|
rewrapper.pkcs11.return_session(rewrapper.hsm_session)
|
|
|
|
def _create_pkcs11_session(self, passphrase, libpath, slotid):
|
|
self.pkcs11 = pkcs11.PKCS11(
|
|
library_path=libpath, login_passphrase=passphrase,
|
|
rw_session=True, slot_id=slotid
|
|
)
|
|
self.session = self.pkcs11.get_session()
|
|
|
|
def _verify_label_does_not_exist(self, label, session):
|
|
key_handle = self.pkcs11.get_key_handle(label, session)
|
|
if key_handle:
|
|
print (
|
|
"The label {label} already exists! "
|
|
"Please try again.".format(label=label)
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
CATEGORIES = {
|
|
'db': DbCommands,
|
|
'hsm': HSMCommands,
|
|
}
|
|
|
|
|
|
# Modifying similiar code from nova/cmd/manage.py
|
|
def methods_of(obj):
|
|
"""Get all callable methods of an object that don't start with underscore
|
|
|
|
returns a list of tuples of the form (method_name, method)
|
|
"""
|
|
|
|
result = []
|
|
for fn in dir(obj):
|
|
if callable(getattr(obj, fn)) and not fn.startswith('_'):
|
|
result.append((fn, getattr(obj, fn),
|
|
getattr(obj, fn+'_description', None)))
|
|
return result
|
|
|
|
|
|
# Shamelessly taking same code from nova/cmd/manage.py
|
|
def add_command_parsers(subparsers):
|
|
"""Add subcommand parser to oslo_config object"""
|
|
|
|
for category in CATEGORIES:
|
|
command_object = CATEGORIES[category]()
|
|
|
|
desc = getattr(command_object, 'description', None)
|
|
parser = subparsers.add_parser(category, description=desc)
|
|
parser.set_defaults(command_object=command_object)
|
|
|
|
category_subparsers = parser.add_subparsers(dest='action')
|
|
|
|
for (action, action_fn, action_desc) in methods_of(command_object):
|
|
parser = category_subparsers.add_parser(action,
|
|
description=action_desc)
|
|
|
|
action_kwargs = []
|
|
for args, kwargs in getattr(action_fn, 'args', []):
|
|
# Assuming dest is the arg name without the leading
|
|
# hyphens if no dest is supplied
|
|
kwargs.setdefault('dest', args[0][2:])
|
|
if kwargs['dest'].startswith('action_kwarg_'):
|
|
action_kwargs.append(
|
|
kwargs['dest'][len('action_kwarg_'):])
|
|
else:
|
|
action_kwargs.append(kwargs['dest'])
|
|
kwargs['dest'] = 'action_kwarg_' + kwargs['dest']
|
|
|
|
parser.add_argument(*args, **kwargs)
|
|
|
|
parser.set_defaults(action_fn=action_fn)
|
|
parser.set_defaults(action_kwargs=action_kwargs)
|
|
|
|
parser.add_argument('action_args', nargs='*',
|
|
help=argparse.SUPPRESS)
|
|
|
|
|
|
# Define subcommand category
|
|
category_opt = cfg.SubCommandOpt('category',
|
|
title='Command categories',
|
|
help='Available categories',
|
|
handler=add_command_parsers)
|
|
|
|
|
|
def main():
|
|
"""Parse options and call the appropriate class/method."""
|
|
CONF = config.new_config()
|
|
CONF.register_cli_opt(category_opt)
|
|
|
|
try:
|
|
logging.register_options(CONF)
|
|
logging.setup(CONF, "barbican-manage")
|
|
cfg_files = cfg.find_config_files(project='barbican')
|
|
|
|
CONF(args=sys.argv[1:],
|
|
project='barbican',
|
|
prog='barbican-manage',
|
|
version=barbican.version.__version__,
|
|
default_config_files=cfg_files)
|
|
|
|
except RuntimeError as e:
|
|
sys.exit("ERROR: %s" % e)
|
|
|
|
# find sub-command and its arguments
|
|
fn = CONF.category.action_fn
|
|
fn_args = [arg.decode('utf-8') for arg in CONF.category.action_args]
|
|
fn_kwargs = {}
|
|
for k in CONF.category.action_kwargs:
|
|
v = getattr(CONF.category, 'action_kwarg_' + k)
|
|
if v is None:
|
|
continue
|
|
if isinstance(v, bytes):
|
|
v = v.decode('utf-8')
|
|
fn_kwargs[k] = v
|
|
|
|
# call the action with the remaining arguments
|
|
try:
|
|
return fn(*fn_args, **fn_kwargs)
|
|
except Exception as e:
|
|
sys.exit("ERROR: %s" % e)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|