1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17 18from __future__ import absolute_import, division, print_function 19__metaclass__ = type 20 21DOCUMENTATION = ''' 22--- 23module: oneandone_firewall_policy 24short_description: Configure 1&1 firewall policy. 25description: 26 - Create, remove, reconfigure, update firewall policies. 27 This module has a dependency on 1and1 >= 1.0 28options: 29 state: 30 description: 31 - Define a firewall policy state to create, remove, or update. 32 required: false 33 type: str 34 default: 'present' 35 choices: [ "present", "absent", "update" ] 36 auth_token: 37 description: 38 - Authenticating API token provided by 1&1. 39 type: str 40 api_url: 41 description: 42 - Custom API URL. Overrides the 43 ONEANDONE_API_URL environment variable. 44 type: str 45 required: false 46 name: 47 description: 48 - Firewall policy name used with present state. Used as identifier (id or name) when used with absent state. 49 maxLength=128 50 type: str 51 firewall_policy: 52 description: 53 - The identifier (id or name) of the firewall policy used with update state. 54 type: str 55 rules: 56 description: 57 - A list of rules that will be set for the firewall policy. 58 Each rule must contain protocol parameter, in addition to three optional parameters 59 (port_from, port_to, and source) 60 type: list 61 elements: dict 62 add_server_ips: 63 description: 64 - A list of server identifiers (id or name) to be assigned to a firewall policy. 65 Used in combination with update state. 66 type: list 67 elements: str 68 required: false 69 remove_server_ips: 70 description: 71 - A list of server IP ids to be unassigned from a firewall policy. Used in combination with update state. 72 type: list 73 elements: str 74 required: false 75 add_rules: 76 description: 77 - A list of rules that will be added to an existing firewall policy. 78 It is syntax is the same as the one used for rules parameter. Used in combination with update state. 79 type: list 80 elements: dict 81 required: false 82 remove_rules: 83 description: 84 - A list of rule ids that will be removed from an existing firewall policy. Used in combination with update state. 85 type: list 86 elements: str 87 required: false 88 description: 89 description: 90 - Firewall policy description. maxLength=256 91 type: str 92 required: false 93 wait: 94 description: 95 - wait for the instance to be in state 'running' before returning 96 required: false 97 default: "yes" 98 type: bool 99 wait_timeout: 100 description: 101 - how long before wait gives up, in seconds 102 type: int 103 default: 600 104 wait_interval: 105 description: 106 - Defines the number of seconds to wait when using the _wait_for methods 107 type: int 108 default: 5 109 110requirements: 111 - "1and1" 112 - "python >= 2.6" 113 114author: 115 - "Amel Ajdinovic (@aajdinov)" 116 - "Ethan Devenport (@edevenport)" 117''' 118 119EXAMPLES = ''' 120- name: Create a firewall policy 121 community.general.oneandone_firewall_policy: 122 auth_token: oneandone_private_api_key 123 name: ansible-firewall-policy 124 description: Testing creation of firewall policies with ansible 125 rules: 126 - 127 protocol: TCP 128 port_from: 80 129 port_to: 80 130 source: 0.0.0.0 131 wait: true 132 wait_timeout: 500 133 134- name: Destroy a firewall policy 135 community.general.oneandone_firewall_policy: 136 auth_token: oneandone_private_api_key 137 state: absent 138 name: ansible-firewall-policy 139 140- name: Update a firewall policy 141 community.general.oneandone_firewall_policy: 142 auth_token: oneandone_private_api_key 143 state: update 144 firewall_policy: ansible-firewall-policy 145 name: ansible-firewall-policy-updated 146 description: Testing creation of firewall policies with ansible - updated 147 148- name: Add server to a firewall policy 149 community.general.oneandone_firewall_policy: 150 auth_token: oneandone_private_api_key 151 firewall_policy: ansible-firewall-policy-updated 152 add_server_ips: 153 - server_identifier (id or name) 154 - server_identifier #2 (id or name) 155 wait: true 156 wait_timeout: 500 157 state: update 158 159- name: Remove server from a firewall policy 160 community.general.oneandone_firewall_policy: 161 auth_token: oneandone_private_api_key 162 firewall_policy: ansible-firewall-policy-updated 163 remove_server_ips: 164 - B2504878540DBC5F7634EB00A07C1EBD (server's IP id) 165 wait: true 166 wait_timeout: 500 167 state: update 168 169- name: Add rules to a firewall policy 170 community.general.oneandone_firewall_policy: 171 auth_token: oneandone_private_api_key 172 firewall_policy: ansible-firewall-policy-updated 173 description: Adding rules to an existing firewall policy 174 add_rules: 175 - 176 protocol: TCP 177 port_from: 70 178 port_to: 70 179 source: 0.0.0.0 180 - 181 protocol: TCP 182 port_from: 60 183 port_to: 60 184 source: 0.0.0.0 185 wait: true 186 wait_timeout: 500 187 state: update 188 189- name: Remove rules from a firewall policy 190 community.general.oneandone_firewall_policy: 191 auth_token: oneandone_private_api_key 192 firewall_policy: ansible-firewall-policy-updated 193 remove_rules: 194 - rule_id #1 195 - rule_id #2 196 - ... 197 wait: true 198 wait_timeout: 500 199 state: update 200''' 201 202RETURN = ''' 203firewall_policy: 204 description: Information about the firewall policy that was processed 205 type: dict 206 sample: '{"id": "92B74394A397ECC3359825C1656D67A6", "name": "Default Policy"}' 207 returned: always 208''' 209 210import os 211from ansible.module_utils.basic import AnsibleModule 212from ansible_collections.community.general.plugins.module_utils.oneandone import ( 213 get_firewall_policy, 214 get_server, 215 OneAndOneResources, 216 wait_for_resource_creation_completion 217) 218 219HAS_ONEANDONE_SDK = True 220 221try: 222 import oneandone.client 223except ImportError: 224 HAS_ONEANDONE_SDK = False 225 226 227def _check_mode(module, result): 228 if module.check_mode: 229 module.exit_json( 230 changed=result 231 ) 232 233 234def _add_server_ips(module, oneandone_conn, firewall_id, server_ids): 235 """ 236 Assigns servers to a firewall policy. 237 """ 238 try: 239 attach_servers = [] 240 241 for _server_id in server_ids: 242 server = get_server(oneandone_conn, _server_id, True) 243 attach_server = oneandone.client.AttachServer( 244 server_id=server['id'], 245 server_ip_id=next(iter(server['ips'] or []), None)['id'] 246 ) 247 attach_servers.append(attach_server) 248 249 if module.check_mode: 250 if attach_servers: 251 return True 252 return False 253 254 firewall_policy = oneandone_conn.attach_server_firewall_policy( 255 firewall_id=firewall_id, 256 server_ips=attach_servers) 257 return firewall_policy 258 except Exception as e: 259 module.fail_json(msg=str(e)) 260 261 262def _remove_firewall_server(module, oneandone_conn, firewall_id, server_ip_id): 263 """ 264 Unassigns a server/IP from a firewall policy. 265 """ 266 try: 267 if module.check_mode: 268 firewall_server = oneandone_conn.get_firewall_server( 269 firewall_id=firewall_id, 270 server_ip_id=server_ip_id) 271 if firewall_server: 272 return True 273 return False 274 275 firewall_policy = oneandone_conn.remove_firewall_server( 276 firewall_id=firewall_id, 277 server_ip_id=server_ip_id) 278 return firewall_policy 279 except Exception as e: 280 module.fail_json(msg=str(e)) 281 282 283def _add_firewall_rules(module, oneandone_conn, firewall_id, rules): 284 """ 285 Adds new rules to a firewall policy. 286 """ 287 try: 288 firewall_rules = [] 289 290 for rule in rules: 291 firewall_rule = oneandone.client.FirewallPolicyRule( 292 protocol=rule['protocol'], 293 port_from=rule['port_from'], 294 port_to=rule['port_to'], 295 source=rule['source']) 296 firewall_rules.append(firewall_rule) 297 298 if module.check_mode: 299 firewall_policy_id = get_firewall_policy(oneandone_conn, firewall_id) 300 if (firewall_rules and firewall_policy_id): 301 return True 302 return False 303 304 firewall_policy = oneandone_conn.add_firewall_policy_rule( 305 firewall_id=firewall_id, 306 firewall_policy_rules=firewall_rules 307 ) 308 return firewall_policy 309 except Exception as e: 310 module.fail_json(msg=str(e)) 311 312 313def _remove_firewall_rule(module, oneandone_conn, firewall_id, rule_id): 314 """ 315 Removes a rule from a firewall policy. 316 """ 317 try: 318 if module.check_mode: 319 rule = oneandone_conn.get_firewall_policy_rule( 320 firewall_id=firewall_id, 321 rule_id=rule_id) 322 if rule: 323 return True 324 return False 325 326 firewall_policy = oneandone_conn.remove_firewall_rule( 327 firewall_id=firewall_id, 328 rule_id=rule_id 329 ) 330 return firewall_policy 331 except Exception as e: 332 module.fail_json(msg=str(e)) 333 334 335def update_firewall_policy(module, oneandone_conn): 336 """ 337 Updates a firewall policy based on input arguments. 338 Firewall rules and server ips can be added/removed to/from 339 firewall policy. Firewall policy name and description can be 340 updated as well. 341 342 module : AnsibleModule object 343 oneandone_conn: authenticated oneandone object 344 """ 345 try: 346 firewall_policy_id = module.params.get('firewall_policy') 347 name = module.params.get('name') 348 description = module.params.get('description') 349 add_server_ips = module.params.get('add_server_ips') 350 remove_server_ips = module.params.get('remove_server_ips') 351 add_rules = module.params.get('add_rules') 352 remove_rules = module.params.get('remove_rules') 353 354 changed = False 355 356 firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy_id, True) 357 if firewall_policy is None: 358 _check_mode(module, False) 359 360 if name or description: 361 _check_mode(module, True) 362 firewall_policy = oneandone_conn.modify_firewall( 363 firewall_id=firewall_policy['id'], 364 name=name, 365 description=description) 366 changed = True 367 368 if add_server_ips: 369 if module.check_mode: 370 _check_mode(module, _add_server_ips(module, 371 oneandone_conn, 372 firewall_policy['id'], 373 add_server_ips)) 374 375 firewall_policy = _add_server_ips(module, oneandone_conn, firewall_policy['id'], add_server_ips) 376 changed = True 377 378 if remove_server_ips: 379 chk_changed = False 380 for server_ip_id in remove_server_ips: 381 if module.check_mode: 382 chk_changed |= _remove_firewall_server(module, 383 oneandone_conn, 384 firewall_policy['id'], 385 server_ip_id) 386 387 _remove_firewall_server(module, 388 oneandone_conn, 389 firewall_policy['id'], 390 server_ip_id) 391 _check_mode(module, chk_changed) 392 firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy['id'], True) 393 changed = True 394 395 if add_rules: 396 firewall_policy = _add_firewall_rules(module, 397 oneandone_conn, 398 firewall_policy['id'], 399 add_rules) 400 _check_mode(module, firewall_policy) 401 changed = True 402 403 if remove_rules: 404 chk_changed = False 405 for rule_id in remove_rules: 406 if module.check_mode: 407 chk_changed |= _remove_firewall_rule(module, 408 oneandone_conn, 409 firewall_policy['id'], 410 rule_id) 411 412 _remove_firewall_rule(module, 413 oneandone_conn, 414 firewall_policy['id'], 415 rule_id) 416 _check_mode(module, chk_changed) 417 firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy['id'], True) 418 changed = True 419 420 return (changed, firewall_policy) 421 except Exception as e: 422 module.fail_json(msg=str(e)) 423 424 425def create_firewall_policy(module, oneandone_conn): 426 """ 427 Create a new firewall policy. 428 429 module : AnsibleModule object 430 oneandone_conn: authenticated oneandone object 431 """ 432 try: 433 name = module.params.get('name') 434 description = module.params.get('description') 435 rules = module.params.get('rules') 436 wait = module.params.get('wait') 437 wait_timeout = module.params.get('wait_timeout') 438 wait_interval = module.params.get('wait_interval') 439 440 firewall_rules = [] 441 442 for rule in rules: 443 firewall_rule = oneandone.client.FirewallPolicyRule( 444 protocol=rule['protocol'], 445 port_from=rule['port_from'], 446 port_to=rule['port_to'], 447 source=rule['source']) 448 firewall_rules.append(firewall_rule) 449 450 firewall_policy_obj = oneandone.client.FirewallPolicy( 451 name=name, 452 description=description 453 ) 454 455 _check_mode(module, True) 456 firewall_policy = oneandone_conn.create_firewall_policy( 457 firewall_policy=firewall_policy_obj, 458 firewall_policy_rules=firewall_rules 459 ) 460 461 if wait: 462 wait_for_resource_creation_completion( 463 oneandone_conn, 464 OneAndOneResources.firewall_policy, 465 firewall_policy['id'], 466 wait_timeout, 467 wait_interval) 468 469 firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy['id'], True) # refresh 470 changed = True if firewall_policy else False 471 472 _check_mode(module, False) 473 474 return (changed, firewall_policy) 475 except Exception as e: 476 module.fail_json(msg=str(e)) 477 478 479def remove_firewall_policy(module, oneandone_conn): 480 """ 481 Removes a firewall policy. 482 483 module : AnsibleModule object 484 oneandone_conn: authenticated oneandone object 485 """ 486 try: 487 fp_id = module.params.get('name') 488 firewall_policy_id = get_firewall_policy(oneandone_conn, fp_id) 489 if module.check_mode: 490 if firewall_policy_id is None: 491 _check_mode(module, False) 492 _check_mode(module, True) 493 firewall_policy = oneandone_conn.delete_firewall(firewall_policy_id) 494 495 changed = True if firewall_policy else False 496 497 return (changed, { 498 'id': firewall_policy['id'], 499 'name': firewall_policy['name'] 500 }) 501 except Exception as e: 502 module.fail_json(msg=str(e)) 503 504 505def main(): 506 module = AnsibleModule( 507 argument_spec=dict( 508 auth_token=dict( 509 type='str', no_log=True, 510 default=os.environ.get('ONEANDONE_AUTH_TOKEN')), 511 api_url=dict( 512 type='str', 513 default=os.environ.get('ONEANDONE_API_URL')), 514 name=dict(type='str'), 515 firewall_policy=dict(type='str'), 516 description=dict(type='str'), 517 rules=dict(type='list', elements="dict", default=[]), 518 add_server_ips=dict(type='list', elements="str", default=[]), 519 remove_server_ips=dict(type='list', elements="str", default=[]), 520 add_rules=dict(type='list', elements="dict", default=[]), 521 remove_rules=dict(type='list', elements="str", default=[]), 522 wait=dict(type='bool', default=True), 523 wait_timeout=dict(type='int', default=600), 524 wait_interval=dict(type='int', default=5), 525 state=dict(type='str', default='present', choices=['present', 'absent', 'update']), 526 ), 527 supports_check_mode=True 528 ) 529 530 if not HAS_ONEANDONE_SDK: 531 module.fail_json(msg='1and1 required for this module') 532 533 if not module.params.get('auth_token'): 534 module.fail_json( 535 msg='The "auth_token" parameter or ' + 536 'ONEANDONE_AUTH_TOKEN environment variable is required.') 537 538 if not module.params.get('api_url'): 539 oneandone_conn = oneandone.client.OneAndOneService( 540 api_token=module.params.get('auth_token')) 541 else: 542 oneandone_conn = oneandone.client.OneAndOneService( 543 api_token=module.params.get('auth_token'), api_url=module.params.get('api_url')) 544 545 state = module.params.get('state') 546 547 if state == 'absent': 548 if not module.params.get('name'): 549 module.fail_json( 550 msg="'name' parameter is required to delete a firewall policy.") 551 try: 552 (changed, firewall_policy) = remove_firewall_policy(module, oneandone_conn) 553 except Exception as e: 554 module.fail_json(msg=str(e)) 555 556 elif state == 'update': 557 if not module.params.get('firewall_policy'): 558 module.fail_json( 559 msg="'firewall_policy' parameter is required to update a firewall policy.") 560 try: 561 (changed, firewall_policy) = update_firewall_policy(module, oneandone_conn) 562 except Exception as e: 563 module.fail_json(msg=str(e)) 564 565 elif state == 'present': 566 for param in ('name', 'rules'): 567 if not module.params.get(param): 568 module.fail_json( 569 msg="%s parameter is required for new firewall policies." % param) 570 try: 571 (changed, firewall_policy) = create_firewall_policy(module, oneandone_conn) 572 except Exception as e: 573 module.fail_json(msg=str(e)) 574 575 module.exit_json(changed=changed, firewall_policy=firewall_policy) 576 577 578if __name__ == '__main__': 579 main() 580