Introducing barbican-manage utility command
A new 'barbican-manage' utility command is introduced as Barbican admin tool. This command interacts with Barbican service for management operations which usually cannot be accomplished with REST APIs. This can improve usability and extensibility in the future. The related blueprint is https://review.openstack.org/#/c/253719/ This CR includes 1) implementation of barbican_manage.py 2) unit test code 3) document of barbican-manage command Co-Authored-By: Michael Perng <mperng@us.ibm.com> Change-Id: I784b46df86742d00d1737e3f8964280514a7fa1b
This commit is contained in:
parent
75f570097a
commit
77a164b062
|
@ -0,0 +1,300 @@
|
|||
#!/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 six
|
||||
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.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"
|
||||
|
||||
cleanup_description = "Cleanup soft-deleted secrets in database"
|
||||
|
||||
@args('--db-url', '-d', metavar='<db-url>', dest='dburl',
|
||||
help='barbican database URL')
|
||||
def cleanup(self, dburl=None):
|
||||
raise NotImplementedError
|
||||
|
||||
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, six.string_types):
|
||||
v = v.decode('utf-8')
|
||||
fn_kwargs[k] = v
|
||||
|
||||
# call the action with the remaining arguments
|
||||
try:
|
||||
ret = fn(*fn_args, **fn_kwargs)
|
||||
return(ret)
|
||||
except Exception as e:
|
||||
sys.exit("ERROR: %s" % e)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,125 @@
|
|||
# Copyright (c) 2015 Rackspace, Inc.
|
||||
#
|
||||
# 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.
|
||||
import fixtures
|
||||
import mock
|
||||
|
||||
from barbican.cmd import barbican_manage as manager
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
class TestBarbicanManageBase(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestBarbicanManageBase, self).setUp()
|
||||
|
||||
def clear_conf():
|
||||
manager.CONF.reset()
|
||||
manager.CONF.unregister_opt(manager.category_opt)
|
||||
clear_conf()
|
||||
self.addCleanup(clear_conf)
|
||||
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'oslo_log.log.setup', lambda barbican_test, version='test': None))
|
||||
manager.CONF.set_override('sql_connection', 'mockdburl')
|
||||
|
||||
def _main_test_helper(self, argv, func_name=None, *exp_args, **exp_kwargs):
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.argv', argv))
|
||||
manager.main()
|
||||
func_name.assert_called_once_with(*exp_args, **exp_kwargs)
|
||||
|
||||
|
||||
class TestBarbicanManage(TestBarbicanManageBase):
|
||||
"""Test barbican-manage functionality."""
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.generate')
|
||||
def test_db_revision(self, mock_generate):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'revision', '--db-url',
|
||||
'mockdb', '--message', 'mockmsg'], mock_generate,
|
||||
autogenerate=False, message='mockmsg', sql_url='mockdb')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.generate')
|
||||
def test_db_revision_autogenerate(self, mock_generate):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'revision', '--db-url',
|
||||
'mockdb', '--message', 'mockmsg', '--autogenerate'],
|
||||
mock_generate, autogenerate=True, message='mockmsg',
|
||||
sql_url='mockdb')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.generate')
|
||||
def test_db_revision_no_dburl(self, mock_generate):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'revision', '--message',
|
||||
'mockmsg'], mock_generate, autogenerate=False, message='mockmsg',
|
||||
sql_url='mockdburl')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.upgrade')
|
||||
def test_db_upgrade(self, mock_upgrade):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'upgrade', '--db-url',
|
||||
'mockdb'], mock_upgrade, to_version='head', sql_url='mockdb')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.upgrade')
|
||||
def test_db_upgrade_no_dburl(self, mock_upgrade):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'upgrade'], mock_upgrade,
|
||||
to_version='head', sql_url='mockdburl')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.history')
|
||||
def test_db_history(self, mock_history):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'history', '--db-url',
|
||||
'mockdb'], mock_history, False, sql_url='mockdb')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.history')
|
||||
def test_db_history_no_dburl(self, mock_history):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'history'], mock_history,
|
||||
False, sql_url='mockdburl')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.current')
|
||||
def test_db_current(self, mock_current):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'current', '--db-url',
|
||||
'mockdb'], mock_current, False, sql_url='mockdb')
|
||||
|
||||
@mock.patch('barbican.model.migration.commands.current')
|
||||
def test_db_current_no_dburl(self, mock_current):
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'db', 'current'], mock_current,
|
||||
False, sql_url='mockdburl')
|
||||
|
||||
@mock.patch('barbican.plugin.crypto.pkcs11.PKCS11')
|
||||
def test_hsm_gen_mkek(self, mock_pkcs11):
|
||||
mock_pkcs11.return_value.get_session.return_value = long(1)
|
||||
mock_pkcs11.return_value.get_key_handle.return_value = None
|
||||
mock_pkcs11.return_value.generate_key.return_value = long(0)
|
||||
mock_genkey = mock_pkcs11.return_value.generate_key
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'hsm', 'gen_mkek',
|
||||
'--library-path', 'mocklib', '--passphrase', 'mockpassewd',
|
||||
'--label', 'mocklabel'], mock_genkey,
|
||||
32, 1, 'mocklabel', encrypt=True, wrap=True, master_key=True)
|
||||
|
||||
@mock.patch('barbican.plugin.crypto.pkcs11.PKCS11')
|
||||
def test_hsm_gen_hmac(self, mock_pkcs11):
|
||||
mock_pkcs11.return_value.get_session.return_value = long(1)
|
||||
mock_pkcs11.return_value.get_key_handle.return_value = None
|
||||
mock_pkcs11.return_value.generate_key.return_value = long(0)
|
||||
mock_genkey = mock_pkcs11.return_value.generate_key
|
||||
self._main_test_helper(
|
||||
['barbican.cmd.barbican_manage', 'hsm', 'gen_hmac',
|
||||
'--library-path', 'mocklib', '--passphrase', 'mockpassewd',
|
||||
'--label', 'mocklabel'], mock_genkey,
|
||||
32, 1, 'mocklabel', sign=True, master_key=True)
|
|
@ -0,0 +1,78 @@
|
|||
===================================
|
||||
Barbican Service Management Utility
|
||||
===================================
|
||||
|
||||
Description
|
||||
===========
|
||||
``barbican-manage`` is a utility that is used to control the barbican key
|
||||
manager service database and Hardware Secure Module (HSM) plugin device. Use
|
||||
cases include migrating the secret database or generating a Master Key
|
||||
Encryption Key (MKEK) in the HSM. This command set should only be executed by
|
||||
a user with admin privileges.
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
The standard pattern for executing a barbican-manage command is:
|
||||
|
||||
``barbican-manage <category> <command> [<args>]``
|
||||
|
||||
Running ``barbican-manage`` without arguments shows a list of available command
|
||||
categories. Currently, there are 2 supported categories: *db* and *hsm*.
|
||||
|
||||
Running with a category argument shows a list of commands in that category:
|
||||
|
||||
* ``barbican-manage db --help``
|
||||
* ``barbican-manage hsm --help``
|
||||
* ``barbican-manage --version`` shows the version number of barbican service.
|
||||
|
||||
The following sections describe the available categories and arguments for
|
||||
barbican-manage.
|
||||
|
||||
Barbican Database
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. Warning::
|
||||
Before executing **barbican-manage db** commands, make sure you are
|
||||
familiar with `Database Migration`_ first.
|
||||
|
||||
``barbican-manage db revision [--db-url] [--message] [--autogenerate]``
|
||||
|
||||
Create a new database version file.
|
||||
|
||||
``barbican-manage db upgrade [--db-url] [--version]``
|
||||
|
||||
Upgrade to a future version database.
|
||||
|
||||
``barbican-manage db history [--db-url] [--verbose]``
|
||||
|
||||
Show database changeset history.
|
||||
|
||||
``barbican-manage db current [--db-url] [--verbose]``
|
||||
|
||||
Show current revision of database.
|
||||
|
||||
Barbican PKCS11/HSM
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``barbican-manage hsm gen_mkek [--library-path] [--passphrase] [--slot-id] [--label] [--length]``
|
||||
|
||||
Create a new Master key encryption key in HSM.
|
||||
This MKEK will be used to encrypt all project key encryption keys.
|
||||
Its label must be unique.
|
||||
|
||||
``barbican-manage hsm gen_hmac [--library-path] [--passphrase] [--slot-id] [--label] [--length]``
|
||||
|
||||
Create a new Master HMAC key in HSM.
|
||||
This HMAC key will be used to generate an authentication tag of encrypted
|
||||
project key encryption keys. Its label must be unique.
|
||||
|
||||
``barbican-manage hsm rewrap_pkek [--dry-run]``
|
||||
|
||||
Rewrap project key encryption keys after rotating to new MKEK and/or HMAC
|
||||
key(s) in HSM. The new MKEK and HMAC key should have already been generated
|
||||
using the above commands. The user will have to configure new MKEK and HMAC
|
||||
key labels in /etc/barbican.conf and restart barbican server before
|
||||
executing this command.
|
||||
|
||||
.. _Database Migration: http://docs.openstack.org/developer/barbican/contribute/database_migrations.html
|
|
@ -10,3 +10,5 @@ management of secrets.
|
|||
:maxdepth: 1
|
||||
|
||||
access_control.rst
|
||||
|
||||
barbican_manage.rst
|
||||
|
|
Loading…
Reference in New Issue