1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright: (c) 2019, F5 Networks Inc. 5# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = r''' 11--- 12module: bigip_remote_user 13short_description: Manages default settings for remote user accounts on a BIG-IP 14description: 15 - Manages the default settings for remote user accounts on a BIG-IP system. 16version_added: "1.0.0" 17options: 18 default_role: 19 description: 20 - Specifies the default role for all remote user accounts. 21 - The default system value is C(no-access). 22 type: str 23 choices: 24 - acceleration-policy-editor 25 - admin 26 - application-editor 27 - auditor 28 - certificate-manager 29 - firewall-manager 30 - fraud-protection-manager 31 - guest 32 - irule-manager 33 - manager 34 - no-access 35 - operator 36 - resource-admin 37 - user-manager 38 - web-application-security-administrator 39 - web-application-security-editor 40 default_partition: 41 description: 42 - Specifies the default partition for all remote user accounts. 43 - The default system value is C(all) for all partitions. 44 type: str 45 console_access: 46 description: 47 - Enables or disables the default console access for all remote user accounts. 48 - The default system value is C(disabled). 49 type: bool 50 description: 51 description: 52 - User-defined description. 53 type: str 54extends_documentation_fragment: f5networks.f5_modules.f5 55author: 56 - Wojciech Wypior (@wojtek0806) 57''' 58 59EXAMPLES = r''' 60- name: Modify default partition and console access 61 bigip_remote_user: 62 default_partition: Common 63 console_access: yes 64 provider: 65 password: secret 66 server: lb.mydomain.com 67 user: admin 68 delegate_to: localhost 69 70- name: Modify default role, partition and console access 71 bigip_remote_user: 72 default_partition: Common 73 default_role: manager 74 console_access: yes 75 description: "Changed new settings" 76 provider: 77 password: secret 78 server: lb.mydomain.com 79 user: admin 80 delegate_to: localhost 81 82- name: Revert to default settings 83 bigip_remote_user: 84 default_partition: all 85 default_role: "no-access" 86 console_access: no 87 description: "" 88 provider: 89 password: secret 90 server: lb.mydomain.com 91 user: admin 92 delegate_to: localhost 93''' 94 95RETURN = r''' 96default_role: 97 description: The default role for all remote user accounts. 98 returned: changed 99 type: str 100 sample: auditor 101default_partition: 102 description: The default partition for all remote user accounts. 103 returned: changed 104 type: str 105 sample: Common 106console_access: 107 description: The default console access for all remote user accounts. 108 returned: changed 109 type: bool 110 sample: no 111description: 112 description: The user-defined description. 113 returned: changed 114 type: str 115 sample: Foo is bar 116''' 117from datetime import datetime 118 119from ansible.module_utils.basic import AnsibleModule 120 121from ..module_utils.bigip import F5RestClient 122from ..module_utils.common import ( 123 F5ModuleError, AnsibleF5Parameters, f5_argument_spec, flatten_boolean 124) 125from ..module_utils.compare import cmp_str_with_none 126from ..module_utils.icontrol import tmos_version 127from ..module_utils.teem import send_teem 128 129 130class Parameters(AnsibleF5Parameters): 131 api_map = { 132 'defaultPartition': 'default_partition', 133 'defaultRole': 'default_role', 134 'remoteConsoleAccess': 'console_access', 135 } 136 137 api_attributes = [ 138 'defaultPartition', 139 'defaultRole', 140 'description', 141 'remoteConsoleAccess', 142 143 ] 144 145 returnables = [ 146 'default_partition', 147 'default_role', 148 'console_access', 149 'description', 150 ] 151 152 updatables = [ 153 'default_partition', 154 'default_role', 155 'console_access', 156 'description', 157 ] 158 159 160class ApiParameters(Parameters): 161 pass 162 163 164class ModuleParameters(Parameters): 165 @property 166 def console_access(self): 167 result = flatten_boolean(self._values['console_access']) 168 if result == 'yes': 169 return 'tmsh' 170 if result == 'no': 171 return 'disabled' 172 173 174class Changes(Parameters): 175 def to_return(self): 176 result = {} 177 try: 178 for returnable in self.returnables: 179 result[returnable] = getattr(self, returnable) 180 result = self._filter_params(result) 181 except Exception: 182 pass 183 return result 184 185 186class UsableChanges(Changes): 187 pass 188 189 190class ReportableChanges(Changes): 191 @property 192 def console_access(self): 193 if self._values['console_access'] is None: 194 return None 195 if self._values['console_access'] == 'tmsh': 196 return 'yes' 197 if self._values['console_access'] == 'disabled': 198 return 'no' 199 200 201class Difference(object): 202 def __init__(self, want, have=None): 203 self.want = want 204 self.have = have 205 206 def compare(self, param): 207 try: 208 result = getattr(self, param) 209 return result 210 except AttributeError: 211 return self.__default(param) 212 213 def __default(self, param): 214 attr1 = getattr(self.want, param) 215 try: 216 attr2 = getattr(self.have, param) 217 if attr1 != attr2: 218 return attr1 219 except AttributeError: 220 return attr1 221 222 @property 223 def description(self): 224 result = cmp_str_with_none(self.want.description, self.have.description) 225 return result 226 227 228class ModuleManager(object): 229 def __init__(self, *args, **kwargs): 230 self.module = kwargs.get('module', None) 231 self.client = F5RestClient(**self.module.params) 232 self.want = ModuleParameters(params=self.module.params) 233 self.have = ApiParameters() 234 self.changes = UsableChanges() 235 236 def _update_changed_options(self): 237 diff = Difference(self.want, self.have) 238 updatables = Parameters.updatables 239 changed = dict() 240 for k in updatables: 241 change = diff.compare(k) 242 if change is None: 243 continue 244 else: 245 if isinstance(change, dict): 246 changed.update(change) 247 else: 248 changed[k] = change 249 if changed: 250 self.changes = UsableChanges(params=changed) 251 return True 252 return False 253 254 def _announce_deprecations(self, result): 255 warnings = result.pop('__warnings', []) 256 for warning in warnings: 257 self.client.module.deprecate( 258 msg=warning['msg'], 259 version=warning['version'] 260 ) 261 262 def exec_module(self): 263 start = datetime.now().isoformat() 264 version = tmos_version(self.client) 265 result = dict() 266 267 changed = self.update() 268 269 reportable = ReportableChanges(params=self.changes.to_return()) 270 changes = reportable.to_return() 271 result.update(**changes) 272 result.update(dict(changed=changed)) 273 self._announce_deprecations(result) 274 send_teem(start, self.client, self.module, version) 275 return result 276 277 def should_update(self): 278 result = self._update_changed_options() 279 if result: 280 return True 281 return False 282 283 def update(self): 284 self.have = self.read_current_from_device() 285 if not self.should_update(): 286 return False 287 if self.module.check_mode: 288 return True 289 self.update_on_device() 290 return True 291 292 def update_on_device(self): 293 params = self.changes.api_params() 294 uri = "https://{0}:{1}/mgmt/tm/auth/remote-user/".format( 295 self.client.provider['server'], 296 self.client.provider['server_port'], 297 ) 298 resp = self.client.api.patch(uri, json=params) 299 try: 300 response = resp.json() 301 except ValueError as ex: 302 raise F5ModuleError(str(ex)) 303 304 if 'code' in response and response['code'] == 400: 305 if 'message' in response: 306 raise F5ModuleError(response['message']) 307 else: 308 raise F5ModuleError(resp.content) 309 310 def read_current_from_device(self): 311 uri = "https://{0}:{1}/mgmt/tm/auth/remote-user/".format( 312 self.client.provider['server'], 313 self.client.provider['server_port'], 314 ) 315 resp = self.client.api.get(uri) 316 try: 317 response = resp.json() 318 except ValueError as ex: 319 raise F5ModuleError(str(ex)) 320 321 if 'code' in response and response['code'] == 400: 322 if 'message' in response: 323 raise F5ModuleError(response['message']) 324 else: 325 raise F5ModuleError(resp.content) 326 return ApiParameters(params=response) 327 328 329class ArgumentSpec(object): 330 def __init__(self): 331 self.supports_check_mode = True 332 self.choices = [ 333 'acceleration-policy-editor', 334 'admin', 335 'application-editor', 336 'auditor', 337 'certificate-manager', 338 'firewall-manager', 339 'fraud-protection-manager', 340 'guest', 341 'irule-manager', 342 'manager', 343 'no-access', 344 'operator', 345 'resource-admin', 346 'user-manager', 347 'web-application-security-administrator', 348 'web-application-security-editor' 349 ] 350 argument_spec = dict( 351 default_role=dict( 352 choices=self.choices 353 ), 354 default_partition=dict(), 355 console_access=dict(type='bool'), 356 description=dict() 357 ) 358 self.argument_spec = {} 359 self.argument_spec.update(f5_argument_spec) 360 self.argument_spec.update(argument_spec) 361 self.required_one_of = [ 362 ['default_role', 'default_partition'] 363 ] 364 365 366def main(): 367 spec = ArgumentSpec() 368 369 module = AnsibleModule( 370 argument_spec=spec.argument_spec, 371 supports_check_mode=spec.supports_check_mode, 372 ) 373 374 try: 375 mm = ModuleManager(module=module) 376 results = mm.exec_module() 377 module.exit_json(**results) 378 except F5ModuleError as ex: 379 module.fail_json(msg=str(ex)) 380 381 382if __name__ == '__main__': 383 main() 384