]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/settings.py
6018f0d7f9c73facc52097e0efe1db1df6e33ef3
[ceph.git] / ceph / src / pybind / mgr / dashboard / settings.py
1 # -*- coding: utf-8 -*-
2 import errno
3 import inspect
4 from ast import literal_eval
5 from typing import Any
6
7 from mgr_module import CLICheckNonemptyFileInput
8
9 from . import mgr
10
11
12 class Setting:
13 """
14 Setting representation that allows to set a default value and a list of allowed data types.
15 :param default_value: The name of the bucket.
16 :param types: a list consisting of the primary/preferred type and, optionally,
17 secondary/legacy types for backward compatibility.
18 """
19
20 def __init__(self, default_value: Any, types: list):
21 if not isinstance(types, list):
22 raise ValueError('Setting types must be a list.')
23 default_value_type = type(default_value)
24 if default_value_type not in types:
25 raise ValueError('Default value type not allowed.')
26 self.default_value = default_value
27 self.types = types
28
29 def types_as_str(self):
30 return ','.join([x.__name__ for x in self.types])
31
32 def cast(self, value):
33 for type_index, setting_type in enumerate(self.types):
34 try:
35 if setting_type.__name__ == 'bool' and str(value).lower() == 'false':
36 return False
37 elif setting_type.__name__ == 'dict':
38 return literal_eval(value)
39 return setting_type(value)
40 except (SyntaxError, TypeError, ValueError) as error:
41 if type_index == len(self.types) - 1:
42 raise error
43
44
45 class Options(object):
46 """
47 If you need to store some configuration value please add the config option
48 name as a class attribute to this class.
49
50 Example::
51
52 GRAFANA_API_HOST = ('localhost', str)
53 GRAFANA_API_PORT = (3000, int)
54 """
55 ENABLE_BROWSABLE_API = Setting(True, [bool])
56 REST_REQUESTS_TIMEOUT = Setting(45, [int])
57
58 # AUTHENTICATION ATTEMPTS
59 ACCOUNT_LOCKOUT_ATTEMPTS = Setting(10, [int])
60
61 # API auditing
62 AUDIT_API_ENABLED = Setting(False, [bool])
63 AUDIT_API_LOG_PAYLOAD = Setting(True, [bool])
64
65 # RGW settings
66 RGW_API_ACCESS_KEY = Setting('', [dict, str])
67 RGW_API_SECRET_KEY = Setting('', [dict, str])
68 RGW_API_ADMIN_RESOURCE = Setting('admin', [str])
69 RGW_API_SSL_VERIFY = Setting(True, [bool])
70
71 # Ceph Issue Tracker API Access Key
72 ISSUE_TRACKER_API_KEY = Setting('', [str])
73
74 # Grafana settings
75 GRAFANA_API_URL = Setting('', [str])
76 GRAFANA_FRONTEND_API_URL = Setting('', [str])
77 GRAFANA_API_USERNAME = Setting('admin', [str])
78 GRAFANA_API_PASSWORD = Setting('admin', [str])
79 GRAFANA_API_SSL_VERIFY = Setting(True, [bool])
80 GRAFANA_UPDATE_DASHBOARDS = Setting(False, [bool])
81
82 # NFS Ganesha settings
83 GANESHA_CLUSTERS_RADOS_POOL_NAMESPACE = Setting('', [str])
84
85 # Prometheus settings
86 PROMETHEUS_API_HOST = Setting('', [str])
87 PROMETHEUS_API_SSL_VERIFY = Setting(True, [bool])
88 ALERTMANAGER_API_HOST = Setting('', [str])
89 ALERTMANAGER_API_SSL_VERIFY = Setting(True, [bool])
90
91 # iSCSI management settings
92 ISCSI_API_SSL_VERIFICATION = Setting(True, [bool])
93
94 # user management settings
95 # Time span of user passwords to expire in days.
96 # The default value is '0' which means that user passwords are
97 # never going to expire.
98 USER_PWD_EXPIRATION_SPAN = Setting(0, [int])
99 # warning levels to notify the user that the password is going
100 # to expire soon
101 USER_PWD_EXPIRATION_WARNING_1 = Setting(10, [int])
102 USER_PWD_EXPIRATION_WARNING_2 = Setting(5, [int])
103
104 # Password policy
105 PWD_POLICY_ENABLED = Setting(True, [bool])
106 # Individual checks
107 PWD_POLICY_CHECK_LENGTH_ENABLED = Setting(True, [bool])
108 PWD_POLICY_CHECK_OLDPWD_ENABLED = Setting(True, [bool])
109 PWD_POLICY_CHECK_USERNAME_ENABLED = Setting(False, [bool])
110 PWD_POLICY_CHECK_EXCLUSION_LIST_ENABLED = Setting(False, [bool])
111 PWD_POLICY_CHECK_COMPLEXITY_ENABLED = Setting(False, [bool])
112 PWD_POLICY_CHECK_SEQUENTIAL_CHARS_ENABLED = Setting(False, [bool])
113 PWD_POLICY_CHECK_REPETITIVE_CHARS_ENABLED = Setting(False, [bool])
114 # Settings
115 PWD_POLICY_MIN_LENGTH = Setting(8, [int])
116 PWD_POLICY_MIN_COMPLEXITY = Setting(10, [int])
117 PWD_POLICY_EXCLUSION_LIST = Setting(','.join(['osd', 'host', 'dashboard', 'pool',
118 'block', 'nfs', 'ceph', 'monitors',
119 'gateway', 'logs', 'crush', 'maps']),
120 [str])
121
122 @staticmethod
123 def has_default_value(name):
124 return getattr(Settings, name, None) is None or \
125 getattr(Settings, name) == getattr(Options, name).default_value
126
127
128 class SettingsMeta(type):
129 def __getattr__(cls, attr):
130 setting = getattr(Options, attr)
131 return setting.cast(mgr.get_module_option(attr, setting.default_value))
132
133 def __setattr__(cls, attr, value):
134 if not attr.startswith('_') and hasattr(Options, attr):
135 mgr.set_module_option(attr, str(value))
136 else:
137 setattr(SettingsMeta, attr, value)
138
139 def __delattr__(cls, attr):
140 if not attr.startswith('_') and hasattr(Options, attr):
141 mgr.set_module_option(attr, None)
142
143
144 # pylint: disable=no-init
145 class Settings(object, metaclass=SettingsMeta):
146 pass
147
148
149 def _options_command_map():
150 def filter_attr(member):
151 return not inspect.isroutine(member)
152
153 cmd_map = {}
154 for option, setting in inspect.getmembers(Options, filter_attr):
155 if option.startswith('_'):
156 continue
157 key_get = 'dashboard get-{}'.format(option.lower().replace('_', '-'))
158 key_set = 'dashboard set-{}'.format(option.lower().replace('_', '-'))
159 key_reset = 'dashboard reset-{}'.format(option.lower().replace('_', '-'))
160 cmd_map[key_get] = {'name': option, 'type': None}
161 cmd_map[key_set] = {'name': option, 'type': setting.types_as_str()}
162 cmd_map[key_reset] = {'name': option, 'type': None}
163 return cmd_map
164
165
166 _OPTIONS_COMMAND_MAP = _options_command_map()
167
168
169 def options_command_list():
170 """
171 This function generates a list of ``get`` and ``set`` commands
172 for each declared configuration option in class ``Options``.
173 """
174 def py2ceph(pytype):
175 if pytype == str:
176 return 'CephString'
177 elif pytype == int:
178 return 'CephInt'
179 return 'CephString'
180
181 cmd_list = []
182 for cmd, opt in _OPTIONS_COMMAND_MAP.items():
183 if cmd.startswith('dashboard get'):
184 cmd_list.append({
185 'cmd': '{}'.format(cmd),
186 'desc': 'Get the {} option value'.format(opt['name']),
187 'perm': 'r'
188 })
189 elif cmd.startswith('dashboard set'):
190 cmd_entry = {
191 'cmd': '{} name=value,type={}'
192 .format(cmd, py2ceph(opt['type'])),
193 'desc': 'Set the {} option value'.format(opt['name']),
194 'perm': 'w'
195 }
196 if handles_secret(cmd):
197 cmd_entry['cmd'] = cmd
198 cmd_entry['desc'] = '{} read from -i <file>'.format(cmd_entry['desc'])
199 cmd_list.append(cmd_entry)
200 elif cmd.startswith('dashboard reset'):
201 desc = 'Reset the {} option to its default value'.format(
202 opt['name'])
203 cmd_list.append({
204 'cmd': '{}'.format(cmd),
205 'desc': desc,
206 'perm': 'w'
207 })
208
209 return cmd_list
210
211
212 def options_schema_list():
213 def filter_attr(member):
214 return not inspect.isroutine(member)
215
216 result = []
217 for option, setting in inspect.getmembers(Options, filter_attr):
218 if option.startswith('_'):
219 continue
220 result.append({'name': option, 'default': setting.default_value,
221 'type': setting.types_as_str()})
222
223 return result
224
225
226 def handle_option_command(cmd, inbuf):
227 if cmd['prefix'] not in _OPTIONS_COMMAND_MAP:
228 return -errno.ENOSYS, '', "Command not found '{}'".format(cmd['prefix'])
229
230 opt = _OPTIONS_COMMAND_MAP[cmd['prefix']]
231
232 if cmd['prefix'].startswith('dashboard reset'):
233 delattr(Settings, opt['name'])
234 return 0, 'Option {} reset to default value "{}"'.format(
235 opt['name'], getattr(Settings, opt['name'])), ''
236 elif cmd['prefix'].startswith('dashboard get'):
237 return 0, str(getattr(Settings, opt['name'])), ''
238 elif cmd['prefix'].startswith('dashboard set'):
239 if handles_secret(cmd['prefix']):
240 value, stdout, stderr = get_secret(inbuf=inbuf)
241 if stderr:
242 return value, stdout, stderr
243 else:
244 value = cmd['value']
245 setting = getattr(Options, opt['name'])
246 setattr(Settings, opt['name'], setting.cast(value))
247 return 0, 'Option {} updated'.format(opt['name']), ''
248
249
250 def handles_secret(cmd: str) -> bool:
251 return bool([cmd for secret_word in ['password', 'key'] if (secret_word in cmd)])
252
253
254 @CLICheckNonemptyFileInput(desc='password/secret')
255 def get_secret(inbuf=None):
256 return inbuf, None, None