"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/cmd/bootstrap.py" (13 May 2020, 14021 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "bootstrap.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 # not use this file except in compliance with the License. You may obtain
    3 # a copy of the License at
    4 #
    5 #      http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 # Unless required by applicable law or agreed to in writing, software
    8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 # License for the specific language governing permissions and limitations
   11 # under the License.
   12 
   13 import uuid
   14 
   15 from oslo_log import log
   16 
   17 from keystone.common import driver_hints
   18 from keystone.common import provider_api
   19 import keystone.conf
   20 from keystone import exception
   21 from keystone.server import backends
   22 
   23 CONF = keystone.conf.CONF
   24 LOG = log.getLogger(__name__)
   25 PROVIDERS = provider_api.ProviderAPIs
   26 
   27 
   28 class Bootstrapper(object):
   29 
   30     def __init__(self):
   31         backends.load_backends()
   32 
   33         self.admin_password = None
   34         self.admin_username = None
   35 
   36         self.project_id = None
   37         self.project_name = None
   38 
   39         self.reader_role_id = None
   40         self.reader_role_name = 'reader'
   41 
   42         self.member_role_id = None
   43         self.member_role_name = 'member'
   44 
   45         self.admin_role_id = None
   46         self.admin_role_name = None
   47 
   48         self.region_id = None
   49 
   50         self.service_name = None
   51         self.public_url = None
   52         self.internal_url = None
   53         self.admin_url = None
   54         self.endpoints = {}
   55 
   56         self.default_domain_id = None
   57         self.admin_user_id = None
   58 
   59         self.immutable_roles = False
   60 
   61     def bootstrap(self):
   62         # NOTE(morganfainberg): Ensure the default domain is in-fact created
   63         self._bootstrap_default_domain()
   64         self._bootstrap_project()
   65         self._bootstrap_admin_user()
   66         self._bootstrap_reader_role()
   67         self._bootstrap_member_role()
   68         self._bootstrap_admin_role()
   69         self._bootstrap_project_role_assignment()
   70         self._bootstrap_system_role_assignment()
   71         self._bootstrap_region()
   72         self._bootstrap_catalog()
   73 
   74     def _bootstrap_default_domain(self):
   75         default_domain = {
   76             'id': CONF.identity.default_domain_id,
   77             'name': 'Default',
   78             'enabled': True,
   79             'description': 'The default domain'
   80         }
   81         try:
   82             PROVIDERS.resource_api.create_domain(
   83                 domain_id=default_domain['id'],
   84                 domain=default_domain)
   85             LOG.info('Created domain %s', default_domain['id'])
   86         except exception.Conflict:
   87             # NOTE(morganfainberg): Domain already exists, continue on.
   88             LOG.info('Domain %s already exists, skipping creation.',
   89                      default_domain['id'])
   90 
   91         self.default_domain_id = default_domain['id']
   92 
   93     def _bootstrap_project(self):
   94         try:
   95             project_id = uuid.uuid4().hex
   96             project = {
   97                 'enabled': True,
   98                 'id': project_id,
   99                 'domain_id': self.default_domain_id,
  100                 'description': 'Bootstrap project for initializing the cloud.',
  101                 'name': self.project_name
  102             }
  103             PROVIDERS.resource_api.create_project(project_id, project)
  104             LOG.info('Created project %s', self.project_name)
  105         except exception.Conflict:
  106             LOG.info('Project %s already exists, skipping creation.',
  107                      self.project_name)
  108             project = PROVIDERS.resource_api.get_project_by_name(
  109                 self.project_name, self.default_domain_id
  110             )
  111 
  112         self.project_id = project['id']
  113 
  114     def _ensure_role_exists(self, role_name):
  115         # NOTE(morganfainberg): Do not create the role if it already exists.
  116         try:
  117             role_id = uuid.uuid4().hex
  118             role = {'name': role_name, 'id': role_id}
  119             if self.immutable_roles:
  120                 role['options'] = {'immutable': True}
  121             role = PROVIDERS.role_api.create_role(role_id, role)
  122             LOG.info('Created role %s', role_name)
  123             if not self.immutable_roles:
  124                 LOG.warning("Role %(role)s was created as a mutable role. It "
  125                             "is recommended to make this role immutable by "
  126                             "adding the 'immutable' resource option to this "
  127                             "role, or re-running this command without "
  128                             "--no-immutable-role.", {'role': role_name})
  129             return role
  130         except exception.Conflict:
  131             LOG.info('Role %s exists, skipping creation.', role_name)
  132             # NOTE(davechen): There is no backend method to get the role
  133             # by name, so build the hints to list the roles and filter by
  134             # name instead.
  135             hints = driver_hints.Hints()
  136             hints.add_filter('name', role_name)
  137             # Only return global roles, domain-specific roles can't be used in
  138             # system assignments and bootstrap isn't designed to work with
  139             # domain-specific roles.
  140             hints.add_filter('domain_id', None)
  141 
  142             # NOTE(lbragstad): Global roles are unique based on name. At this
  143             # point we should be safe to return the first, and only, element in
  144             # the list.
  145             return PROVIDERS.role_api.list_roles(hints)[0]
  146 
  147     def _ensure_implied_role(self, prior_role_id, implied_role_id):
  148         try:
  149             PROVIDERS.role_api.create_implied_role(prior_role_id,
  150                                                    implied_role_id)
  151             LOG.info(
  152                 'Created implied role where %s implies %s',
  153                 prior_role_id,
  154                 implied_role_id
  155             )
  156         except exception.Conflict:
  157             LOG.info(
  158                 'Implied role where %s implies %s exists, skipping creation.',
  159                 prior_role_id,
  160                 implied_role_id
  161             )
  162 
  163     def _bootstrap_reader_role(self):
  164         role = self._ensure_role_exists(self.reader_role_name)
  165         self.reader_role_id = role['id']
  166 
  167     def _bootstrap_member_role(self):
  168         role = self._ensure_role_exists(self.member_role_name)
  169         self.member_role_id = role['id']
  170         self._ensure_implied_role(self.member_role_id, self.reader_role_id)
  171 
  172     def _bootstrap_admin_role(self):
  173         role = self._ensure_role_exists(self.admin_role_name)
  174         self.admin_role_id = role['id']
  175         self._ensure_implied_role(self.admin_role_id, self.member_role_id)
  176 
  177     def _bootstrap_admin_user(self):
  178         # NOTE(morganfainberg): Do not create the user if it already exists.
  179         try:
  180             user = PROVIDERS.identity_api.get_user_by_name(
  181                 self.admin_username, self.default_domain_id
  182             )
  183             LOG.info('User %s already exists, skipping creation.',
  184                      self.admin_username)
  185 
  186             # If the user is not enabled, re-enable them. This also helps
  187             # provide some useful logging output later.
  188             update = {}
  189             enabled = user['enabled']
  190             if not enabled:
  191                 update['enabled'] = True
  192 
  193             try:
  194                 PROVIDERS.identity_api.driver.authenticate(
  195                     user['id'], self.admin_password
  196                 )
  197             except AssertionError:
  198                 # This means that authentication failed and that we need to
  199                 # update the user's password. This is going to persist a
  200                 # revocation event that will make all previous tokens for the
  201                 # user invalid, which is OK because it falls within the scope
  202                 # of revocation. If a password changes, we shouldn't be able to
  203                 # use tokens obtained with an old password.
  204                 update['password'] = self.admin_password
  205 
  206             # Only make a call to update the user if the password has changed
  207             # or the user was previously disabled. This allows bootstrap to act
  208             # as a recovery tool, without having to create a new user.
  209             if update:
  210                 user = PROVIDERS.identity_api.update_user(
  211                     user['id'], update
  212                 )
  213                 LOG.info('Reset password for user %s.', self.admin_username)
  214                 if not enabled and user['enabled']:
  215                     # Although we always try to enable the user, this log
  216                     # message only makes sense if we know that the user was
  217                     # previously disabled.
  218                     LOG.info('Enabled user %s.', self.admin_username)
  219         except exception.UserNotFound:
  220             user = PROVIDERS.identity_api.create_user(
  221                 user_ref={
  222                     'name': self.admin_username,
  223                     'enabled': True,
  224                     'domain_id': self.default_domain_id,
  225                     'password': self.admin_password
  226                 }
  227             )
  228             LOG.info('Created user %s', self.admin_username)
  229 
  230         self.admin_user_id = user['id']
  231 
  232     def _bootstrap_project_role_assignment(self):
  233         try:
  234             PROVIDERS.assignment_api.add_role_to_user_and_project(
  235                 user_id=self.admin_user_id,
  236                 project_id=self.project_id,
  237                 role_id=self.admin_role_id
  238             )
  239             LOG.info('Granted role %(role)s on project %(project)s to '
  240                      'user %(username)s.',
  241                      {'role': self.admin_role_name,
  242                       'project': self.project_name,
  243                       'username': self.admin_username})
  244         except exception.Conflict:
  245             LOG.info('User %(username)s already has role %(role)s on '
  246                      'project %(project)s.',
  247                      {'username': self.admin_username,
  248                       'role': self.admin_role_name,
  249                       'project': self.project_name})
  250 
  251     def _bootstrap_system_role_assignment(self):
  252         # NOTE(lbragstad): We need to make sure a user has at least one role on
  253         # the system. Otherwise it's possible for administrators to lock
  254         # themselves out of system-level APIs in their deployment. This is
  255         # considered backwards compatible because even if the assignment
  256         # exists, it needs to be enabled through oslo.policy configuration
  257         # options to be enforced.
  258         try:
  259             PROVIDERS.assignment_api.create_system_grant_for_user(
  260                 self.admin_user_id, self.admin_role_id
  261             )
  262             LOG.info('Granted role %(role)s on the system to user'
  263                      ' %(username)s.',
  264                      {'role': self.admin_role_name,
  265                       'username': self.admin_username})
  266         except exception.Conflict:
  267             LOG.info('User %(username)s already has role %(role)s on '
  268                      'the system.',
  269                      {'username': self.admin_username,
  270                       'role': self.admin_role_name})
  271 
  272     def _bootstrap_region(self):
  273         if self.region_id:
  274             try:
  275                 PROVIDERS.catalog_api.create_region(
  276                     region_ref={'id': self.region_id}
  277                 )
  278                 LOG.info('Created region %s', self.region_id)
  279             except exception.Conflict:
  280                 LOG.info('Region %s exists, skipping creation.',
  281                          self.region_id)
  282 
  283     def _bootstrap_catalog(self):
  284         if self.public_url or self.admin_url or self.internal_url:
  285             hints = driver_hints.Hints()
  286             hints.add_filter('type', 'identity')
  287             services = PROVIDERS.catalog_api.list_services(hints)
  288 
  289             if services:
  290                 service = services[0]
  291 
  292                 hints = driver_hints.Hints()
  293                 hints.add_filter('service_id', service['id'])
  294                 if self.region_id:
  295                     hints.add_filter('region_id', self.region_id)
  296 
  297                 endpoints = PROVIDERS.catalog_api.list_endpoints(hints)
  298             else:
  299                 service_id = uuid.uuid4().hex
  300                 service = {
  301                     'id': service_id, 'name': self.service_name,
  302                     'type': 'identity', 'enabled': True
  303                 }
  304 
  305                 PROVIDERS.catalog_api.create_service(service_id, service)
  306                 endpoints = []
  307 
  308             self.service_id = service['id']
  309             available_interfaces = {e['interface']: e for e in endpoints}
  310             expected_endpoints = {'public': self.public_url,
  311                                   'internal': self.internal_url,
  312                                   'admin': self.admin_url}
  313 
  314             for interface, url in expected_endpoints.items():
  315                 if not url:
  316                     # not specified to bootstrap command
  317                     continue
  318 
  319                 try:
  320                     endpoint_ref = available_interfaces[interface]
  321                 except KeyError:
  322                     endpoint_ref = {'id': uuid.uuid4().hex,
  323                                     'interface': interface,
  324                                     'url': url,
  325                                     'service_id': self.service_id,
  326                                     'enabled': True}
  327 
  328                     if self.region_id:
  329                         endpoint_ref['region_id'] = self.region_id
  330 
  331                     PROVIDERS.catalog_api.create_endpoint(
  332                         endpoint_id=endpoint_ref['id'],
  333                         endpoint_ref=endpoint_ref)
  334 
  335                     LOG.info('Created %(interface)s endpoint %(url)s',
  336                              {'interface': interface, 'url': url})
  337                 else:
  338                     endpoint_ref['url'] = url
  339                     PROVIDERS.catalog_api.update_endpoint(
  340                         endpoint_id=endpoint_ref['id'],
  341                         endpoint_ref=endpoint_ref)
  342                     LOG.info('%s endpoint updated', interface)
  343 
  344                 self.endpoints[interface] = endpoint_ref['id']