keystone-manage doctor
This introduces a new keystone-manage command called 'doctor' which attempts to diagnose and report on various ill-advised configurations and deployment states. The number of checks we could perform is basically endless, so this is just a random sampling of checks to get the ball rolling. The idea is that as new features are introduced, as default configurations change, as we have new recommendations to make to deployers, etc, we can implement new checks in keystone-manage doctor and communicate our concerns directly to those operated affected deployments. Change-Id: Ib6660c1a885c439ca03357870628b2ea52e39e9d Implements: bp keystone-manage-doctor
This commit is contained in:
parent
5ac2029856
commit
059f35302d
|
@ -43,6 +43,7 @@ Available commands:
|
|||
* ``bootstrap``: Perform the basic bootstrap process.
|
||||
* ``db_sync``: Sync the database.
|
||||
* ``db_version``: Print the current migration version of the database.
|
||||
* ``doctor``: Diagnose common problems with keystone deployments.
|
||||
* ``domain_config_upload``: Upload domain configuration file.
|
||||
* ``fernet_rotate``: Rotate keys in the Fernet key repository.
|
||||
* ``fernet_setup``: Setup a Fernet key repository.
|
||||
|
|
|
@ -25,6 +25,7 @@ from oslo_log import versionutils
|
|||
from oslo_serialization import jsonutils
|
||||
import pbr.version
|
||||
|
||||
from keystone.cmd import doctor
|
||||
from keystone.common import driver_hints
|
||||
from keystone.common import openssl
|
||||
from keystone.common import sql
|
||||
|
@ -361,6 +362,22 @@ class BootStrap(BaseApp):
|
|||
klass.do_bootstrap()
|
||||
|
||||
|
||||
class Doctor(BaseApp):
|
||||
"""Diagnose common problems with keystone deployments."""
|
||||
|
||||
name = 'doctor'
|
||||
|
||||
@classmethod
|
||||
def add_argument_parser(cls, subparsers):
|
||||
parser = super(Doctor, cls).add_argument_parser(subparsers)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def main():
|
||||
# Return a non-zero exit code if we detect any symptoms.
|
||||
raise SystemExit(doctor.diagnose())
|
||||
|
||||
|
||||
class DbSync(BaseApp):
|
||||
"""Sync the database."""
|
||||
|
||||
|
@ -942,6 +959,7 @@ CMDS = [
|
|||
BootStrap,
|
||||
DbSync,
|
||||
DbVersion,
|
||||
Doctor,
|
||||
DomainConfigUpload,
|
||||
FernetRotate,
|
||||
FernetSetup,
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# 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.
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from keystone.cmd.doctor import caching
|
||||
from keystone.cmd.doctor import database
|
||||
from keystone.cmd.doctor import federation
|
||||
from keystone.cmd.doctor import ldap
|
||||
from keystone.cmd.doctor import tokens
|
||||
from keystone.cmd.doctor import tokens_fernet
|
||||
import keystone.conf
|
||||
from keystone.i18n import _
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
SYMPTOM_PREFIX = 'symptom_'
|
||||
SYMPTOM_MODULES = [
|
||||
caching,
|
||||
database,
|
||||
federation,
|
||||
ldap,
|
||||
tokens,
|
||||
tokens_fernet]
|
||||
|
||||
|
||||
def diagnose():
|
||||
"""Report diagnosis for any symptoms we find.
|
||||
|
||||
Returns true when any symptoms are found, false otherwise.
|
||||
"""
|
||||
symptoms_found = False
|
||||
|
||||
for symptom in gather_symptoms():
|
||||
if CONF.debug:
|
||||
# Some symptoms may take a long time to check, so let's keep
|
||||
# curious users posted on our progress as we go.
|
||||
print(
|
||||
'Checking for %s...' %
|
||||
symptom.__name__[len(SYMPTOM_PREFIX):].replace('_', ' '))
|
||||
|
||||
# All symptoms are just callables that return true when they match the
|
||||
# condition that they're looking for. When that happens, we need to
|
||||
# inform the user by providing some helpful documentation.
|
||||
if symptom():
|
||||
# We use this to keep track of our exit condition
|
||||
symptoms_found = True
|
||||
|
||||
# Ignore 'H701: empty localization string' because we're definitely
|
||||
# passing a string here. Also, we include a line break here to
|
||||
# visually separate the symptom's description from any other
|
||||
# checks -- it provides a better user experience.
|
||||
print(_('\nWARNING: %s') % _(symptom.__doc__)) # noqa: See comment above.
|
||||
|
||||
return symptoms_found
|
||||
|
||||
|
||||
def gather_symptoms():
|
||||
"""Gather all of the objects in this module that are named symptom_*."""
|
||||
symptoms = []
|
||||
for module in SYMPTOM_MODULES:
|
||||
for name in dir(module):
|
||||
if name.startswith(SYMPTOM_PREFIX):
|
||||
symptoms.append(getattr(module, name))
|
||||
return symptoms
|
|
@ -0,0 +1,35 @@
|
|||
# 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 keystone.conf
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
def symptom_caching_disabled():
|
||||
"""`keystone.conf [caching] enabled` is not enabled.
|
||||
|
||||
Caching greatly improves the performance of keystone, and it is highly
|
||||
recommended that you enable it.
|
||||
"""
|
||||
return not CONF.cache.enabled and not CONF.debug
|
||||
|
||||
|
||||
def symptom_caching_enabled_without_a_backend():
|
||||
"""Caching is not completely configured.
|
||||
|
||||
Although caching is enabled in `keystone.conf [cache] enabled`, the default
|
||||
backend is still set to the no-op backend. Instead, configure keystone to
|
||||
point to a real caching backend like memcached.
|
||||
"""
|
||||
return CONF.cache.enabled and CONF.cache.backend == 'dogpile.cache.null'
|
|
@ -0,0 +1,30 @@
|
|||
# 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 keystone.conf
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
def symptom_database_connection_is_not_SQLite():
|
||||
"""SQLite is not recommended for production deployments.
|
||||
|
||||
SQLite does not enforce type checking and has limited support for
|
||||
migrations, making it unsuitable for use in keystone. Please change your
|
||||
`keystone.conf [database] connection` value to point to a supported
|
||||
database driver, such as MySQL.
|
||||
"""
|
||||
return (
|
||||
CONF.database.connection is not None
|
||||
and 'sqlite' in CONF.database.connection
|
||||
and not CONF.debug)
|
|
@ -0,0 +1,36 @@
|
|||
# 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 keystone.conf
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
def symptom_comma_in_SAML_public_certificate_path():
|
||||
"""`[saml] certfile` should not contain a comma (`,`).
|
||||
|
||||
Because a comma is part of the API between keystone and the external
|
||||
xmlsec1 binary which utilizes the certificate, keystone cannot include a
|
||||
comma in the path to the public certificate file.
|
||||
"""
|
||||
return ',' in CONF.saml.certfile
|
||||
|
||||
|
||||
def symptom_comma_in_SAML_private_key_file_path():
|
||||
"""`[saml] certfile` should not contain a comma (`,`).
|
||||
|
||||
Because a comma is part of the API between keystone and the external
|
||||
xmlsec1 binary which utilizes the key, keystone cannot include a comma in
|
||||
the path to the private key file.
|
||||
"""
|
||||
return ',' in CONF.saml.keyfile
|
|
@ -0,0 +1,52 @@
|
|||
# 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 keystone.conf
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
def symptom_LDAP_user_enabled_emulation_dn_ignored():
|
||||
"""`[ldap] user_enabled_emulation_dn` is being ignored.
|
||||
|
||||
There is no reason to set this value unless `keystone.conf [ldap]
|
||||
user_enabled_emulation` is also enabled.
|
||||
"""
|
||||
return (
|
||||
not CONF.ldap.user_enabled_emulation
|
||||
and CONF.ldap.user_enabled_emulation_dn is not None)
|
||||
|
||||
|
||||
def symptom_LDAP_user_enabled_emulation_use_group_config_ignored():
|
||||
"""`[ldap] user_enabled_emulation_use_group_config` is being ignored.
|
||||
|
||||
There is no reason to set this value unless `keystone.conf [ldap]
|
||||
user_enabled_emulation` is also enabled.
|
||||
"""
|
||||
return (
|
||||
not CONF.ldap.user_enabled_emulation
|
||||
and CONF.ldap.user_enabled_emulation_use_group_config)
|
||||
|
||||
|
||||
def symptom_LDAP_group_members_are_ids_disabled():
|
||||
"""`[ldap] group_members_are_ids` is not enabled.
|
||||
|
||||
Because you've set `keystone.conf [ldap] group_objectclass = posixGroup`,
|
||||
we would have also expected you to enable set `keystone.conf [ldap]
|
||||
group_members_are_ids` because we suspect you're using Open Directory,
|
||||
which would contain user ID's in a `posixGroup` rather than LDAP DNs, as
|
||||
other object classes typically would.
|
||||
"""
|
||||
return (
|
||||
CONF.ldap.group_objectclass == 'posixGroup'
|
||||
and not CONF.ldap.group_members_are_ids)
|
|
@ -0,0 +1,46 @@
|
|||
# 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 keystone.conf
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
def symptom_unreasonable_max_token_size():
|
||||
"""`keystone.conf [DEFAULT] max_token_size` should be adjusted.
|
||||
|
||||
This option is intended to protect keystone from unreasonably sized tokens,
|
||||
where "reasonable" is mostly dependent on the `keystone.conf [token]
|
||||
provider` that you're using. If you're using one of the following token
|
||||
providers, then you should set `keystone.conf [DEFAULT] max_token_size`
|
||||
accordingly:
|
||||
|
||||
- For UUID, set `keystone.conf [DEFAULT] max_token_size = 32`, because UUID
|
||||
tokens are always exactly 32 characters.
|
||||
|
||||
- For PKI and PKIZ, set `keystone.conf [DEFAULT] max_token_size = 8192`,
|
||||
because PKI and PKIZ tokens can be quite large, but any larger than 8192
|
||||
and they tend to break certain implementations of HTTP.
|
||||
|
||||
- For Fernet, set `keystone.conf [DEFAULT] max_token_size = 255`, because
|
||||
Fernet tokens should never exceed this length in most deployments.
|
||||
However, if you are also using `keystone.conf [identity] driver = ldap`,
|
||||
Fernet tokens may not be built using an efficient packing method,
|
||||
depending on the IDs returned from LDAP, resulting in longer Fernet
|
||||
tokens (adjust your `max_token_size` accordingly).
|
||||
"""
|
||||
return (
|
||||
'uuid' in CONF.token.provider and CONF.max_token_size != 32
|
||||
or 'pki' in CONF.token.provider and CONF.max_token_size < 8192
|
||||
or 'pkiz' in CONF.token.provider and CONF.max_token_size < 8192
|
||||
or 'fernet' in CONF.token.provider and CONF.max_token_size > 255)
|
|
@ -0,0 +1,43 @@
|
|||
# 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 keystone.conf
|
||||
|
||||
from keystone.token.providers.fernet import utils as fernet_utils
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
def symptom_usability_of_Fernet_key_repository():
|
||||
"""Fernet key repository is not setup correctly.
|
||||
|
||||
The Fernet key repository is expected to be readable by the user running
|
||||
keystone, but not world-readable, because it contains security-sensitive
|
||||
secrets.
|
||||
"""
|
||||
return (
|
||||
'fernet' in CONF.token.provider
|
||||
and not fernet_utils.validate_key_repository())
|
||||
|
||||
|
||||
def symptom_keys_in_Fernet_key_repository():
|
||||
"""Fernet key repository is empty.
|
||||
|
||||
After configuring keystone to use the Fernet token provider, you should use
|
||||
`keystone-manage fernet_setup` to initially populate your key repository
|
||||
with keys, and periodically rotate your keys with `keystone-manage
|
||||
fernet_rotate`.
|
||||
"""
|
||||
return (
|
||||
'fernet' in CONF.token.provider
|
||||
and not fernet_utils.load_keys())
|
Loading…
Reference in New Issue