Add keystone-manage bootstrap
command
Add the keystone-manage bootstrap command so that admin_token can be deprecated/removed in the future. This will allow for bootstrapping an initial user into the cloud instead of needing a global-admin token to perform initial actions. Change-Id: I113c6934b6b83ceff23a94101967a6df1126873f bp: bootstrap
This commit is contained in:
parent
44d0c2f5a5
commit
d446e15285
@ -16,6 +16,7 @@ from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
@ -31,7 +32,7 @@ from keystone.common import utils
|
||||
from keystone import exception
|
||||
from keystone.federation import idp
|
||||
from keystone.federation import utils as mapping_engine
|
||||
from keystone.i18n import _, _LW
|
||||
from keystone.i18n import _, _LW, _LI
|
||||
from keystone.server import backends
|
||||
from keystone import token
|
||||
|
||||
@ -51,6 +52,122 @@ class BaseApp(object):
|
||||
return parser
|
||||
|
||||
|
||||
class BootStrap(BaseApp):
|
||||
"""Perform the basic bootstrap process"""
|
||||
|
||||
name = "bootstrap"
|
||||
|
||||
def __init__(self):
|
||||
self.load_backends()
|
||||
self.tenant_id = uuid.uuid4().hex
|
||||
self.role_id = uuid.uuid4().hex
|
||||
self.username = None
|
||||
self.project_name = None
|
||||
self.role_name = None
|
||||
self.password = None
|
||||
|
||||
@classmethod
|
||||
def add_argument_parser(cls, subparsers):
|
||||
parser = super(BootStrap, cls).add_argument_parser(subparsers)
|
||||
parser.add_argument('--bootstrap-username', default='admin',
|
||||
help=('The username of the initial keystone '
|
||||
'user during bootstrap process.'))
|
||||
# NOTE(morganfainberg): See below for ENV Variable that can be used
|
||||
# in lieu of the command-line arguments.
|
||||
parser.add_argument('--bootstrap-password', default=None,
|
||||
help='The bootstrap user password')
|
||||
parser.add_argument('--bootstrap-project-name', default='admin',
|
||||
help=('The initial project created during the '
|
||||
'keystone bootstrap process.'))
|
||||
parser.add_argument('--bootstrap-role-name', default='admin',
|
||||
help=('The initial role-name created during the '
|
||||
'keystone bootstrap process.'))
|
||||
return parser
|
||||
|
||||
def load_backends(self):
|
||||
drivers = backends.load_backends()
|
||||
self.resource_manager = drivers['resource_api']
|
||||
self.identity_manager = drivers['identity_api']
|
||||
self.assignment_manager = drivers['assignment_api']
|
||||
self.role_manager = drivers['role_api']
|
||||
|
||||
def _get_config(self):
|
||||
self.username = (
|
||||
os.environ.get('OS_BOOTSTRAP_USERNAME') or
|
||||
CONF.command.bootstrap_username)
|
||||
self.project_name = (
|
||||
os.environ.get('OS_BOOTSTRAP_PROJECT_NAME') or
|
||||
CONF.command.bootstrap_project_name)
|
||||
self.role_name = (
|
||||
os.environ.get('OS_BOOTSTRAP_ROLE_NAME') or
|
||||
CONF.command.bootstrap_role_name)
|
||||
self.password = (
|
||||
os.environ.get('OS_BOOTSTRAP_PASSWORD') or
|
||||
CONF.command.bootstrap_password)
|
||||
|
||||
def do_bootstrap(self):
|
||||
"""Perform the bootstrap actions.
|
||||
|
||||
Create bootstrap user, project, and role so that CMS, humans, or
|
||||
scripts can continue to perform initial setup (domains, projects,
|
||||
services, endpoints, etc) of Keystone when standing up a new
|
||||
deployment.
|
||||
"""
|
||||
self._get_config()
|
||||
|
||||
if self.password is None:
|
||||
print(_('Either --bootstrap-password argument or '
|
||||
'OS_BOOTSTRAP_PASSWORD must be set.'))
|
||||
raise ValueError
|
||||
|
||||
# NOTE(morganfainberg): Ensure the default domain is in-fact created
|
||||
default_domain = migration_helpers.get_default_domain()
|
||||
try:
|
||||
self.resource_manager.create_domain(
|
||||
domain_id=default_domain['id'],
|
||||
domain=default_domain)
|
||||
LOG.info(_LI('Created domain %s'), default_domain['id'])
|
||||
except exception.Conflict:
|
||||
# NOTE(morganfainberg): Domain already exists, continue on.
|
||||
LOG.info(_LI('Domain %s already exists, skipping creation.'),
|
||||
default_domain['id'])
|
||||
|
||||
LOG.info(_LI('Creating project %s'), self.project_name)
|
||||
self.resource_manager.create_project(
|
||||
tenant_id=self.tenant_id,
|
||||
tenant={'enabled': True,
|
||||
'id': self.tenant_id,
|
||||
'domain_id': default_domain['id'],
|
||||
'description': 'Bootstrap project for initializing the '
|
||||
'cloud.',
|
||||
'name': self.project_name},
|
||||
)
|
||||
LOG.info(_LI('Creating user %s'), self.username)
|
||||
user = self.identity_manager.create_user(
|
||||
user_ref={'name': self.username,
|
||||
'enabled': True,
|
||||
'domain_id': default_domain['id'],
|
||||
'password': self.password
|
||||
}
|
||||
)
|
||||
LOG.info(_LI('Creating Role %s'), self.role_name)
|
||||
self.role_manager.create_role(
|
||||
role_id=self.role_id,
|
||||
role={'name': self.role_name,
|
||||
'id': self.role_id},
|
||||
)
|
||||
self.assignment_manager.add_role_to_user_and_project(
|
||||
user_id=user['id'],
|
||||
tenant_id=self.tenant_id,
|
||||
role_id=self.role_id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def main(cls):
|
||||
klass = cls()
|
||||
klass.do_bootstrap()
|
||||
|
||||
|
||||
class DbSync(BaseApp):
|
||||
"""Sync the database."""
|
||||
|
||||
@ -641,6 +758,7 @@ class MappingEngineTester(BaseApp):
|
||||
|
||||
|
||||
CMDS = [
|
||||
BootStrap,
|
||||
DbSync,
|
||||
DbVersion,
|
||||
DomainConfigUpload,
|
||||
|
@ -15,6 +15,7 @@
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from six.moves import range
|
||||
@ -42,6 +43,63 @@ class CliTestCase(unit.SQLDriverOverrides, unit.TestCase):
|
||||
cli.TokenFlush.main()
|
||||
|
||||
|
||||
class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.useFixture(database.Database())
|
||||
super(CliBootStrapTestCase, self).setUp()
|
||||
|
||||
def config_files(self):
|
||||
self.config_fixture.register_cli_opt(cli.command_opt)
|
||||
config_files = super(CliBootStrapTestCase, self).config_files()
|
||||
config_files.append(unit.dirs.tests_conf('backend_sql.conf'))
|
||||
return config_files
|
||||
|
||||
def config(self, config_files):
|
||||
CONF(args=['bootstrap', '--bootstrap-password', uuid.uuid4().hex],
|
||||
project='keystone',
|
||||
default_config_files=config_files)
|
||||
|
||||
def test_bootstrap(self):
|
||||
bootstrap = cli.BootStrap()
|
||||
bootstrap.do_bootstrap()
|
||||
project = bootstrap.resource_manager.get_project_by_name(
|
||||
bootstrap.project_name,
|
||||
'default')
|
||||
user = bootstrap.identity_manager.get_user_by_name(
|
||||
bootstrap.username,
|
||||
'default')
|
||||
role = bootstrap.role_manager.get_role(bootstrap.role_id)
|
||||
role_list = (
|
||||
bootstrap.assignment_manager.get_roles_for_user_and_project(
|
||||
user['id'],
|
||||
project['id']))
|
||||
self.assertIs(len(role_list), 1)
|
||||
self.assertEqual(role_list[0], role['id'])
|
||||
# NOTE(morganfainberg): Pass an empty context, it isn't used by
|
||||
# `authenticate` method.
|
||||
bootstrap.identity_manager.authenticate(
|
||||
{},
|
||||
user['id'],
|
||||
bootstrap.password)
|
||||
|
||||
|
||||
class CliBootStrapTestCaseWithEnvironment(CliBootStrapTestCase):
|
||||
|
||||
def config(self, config_files):
|
||||
CONF(args=['bootstrap'], project='keystone',
|
||||
default_config_files=config_files)
|
||||
|
||||
def setUp(self):
|
||||
super(CliBootStrapTestCaseWithEnvironment, self).setUp()
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_BOOTSTRAP_PASSWORD',
|
||||
newvalue=uuid.uuid4().hex))
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable('OS_BOOTSTRAP_USERNAME',
|
||||
newvalue=uuid.uuid4().hex))
|
||||
|
||||
|
||||
class CliDomainConfigAllTestCase(unit.SQLDriverOverrides, unit.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
15
releasenotes/notes/add-bootstrap-cli-192500228cc6e574.yaml
Normal file
15
releasenotes/notes/add-bootstrap-cli-192500228cc6e574.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
features:
|
||||
- keystone-manage now supports the bootstrap command
|
||||
on the CLI so that a keystone install can be
|
||||
initialized without the need of the admin_token
|
||||
filter in the paste-ini.
|
||||
security:
|
||||
- The use of admin_token filter is insecure compared
|
||||
to the use of a proper username/password. Historically
|
||||
the admin_token filter has been left enabled in
|
||||
Keystone after initialization due to the way CMS
|
||||
systems work. Moving to an out-of-band initialization
|
||||
will eliminate the security concerns around a static
|
||||
shared string that conveys admin access to Keystone
|
||||
and therefore to the entire installation.
|
Loading…
Reference in New Issue
Block a user