Esempio n. 1
0
    def test_01_setup(self, dev, state_map):
        state = state_map.setdefault(dev)
        state.err = True
        state.fail_func = pytest.skip
        state.parent = dev
        state.tmpl = None
        state.name = None
        state.obj = None
        state.targets = [testlib.random_name() for x in range(2)]

        if self.FUNC is None:
            pytest.skip('{0}.FUNC must be defined'.format(
                self.__class__.__name__))
        elif self.CLASS is None:
            pytest.skip('{0}.CLASS must be defined'.format(
                self.__class__.__name__))
        elif self.PARAM is None:
            pytest.skip('{0}.PARAM must be defined'.format(
                self.__class__.__name__))

        if isinstance(state.parent, Panorama):
            tmpl = Template(testlib.random_name())
            state.parent.add(tmpl)
            v = Vsys('vsys2')
            tmpl.add(v)
            state.parent = v
            tmpl.create()
            state.tmpl = tmpl
        else:
            vsys_list = [
                x.name for x in Vsys.refreshall(dev, add=False, name_only=True)
            ]
            if 'vsys2' not in vsys_list:
                pytest.skip('Firewall needs vsys2 to exist')

        cls_args = {}
        eth_args = {'mode': 'layer3'}
        if self.CLASS == Vlan:
            eth_args['mode'] = 'layer2'
        elif self.CLASS == Zone:
            cls_args['mode'] = 'layer3'

        state.name = testlib.get_available_interfaces(state.parent)[0]
        state.obj = EthernetInterface(state.name, **eth_args)
        state.parent.add(state.obj)

        if state.tmpl is None:
            dev.vsys = 'vsys2'

        instances = [
            self.CLASS(state.targets[x], **cls_args) for x in range(2)
        ]
        for x in instances:
            state.parent.add(x)
            x.create()

        state.err = False
    def populate_facts(self):
        # Get session usage XML
        session_root = self.parent.op('show session meter')

        # Loop through all VSYS
        virtual_systems = []
        vsys_list = Vsys.refreshall(self.parent, name_only=True)
        for vsys in vsys_list:
            for var in ('display_name', 'interface', 'virtual_routers'):
                vsys.refresh_variable(var)

            zones = [x.name for x in Zone.refreshall(vsys, name_only=True)]
            vsys_id = vsys.name[4:]
            vsys_sessions = session_root.find(".//entry/[vsys='" + vsys_id + "']")
            vsys_currentsessions = vsys_sessions.find('.//current').text
            vsys_maxsessions = vsys_sessions.find('.//maximum').text

            virtual_systems.append({
                'vsys_id': vsys_id,
                'vsys_name': vsys.name,
                'vsys_description': vsys.display_name,
                'vsys_iflist': vsys.interface,
                'vsys_vrlist': vsys.virtual_routers,
                'vsys_zonelist': zones,
                'vsys_maxsessions': vsys_maxsessions,
                'vsys_currentsessions': vsys_currentsessions,
            })

        self.facts.update({
            'virtual-systems': virtual_systems
        })
Esempio n. 3
0
    def test_01_setup(self, dev, state_map):
        state = state_map.setdefault(dev)
        state.err = True
        state.fail_func = pytest.skip
        state.parent = dev
        state.name = None
        state.delete_parent = False
        state.obj = None

        if self.CLASS is None:
            pytest.skip('{0}.CLASS must be defined'.format(
                self.__class__.__name__))
        elif self.VSYS_PARAM is None:
            pytest.skip('{0}.VSYS_PARAM must be defined'.format(
                self.__class__.__name__))

        if isinstance(state.parent, Panorama):
            tmpl = Template(testlib.random_name())
            state.parent.add(tmpl)
            state.parent = tmpl
            state.parent.add(Vsys('vsys1'))
            state.parent.add(Vsys('vsys2'))
            state.parent.add(Vsys('vsys3'))
            state.parent.create()
            state.delete_parent = True
        else:
            vsys_list = [
                x.name for x in Vsys.refreshall(dev, add=False, name_only=True)
            ]
            if not all('vsys{0}'.format(x) in vsys_list for x in range(1, 4)):
                pytest.skip('Firewall needs vsys1 - vsys3 to exist')

        args = {}
        if self.CLASS == EthernetInterface:
            state.name = testlib.get_available_interfaces(state.parent)[0]
            args = {'mode': 'layer3'}
        else:
            state.name = testlib.random_name()
        state.obj = self.CLASS(state.name, **args)
        state.parent.add(state.obj)
        state.err = False
    def test_01_setup(self, dev, state_map):
        state = state_map.setdefault(dev)
        state.err = True
        state.fail_func = pytest.skip
        state.parent = dev
        state.tmpl = None
        state.name = None
        state.obj = None
        state.targets = [testlib.random_name() for x in range(2)]

        if self.FUNC is None:
            pytest.skip('{0}.FUNC must be defined'.format(
                        self.__class__.__name__))
        elif self.CLASS is None:
            pytest.skip('{0}.CLASS must be defined'.format(
                        self.__class__.__name__))
        elif self.PARAM is None:
            pytest.skip('{0}.PARAM must be defined'.format(
                        self.__class__.__name__))

        if isinstance(state.parent, Panorama):
            tmpl = Template(testlib.random_name())
            state.parent.add(tmpl)
            v = Vsys('vsys2')
            tmpl.add(v)
            state.parent = v
            tmpl.create()
            state.tmpl = tmpl
        else:
            vsys_list = [x.name for x in Vsys.refreshall(dev, add=False, name_only=True)]
            if 'vsys2' not in vsys_list:
                pytest.skip('Firewall needs vsys2 to exist')

        cls_args = {}
        eth_args = {'mode': 'layer3'}
        if self.CLASS == Vlan:
            eth_args['mode'] = 'layer2'
        elif self.CLASS == Zone:
            cls_args['mode'] = 'layer3'

        state.name = testlib.get_available_interfaces(state.parent)[0]
        state.obj = EthernetInterface(state.name, **eth_args)
        state.parent.add(state.obj)

        if state.tmpl is None:
            dev.vsys = 'vsys2'

        instances = [self.CLASS(state.targets[x], **cls_args) for x in range(2)]
        for x in instances:
            state.parent.add(x)
            x.create()

        state.err = False
    def sanity(self, dev, state_map, fw_test=False):
        state = state_map.setdefault(dev)
        if state.err:
            state.fail_func('prereq failed')

        if fw_test:
            if not isinstance(dev, Firewall):
                pytest.skip('Skipping firewall-only test')
            dev.removeall()
            dev.add(state.obj)
            vsys_list = Vsys.refreshall(dev, name_only=True)
            for v in vsys_list:
                v.refresh_variable(self.VSYS_PARAM)

        return state
Esempio n. 6
0
    def sanity(self, dev, state_map, fw_test=False):
        state = state_map.setdefault(dev)
        if state.err:
            state.fail_func('prereq failed')

        if fw_test:
            if not isinstance(dev, Firewall):
                pytest.skip('Skipping firewall-only test')
            dev.removeall()
            dev.add(state.obj)
            vsys_list = Vsys.refreshall(dev, name_only=True)
            for v in vsys_list:
                v.refresh_variable(self.VSYS_PARAM)

        return state
    def assert_imported_into(self, vsys, state):
        found = False

        vsys_list = Vsys.refreshall(state.parent, add=False, name_only=True)
        for v in vsys_list:
            v.refresh_variable(self.VSYS_PARAM)
            if getattr(v, self.VSYS_PARAM) is None:
                setattr(v, self.VSYS_PARAM, [])
            if vsys == v.name:
                found = True
                assert state.name in getattr(v, self.VSYS_PARAM)
            else:
                assert state.name not in getattr(v, self.VSYS_PARAM)

        if vsys is not None:
            assert found
Esempio n. 8
0
    def assert_imported_into(self, vsys, state):
        found = False

        vsys_list = Vsys.refreshall(state.parent, add=False, name_only=True)
        for v in vsys_list:
            v.refresh_variable(self.VSYS_PARAM)
            if getattr(v, self.VSYS_PARAM) is None:
                setattr(v, self.VSYS_PARAM, [])
            if vsys == v.name:
                found = True
                assert state.name in getattr(v, self.VSYS_PARAM)
            else:
                assert state.name not in getattr(v, self.VSYS_PARAM)

        if vsys is not None:
            assert found
    def test_01_setup(self, dev, state_map):
        state = state_map.setdefault(dev)
        state.err = True
        state.fail_func = pytest.skip
        state.parent = dev
        state.name = None
        state.delete_parent = False
        state.obj = None

        if self.CLASS is None:
            pytest.skip('{0}.CLASS must be defined'.format(
                        self.__class__.__name__))
        elif self.VSYS_PARAM is None:
            pytest.skip('{0}.VSYS_PARAM must be defined'.format(
                        self.__class__.__name__))

        if isinstance(state.parent, Panorama):
            tmpl = Template(testlib.random_name())
            state.parent.add(tmpl)
            state.parent = tmpl
            state.parent.add(Vsys('vsys1'))
            state.parent.add(Vsys('vsys2'))
            state.parent.add(Vsys('vsys3'))
            state.parent.create()
            state.delete_parent = True
        else:
            vsys_list = [x.name for x in Vsys.refreshall(dev, add=False, name_only=True)]
            if not all('vsys{0}'.format(x) in vsys_list for x in range(1, 4)):
                pytest.skip('Firewall needs vsys1 - vsys3 to exist')

        args = {}
        if self.CLASS == EthernetInterface:
            state.name = testlib.get_available_interfaces(state.parent)[0]
            args = {'mode': 'layer3'}
        else:
            state.name = testlib.random_name()
        state.obj = self.CLASS(state.name, **args)
        state.parent.add(state.obj)
        state.err = False
Esempio n. 10
0
    def get_pandevice_parent(self, module, timeout=0):
        """Builds the pandevice object tree, returning the parent object.

        If pandevice is not installed, then module.fail_json() will be
        invoked.

        Arguments:
            * module(AnsibleModule): the ansible module.
            * timeout(int): Number of seconds to retry opening the connection to PAN-OS.

        Returns:
            * The parent pandevice object based on the spec given to
              get_connection().
        """
        # Sanity check.
        if not HAS_PANDEVICE:
            module.fail_json(msg='Missing required library "pandevice".')

        # Verify pandevice minimum version.
        if self.min_pandevice_version is not None:
            pdv = tuple(int(x) for x in pandevice.__version__.split('.'))
            if pdv < self.min_pandevice_version:
                module.fail_json(msg=_MIN_VERSION_ERROR.format(
                    'pandevice', pandevice.__version__,
                    _vstr(self.min_pandevice_version)))

        pan_device_auth, serial_number = None, None
        if module.params['provider'] and module.params['provider'][
                'ip_address']:
            pan_device_auth = (
                module.params['provider']['ip_address'],
                module.params['provider']['username'],
                module.params['provider']['password'],
                module.params['provider']['api_key'],
                module.params['provider']['port'],
            )
            serial_number = module.params['provider']['serial_number']
        elif module.params.get('ip_address', None) is not None:
            pan_device_auth = (
                module.params['ip_address'],
                module.params['username'],
                module.params['password'],
                module.params['api_key'],
                module.params['port'],
            )
            msg = 'Classic provider params are deprecated; use "provider" instead'
            module.deprecate(msg, '2.12')
        else:
            module.fail_json(msg='Provider params are required.')

        # Create the connection object.
        if not isinstance(timeout, int):
            raise ValueError('Timeout must be an int')
        elif timeout < 0:
            raise ValueError('Timeout must greater than or equal to 0')
        end_time = time.time() + timeout
        while True:
            try:
                self.device = PanDevice.create_from_device(*pan_device_auth)
            except PanDeviceError as e:
                if timeout == 0:
                    module.fail_json(msg='Failed connection: {0}'.format(e))
                elif time.time() >= end_time:
                    module.fail_json(msg='Connection timeout: {0}'.format(e))
            else:
                break

        # Verify PAN-OS minimum version.
        if self.min_panos_version is not None:
            if self.device._version_info < self.min_panos_version:
                module.fail_json(msg=_MIN_VERSION_ERROR.format(
                    'PAN-OS', _vstr(self.device._version_info),
                    _vstr(self.min_panos_version)))

        # Optional: Firewall via Panorama connectivity specified.
        if hasattr(self.device, 'refresh_devices') and serial_number:
            fw = Firewall(serial=serial_number)
            self.device.add(fw)
            self.device = fw

        parent = self.device
        no_shared = 'Scope "shared" is not allowed'
        not_found = '{0} "{1}" is not present.'
        pano_mia_param = 'Param "{0}" is required for Panorama but not specified.'
        ts_error = 'Specify either the template or the template stack{0}.'
        if hasattr(self.device, 'refresh_devices'):
            # Panorama connection.
            templated = False

            # Error if Panorama is not supported.
            if self.panorama_error is not None:
                module.fail_json(msg=self.panorama_error)

            # Spec: template stack.
            tmpl_required = False
            added_template = False
            if self.template_stack is not None:
                name = module.params[self.template_stack]
                if name is not None:
                    templated = True
                    stacks = TemplateStack.refreshall(parent, name_only=True)
                    for ts in stacks:
                        if ts.name == name:
                            parent = ts
                            added_template = True
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Template stack',
                            name,
                        ))
                elif self.template is not None:
                    tmpl_required = True
                else:
                    module.fail_json(
                        msg=pano_mia_param.format(self.template_stack))

            # Spec: template.
            if self.template is not None:
                name = module.params[self.template]
                if name is not None:
                    templated = True
                    if added_template:
                        module.fail_json(msg=ts_error.format(', not both'))
                    templates = Template.refreshall(parent, name_only=True)
                    for t in templates:
                        if t.name == name:
                            parent = t
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Template',
                            name,
                        ))
                elif tmpl_required:
                    module.fail_json(msg=ts_error.format(''))
                elif not added_template:
                    module.fail_json(msg=pano_mia_param.format(self.template))

            # Spec: vsys_dg or device_group.
            dg_name = self.vsys_dg or self.device_group
            if dg_name is not None:
                name = module.params[dg_name]
                if name not in (None, 'shared'):
                    groups = DeviceGroup.refreshall(parent, name_only=True)
                    for dg in groups:
                        if dg.name == name:
                            parent = dg
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Device group',
                            name,
                        ))
                elif self.error_on_shared:
                    module.fail_json(msg=no_shared)

            # Spec: vsys importable.
            vsys_name = self.vsys_importable or self.vsys or self.vsys_shared
            if dg_name is None and templated and vsys_name is not None:
                name = module.params[vsys_name]
                if name not in (None, 'shared'):
                    vo = Vsys(name)
                    parent.add(vo)
                    parent = vo

            # Spec: rulebase.
            if self.rulebase is not None:
                if module.params[self.rulebase] in (None, 'pre-rulebase'):
                    rb = PreRulebase()
                    parent.add(rb)
                    parent = rb
                elif module.params[self.rulebase] == 'rulebase':
                    rb = Rulebase()
                    parent.add(rb)
                    parent = rb
                elif module.params[self.rulebase] == 'post-rulebase':
                    rb = PostRulebase()
                    parent.add(rb)
                    parent = rb
                else:
                    module.fail_json(msg=not_found.format(
                        'Rulebase', module.params[self.rulebase]))
        else:
            # Firewall connection.
            # Error if firewalls are not supported.
            if self.firewall_error is not None:
                module.fail_json(msg=self.firewall_error)

            # Spec: vsys or vsys_dg or vsys_importable.
            vsys_name = self.vsys_dg or self.vsys or self.vsys_importable or self.vsys_shared
            if vsys_name is not None:
                parent.vsys = module.params[vsys_name]
                if parent.vsys == 'shared' and self.error_on_shared:
                    module.fail_json(msg=no_shared)

            # Spec: rulebase.
            if self.rulebase is not None:
                rb = Rulebase()
                parent.add(rb)
                parent = rb

        # Done.
        return parent
Esempio n. 11
0
def main():
    argument_spec = dict(
        ip_address=dict(required=True),
        password=dict(no_log=True),
        username=dict(default='admin'),
        api_key=dict(no_log=True),
        operation=dict(default='add', choices=['add', 'update', 'delete']),
        state=dict(choices=['present', 'absent']),
        if_name=dict(required=True),
        ip=dict(type='list'),
        ipv6_enabled=dict(),
        management_profile=dict(),
        mtu=dict(),
        netflow_profile=dict(),
        comment=dict(),
        zone_name=dict(required=True),
        vr_name=dict(default='default'),
        vsys_dg=dict(default='vsys1'),
        commit=dict(type='bool', default=True),
    )
    module = AnsibleModule(argument_spec=argument_spec,
                           supports_check_mode=False,
                           required_one_of=[['api_key', 'password']])
    if not HAS_LIB:
        module.fail_json(msg='Missing required libraries.')

    # Get the firewall / panorama auth.
    auth = [
        module.params[x]
        for x in ('ip_address', 'username', 'password', 'api_key')
    ]

    # Get the object params.
    spec = {
        'name': module.params['if_name'],
        'ip': module.params['ip'],
        'ipv6_enabled': module.params['ipv6_enabled'],
        'management_profile': module.params['management_profile'],
        'mtu': module.params['mtu'],
        'netflow_profile': module.params['netflow_profile'],
        'comment': module.params['comment'],
    }

    # Get other info.
    operation = module.params['operation']
    state = module.params['state']
    zone_name = module.params['zone_name']
    vr_name = module.params['vr_name']
    vsys_dg = module.params['vsys_dg']
    commit = module.params['commit']
    if_name = module.params['if_name']

    # Open the connection to the PANOS device.
    con = PanDevice.create_from_device(*auth)

    # Set vsys if firewall, device group if panorama.
    if hasattr(con, 'refresh_devices'):
        # Panorama
        # Normally we want to set the device group here, but there are no
        # interfaces on Panorama.  So if we're given a Panorama device, then
        # error out.
        '''
        groups = panorama.DeviceGroup.refreshall(con, add=False)
        for parent in groups:
            if parent.name == vsys_dg:
                con.add(parent)
                break
        else:
            module.fail_json(msg="'{0}' device group is not present".format(vsys_dg))
        '''
        module.fail_json(msg="Ethernet interfaces don't exist on Panorama")
    else:
        # Firewall
        # Normally we should set the vsys here, but since interfaces are
        # vsys importables, we'll use organize_into_vsys() to help find and
        # cleanup when the interface is imported into an undesired vsys.
        # con.vsys = vsys_dg
        pass

    # Retrieve the current config.
    try:
        interfaces = TunnelInterface.refreshall(con, add=False, name_only=True)
        zones = Zone.refreshall(con)
        routers = VirtualRouter.refreshall(con)
        vsys_list = Vsys.refreshall(con)
    except PanDeviceError:
        e = get_exception()
        module.fail_json(msg=e.message)

    # Build the object based on the user spec.
    tun = TunnelInterface(**spec)
    con.add(tun)

    # Which action should we take on the interface?
    changed = False
    if state == 'present':
        if tun.name in [x.name for x in interfaces]:
            i = TunnelInterface(tun.name)
            con.add(i)
            try:
                i.refresh()
            except PanDeviceError as e:
                module.fail_json(msg='Failed "present" refresh: {0}'.format(e))
            if not i.equal(tun, compare_children=False):
                tun.extend(i.children)
                try:
                    tun.apply()
                    changed = True
                except PanDeviceError as e:
                    module.fail_json(
                        msg='Failed "present" apply: {0}'.format(e))
        else:
            try:
                tun.create()
                changed = True
            except PanDeviceError as e:
                module.fail_json(msg='Failed "present" create: {0}'.format(e))
        try:
            changed |= set_zone(con, tun, zone_name, zones)
            changed |= set_virtual_router(con, tun, vr_name, routers)
        except PanDeviceError as e:
            module.fail_json(msg='Failed zone/vr assignment: {0}'.format(e))
    elif state == 'absent':
        try:
            changed |= set_zone(con, tun, None, zones)
            changed |= set_virtual_router(con, tun, None, routers)
        except PanDeviceError as e:
            module.fail_json(
                msg='Failed "absent" zone/vr cleanup: {0}'.format(e))
            changed = True
        if tun.name in [x.name for x in interfaces]:
            try:
                tun.delete()
                changed = True
            except PanDeviceError as e:
                module.fail_json(msg='Failed "absent" delete: {0}'.format(e))
    elif operation == 'delete':
        if tun.name not in [x.name for x in interfaces]:
            module.fail_json(
                msg='Interface {0} does not exist, and thus cannot be deleted'.
                format(tun.name))

        try:
            con.organize_into_vsys()
            set_zone(con, tun, None, zones)
            set_virtual_router(con, tun, None, routers)
            tun.delete()
            changed = True
        except (PanDeviceError, ValueError):
            e = get_exception()
            module.fail_json(msg=e.message)
    elif operation == 'add':
        if tun.name in [x.name for x in interfaces]:
            module.fail_json(
                msg='Interface {0} is already present; use operation "update"'.
                format(tun.name))

        con.vsys = vsys_dg
        # Create the interface.
        try:
            tun.create()
            set_zone(con, tun, zone_name, zones)
            set_virtual_router(con, tun, vr_name, routers)
            changed = True
        except (PanDeviceError, ValueError):
            e = get_exception()
            module.fail_json(msg=e.message)
    elif operation == 'update':
        if tun.name not in [x.name for x in interfaces]:
            module.fail_json(
                msg=
                'Interface {0} is not present; use operation "add" to create it'
                .format(tun.name))

        # If the interface is in the wrong vsys, remove it from the old vsys.
        try:
            con.organize_into_vsys()
        except PanDeviceError:
            e = get_exception()
            module.fail_json(msg=e.message)
        if tun.vsys != vsys_dg:
            try:
                tun.delete_import()
            except PanDeviceError:
                e = get_exception()
                module.fail_json(msg=e.message)

        # Move the ethernet object to the correct vsys.
        for vsys in vsys_list:
            if vsys.name == vsys_dg:
                vsys.add(tun)
                break
        else:
            module.fail_json(msg='Vsys {0} does not exist'.format(vsys))

        # Update the interface.
        try:
            tun.apply()
            set_zone(con, tun, zone_name, zones)
            set_virtual_router(con, tun, vr_name, routers)
            changed = True
        except (PanDeviceError, ValueError):
            e = get_exception()
            module.fail_json(msg=e.message)
    else:
        module.fail_json(msg="Unsupported operation '{0}'".format(operation))

    # Commit if we were asked to do so.
    if changed and commit:
        try:
            con.commit(sync=True)
        except PanDeviceError:
            e = get_exception()
            module.fail_json(msg='Performed {0} but commit failed: {1}'.format(
                operation, e.message))

    # Done!
    module.exit_json(changed=changed, msg='okey dokey')
def main():
    argument_spec = dict(
        ip_address=dict(required=True),
        username=dict(default='admin'),
        password=dict(no_log=True),
        api_key=dict(no_log=True),
        rule_name=dict(required=True),
        source_zone=dict(type='list', default=['any']),
        source_ip=dict(type='list', default=["any"]),
        source_user=dict(type='list', default=['any']),
        hip_profiles=dict(type='list', default=['any']),
        destination_zone=dict(type='list', default=['any']),
        destination_ip=dict(type='list', default=["any"]),
        application=dict(type='list', default=['any']),
        service=dict(type='list', default=['application-default']),
        category=dict(type='list', default=['any']),
        action=dict(default='allow',
                    choices=[
                        'allow', 'deny', 'drop', 'reset-client',
                        'reset-server', 'reset-both'
                    ]),
        log_setting=dict(),
        log_start=dict(type='bool', default=False),
        log_end=dict(type='bool', default=True),
        description=dict(default=''),
        rule_type=dict(default='universal',
                       choices=['universal', 'intrazone', 'interzone']),
        tag_name=dict(type='list'),
        negate_source=dict(type='bool', default=False),
        negate_destination=dict(type='bool', default=False),
        disabled=dict(type='bool', default=False),
        schedule=dict(),
        icmp_unreachable=dict(type='bool'),
        disable_server_response_inspection=dict(type='bool', default=False),
        group_profile=dict(),
        antivirus=dict(),
        spyware=dict(),
        vulnerability=dict(),
        url_filtering=dict(),
        file_blocking=dict(),
        wildfire_analysis=dict(),
        data_filtering=dict(),
        target=dict(type='list'),
        negate_target=dict(type='bool', default=False),
        location=dict(choices=['top', 'bottom', 'before', 'after']),
        existing_rule=dict(),
        devicegroup=dict(),
        rulebase=dict(default='pre-rulebase',
                      choices=['pre-rulebase', 'post-rulebase']),
        vsys=dict(default='vsys1'),
        state=dict(choices=['present', 'absent']),
        operation=dict(default='add',
                       choices=['add', 'update', 'delete', 'find']),
        commit=dict(type='bool', default=True))
    module = AnsibleModule(argument_spec=argument_spec,
                           supports_check_mode=True,
                           required_one_of=[['api_key', 'password']])

    if not HAS_LIB:
        module.fail_json(msg='Missing required libraries.')
    elif not hasattr(SecurityRule, 'move'):
        module.fail_json(msg='Python library pandevice needs to be updated.')

    # Get the firewall / panorama auth.
    auth = (
        module.params['ip_address'],
        module.params['username'],
        module.params['password'],
        module.params['api_key'],
    )

    # Set the SecurityRule object params
    rule_spec = {
        'name':
        module.params['rule_name'],
        'fromzone':
        module.params['source_zone'],
        'tozone':
        module.params['destination_zone'],
        'source':
        module.params['source_ip'],
        'source_user':
        module.params['source_user'],
        'hip_profiles':
        module.params['hip_profiles'],
        'destination':
        module.params['destination_ip'],
        'application':
        module.params['application'],
        'service':
        module.params['service'],
        'category':
        module.params['category'],
        'action':
        module.params['action'],
        'log_setting':
        module.params['log_setting'],
        'log_start':
        module.params['log_start'],
        'log_end':
        module.params['log_end'],
        'description':
        module.params['description'],
        'type':
        module.params['rule_type'],
        'tag':
        module.params['tag_name'],
        'negate_source':
        module.params['negate_source'],
        'negate_destination':
        module.params['negate_destination'],
        'disabled':
        module.params['disabled'],
        'schedule':
        module.params['schedule'],
        'icmp_unreachable':
        module.params['icmp_unreachable'],
        'disable_server_response_inspection':
        module.params['disable_server_response_inspection'],
        'group':
        module.params['group_profile'],
        'virus':
        module.params['antivirus'],
        'spyware':
        module.params['spyware'],
        'vulnerability':
        module.params['vulnerability'],
        'url_filtering':
        module.params['url_filtering'],
        'file_blocking':
        module.params['file_blocking'],
        'wildfire_analysis':
        module.params['wildfire_analysis'],
        'data_filtering':
        module.params['data_filtering'],
    }

    # Get other info
    location = module.params['location']
    existing_rule = module.params['existing_rule']
    devicegroup = module.params['devicegroup']
    rulebase = module.params['rulebase']
    vsys = module.params['vsys']
    state = module.params['state']
    operation = module.params['operation']
    commit = module.params['commit']

    # Sanity check the location / existing_rule params.
    if location in ('before', 'after') and not existing_rule:
        module.fail_json(
            msg=
            "'existing_rule' must be specified if location is 'before' or 'after'."
        )

    # Open the connection to the PAN-OS device
    device = None
    try:
        device = PanDevice.create_from_device(*auth)
    except PanDeviceError:
        e = get_exception()
        module.fail_json(msg=e.message)

    # Add some additional rule params if device is Panorama
    if isinstance(device, Panorama):
        rule_spec['target'] = module.params['target']
        rule_spec['negate_target'] = module.params['negate_target']

    # Set the attachment point for the RuleBase object
    parent = device
    if isinstance(parent, Firewall):
        if vsys is not None:
            vsys_list = Vsys.refreshall(parent)
            parent = get_vsys(vsys, vsys_list)
            if parent is None:
                module.fail_json(msg='VSYS not found: {0}'.format(vsys))
            parent = parent.add(Rulebase())
    elif isinstance(parent, Panorama):
        if devicegroup == 'shared':
            devicegroup = None
        if devicegroup is not None:
            parent = parent.add(Rulebase())
        else:
            parent = get_devicegroup(parent, devicegroup)
            if parent is None:
                module.fail_json(
                    msg='Device group not found: {0}'.format(devicegroup))
        if rulebase == 'pre-rulebase':
            parent = parent.add(PreRulebase())
        elif rulebase == 'post-rulebase':
            parent = parent.add(PostRulebase())

    # Now that we have the rulebase let's grab its security rules
    rules = SecurityRule.refreshall(parent)

    # Create new rule object from the params and add to rulebase
    new_rule = SecurityRule(**rule_spec)
    parent.add(new_rule)

    # Which action shall we take on the rule object?
    changed = False
    if state == 'present':
        match = find_rule(rules, new_rule)
        if match:
            # Change an existing rule
            if not match.equal(new_rule):
                try:
                    if not module.check_mode:
                        new_rule.apply()
                except PanDeviceError as e:
                    module.fail_json(
                        msg='Failed "present" apply: {0}'.format(e))
                else:
                    changed = True
        else:
            # Add a new rule
            try:
                if not module.check_mode:
                    new_rule.create()
            except PanDeviceError as e:
                module.fail_json(msg='Failed "present" apply: {0}'.format(e))
            else:
                changed = True
        # Move the rule if location is defined
        if location:
            try:
                if not module.check_mode:
                    new_rule.move(location, existing_rule)
            except PanDeviceError as e:
                if '{0}'.format(e) not in ACCEPTABLE_MOVE_ERRORS:
                    module.fail_json(msg='Failed move: {0}'.format(e))
            else:
                changed = True
    elif state == 'absent':
        match = find_rule(rules, new_rule)
        if match:
            # Delete an existing rule
            try:
                if not module.check_mode:
                    new_rule.delete()
            except PanDeviceError as e:
                module.fail_json(msg='Failed "absent" delete: {0}'.format(e))
            else:
                changed = True
    elif operation == "find":
        # Search for the rule
        match = find_rule(rules, new_rule)
        # If found, format and return the result
        if match:
            module.exit_json(stdout_lines=match.about(), msg='Rule matched')
        else:
            module.fail_json(
                msg='Rule \'%s\' not found. Is the name correct?' %
                new_rule.name)
    elif operation == "delete":
        # Search for the object
        match = find_rule(rules, new_rule)
        if match is None:
            module.fail_json(
                msg='Rule \'%s\' not found. Is the name correct?' %
                new_rule.name)
        try:
            if not module.check_mode:
                new_rule.delete()
        except PanDeviceError as e:
            module.fail_json(msg='Failed "delete" delete: {0}'.format(e))
        else:
            changed = True
    elif operation == "add":
        # Search for the rule. Fail if found.
        match = find_rule(rules, new_rule)
        if match:
            module.fail_json(
                msg=
                'Rule \'%s\' already exists. Use operation: \'update\' to change it.'
                % new_rule.name)
        try:
            if not module.check_mode:
                new_rule.create()
        except PanDeviceError as e:
            module.fail_json(msg='Failed "add" create: {0}'.format(e))
        else:
            changed = True
            if location:
                try:
                    if not module.check_mode:
                        new_rule.move(location, existing_rule)
                except PanDeviceError as e:
                    if '{0}'.format(e) not in ACCEPTABLE_MOVE_ERRORS:
                        module.fail_json(msg='Failed move: {0}'.format(e))
    elif operation == 'update':
        # Search for the rule. Update if found.
        match = find_rule(rulebase, new_rule.name)
        if not match:
            module.fail_json(
                msg=
                'Rule \'%s\' does not exist. Use operation: \'add\' to add it.'
                % new_rule.name)
        try:
            if not module.check_mode:
                new_rule.apply()
        except PanDeviceError as e:
            module.fail_json(msg='Failed "update" apply: {0}'.format(e))
        else:
            changed = True
            if location:
                try:
                    if not module.check_mode:
                        new_rule.move(location, existing_rule)
                except PanDeviceError as e:
                    if '{0}'.format(e) not in ACCEPTABLE_MOVE_ERRORS:
                        module.fail_json(msg='Failed move: {0}'.format(e))

    # Optional commit.
    # FIXME: Commits should be done using the separate commit module
    if changed and commit:
        try:
            device.commit(sync=True)
        except PanDeviceError as e:
            module.fail_json(msg='Failed commit: {0}'.format(e))

    module.exit_json(changed=changed, msg='Done')
Esempio n. 13
0
def main():
    argument_spec = dict(ip_address=dict(required=True),
                         password=dict(no_log=True),
                         username=dict(default='admin'),
                         api_key=dict(no_log=True),
                         zone=dict(required=True),
                         mode=dict(choices=[
                             'tap', 'virtual-wire', 'layer2', 'layer3',
                             'external'
                         ],
                                   default='layer3'),
                         interface=dict(type='list'),
                         zone_profile=dict(),
                         log_setting=dict(),
                         enable_userid=dict(type='bool', default=False),
                         include_acl=dict(type='list'),
                         exclude_acl=dict(type='list'),
                         vsys=dict(default='vsys1'),
                         template=dict(),
                         state=dict(choices=['present', 'absent'],
                                    default='present'))
    module = AnsibleModule(argument_spec=argument_spec,
                           supports_check_mode=True,
                           required_one_of=[['api_key', 'password']])
    if not HAS_LIB:
        module.fail_json(msg='Missing required libraries.')

    # Get the firewall / panorama auth.
    auth = (
        module.params['ip_address'],
        module.params['username'],
        module.params['password'],
        module.params['api_key'],
    )

    # Set the Zone object params
    zone_spec = {
        'name': module.params['zone'],
        'mode': module.params['mode'],
        'interface': module.params['interface'],
        'zone_profile': module.params['zone_profile'],
        'log_setting': module.params['log_setting'],
        'enable_user_identification': module.params['enable_userid'],
        'include_acl': module.params['include_acl'],
        'exclude_acl': module.params['exclude_acl']
    }

    # Get other info
    vsys = module.params['vsys']
    template = module.params['template']
    state = module.params['state']

    # Open the connection to the PAN-OS device
    device = None
    try:
        device = PanDevice.create_from_device(*auth)
    except PanDeviceError:
        e = get_exception()
        module.fail_json(msg=e.message)

    # Set the attachment point for the Zone object
    parent = None
    if isinstance(device, Firewall):
        parent = device
    elif isinstance(device, Panorama):
        if template is not None:
            template_list = Template.refreshall(device)
            parent = get_template(template, template_list)
            if parent is None:
                module.fail_json(
                    msg='Template not found: {0}'.format(template))
        else:
            module.fail_json(
                msg=
                'A template parameter is required when device type is Panorama'
            )
    if vsys is not None:
        v = Vsys(vsys)
        parent.add(v)
        parent = v

    # Retrieve the current list of zones
    try:
        zones = Zone.refreshall(parent)
    except PanDeviceError:
        e = get_exception()
        module.fail_json(msg=e.message)

    # Build the zone and attach to the parent
    new_zone = Zone(**zone_spec)
    parent.add(new_zone)

    # Which action shall we take on the Zone object?
    changed = False
    if state == 'present':
        match = find_zone(zones, new_zone)
        if match:
            # Change an existing zone
            if not match.equal(new_zone):
                try:
                    if not module.check_mode:
                        new_zone.create()
                except PanDeviceError as e:
                    module.fail_json(
                        msg='Failed "present" create: {0}'.format(e))
                else:
                    changed = True
        else:
            # Add a new zone
            try:
                if not module.check_mode:
                    new_zone.apply()
            except PanDeviceError as e:
                module.fail_json(msg='Failed "present" apply: {0}'.format(e))
            else:
                changed = True
    elif state == 'absent':
        match = find_zone(zones, new_zone)
        if match:
            # Delete an existing zone
            try:
                if not module.check_mode:
                    new_zone.delete()
            except PanDeviceError as e:
                module.fail_json(msg='Failed "absent" delete: {0}'.format(e))
            else:
                changed = True

    # Done!
    module.exit_json(changed=changed, msg='Done')
Esempio n. 14
0
    def get_pandevice_parent(self, module):
        """Builds the pandevice object tree, returning the parent object.

        If pandevice is not installed, then module.fail_json() will be
        invoked.

        Arguments:
            * module(AnsibleModule): the ansible module.

        Returns:
            * The parent pandevice object based on the spec given to
              get_connection().
        """
        # Sanity check.
        if not HAS_PANDEVICE:
            module.fail_json(msg='Missing required library "pandevice".')

        d, host_arg = None, None
        if module.params['provider'] and module.params['provider']['host']:
            d = module.params['provider']
            host_arg = 'host'
        elif module.params['ip_address'] is not None:
            d = module.params
            host_arg = 'ip_address'
        else:
            module.fail_json(msg='New or classic provider params are required.')

        # Create the connection object.
        try:
            self.device = PanDevice.create_from_device(
                d[host_arg], d['username'], d['password'], d['api_key'])
        except PanDeviceError as e:
            module.fail_json(msg='Failed connection: {0}'.format(e))

        parent = self.device
        not_found = '{0} "{1}" is not present.'
        if hasattr(self.device, 'refresh_devices'):
            # Panorama connection.
            # Error if Panorama is not supported.
            if self.panorama_error is not None:
                module.fail_json(msg=self.panorama_error)

            # Spec: template stack.
            if self.template_stack is not None:
                name = module.params[self.template_stack]
                stacks = TemplateStack.refreshall(parent)
                for ts in stacks:
                    if ts.name == name:
                        parent = ts
                        break
                else:
                    module.fail_json(msg=not_found.format(
                        'Template stack', name,
                    ))

            # Spec: template.
            if self.template is not None:
                name = module.params[self.template]
                templates = Template.refreshall(parent)
                for t in templates:
                    if t.name == name:
                        parent = t
                        break
                else:
                    module.fail_json(msg=not_found.format(
                        'Template', name,
                    ))

            # Spec: vsys importable.
            if self.vsys_importable is not None:
                name = module.params[self.vsys_importable]
                if name is not None:
                    vo = Vsys(name)
                    parent.add(vo)
                    parent = vo

            # Spec: vsys_dg or device_group.
            dg_name = self.vsys_dg or self.device_group
            if dg_name is not None:
                name = module.params[dg_name]
                if name not in (None, 'shared'):
                    groups = DeviceGroup.refreshall(parent)
                    for dg in groups:
                        if dg.name == name:
                            parent = dg
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Device group', name,
                        ))

            # Spec: rulebase.
            if self.rulebase is not None:
                if module.params[self.rulebase] in (None, 'pre-rulebase'):
                    rb = PreRulebase()
                    parent.add(rb)
                    parent = rb
                elif module.params[self.rulebase] == 'post-rulebase':
                    rb = PostRulebase()
                    parent.add(rb)
                    parent = rb
                else:
                    module.fail_json(msg=not_found.format(
                        'Rulebase', module.params[self.rulebase]))
        else:
            # Firewall connection.
            # Error if firewalls are not supported.
            if self.firewall_error is not None:
                module.fail_json(msg=self.firewall_error)

            # Spec: vsys or vsys_dg or vsys_importable.
            vsys_name = self.vsys_dg or self.vsys or self.vsys_importable
            if vsys_name is not None:
                self.con.vsys = module.params[vsys_name]

            # Spec: rulebase.
            if self.rulebase is not None:
                rb = Rulebase()
                parent.add(rb)
                parent = rb

        # Done.
        return parent
Esempio n. 15
0
    def get_pandevice_parent(self, module):
        """Builds the pandevice object tree, returning the parent object.

        If pandevice is not installed, then module.fail_json() will be
        invoked.

        Arguments:
            * module(AnsibleModule): the ansible module.

        Returns:
            * The parent pandevice object based on the spec given to
              get_connection().
        """
        # Sanity check.
        if not HAS_PANDEVICE:
            module.fail_json(msg='Missing required library "pandevice".')

        # Verify pandevice minimum version.
        if self.min_pandevice_version is not None:
            pdv = tuple(int(x) for x in pandevice.__version__.split('.'))
            if pdv < self.min_pandevice_version:
                module.fail_json(msg=_MIN_VERSION_ERROR.format(
                    'pandevice', pandevice.__version__,
                    _vstr(self.min_pandevice_version)))

        d, host_arg = None, None
        if module.params['provider'] and module.params['provider']['host']:
            d = module.params['provider']
            host_arg = 'host'
        elif module.params['ip_address'] is not None:
            d = module.params
            host_arg = 'ip_address'
        else:
            module.fail_json(
                msg='New or classic provider params are required.')

        # Create the connection object.
        try:
            self.device = PanDevice.create_from_device(d[host_arg],
                                                       d['username'],
                                                       d['password'],
                                                       d['api_key'], d['port'])
        except PanDeviceError as e:
            module.fail_json(msg='Failed connection: {0}'.format(e))

        # Verify PAN-OS minimum version.
        if self.min_panos_version is not None:
            if self.device._version_info < self.min_panos_version:
                module.fail_json(msg=_MIN_VERSION_ERROR.format(
                    'PAN-OS', _vstr(self.device._version_info),
                    _vstr(self.min_panos_version)))

        parent = self.device
        not_found = '{0} "{1}" is not present.'
        pano_mia_param = 'Param "{0}" is required for Panorama but not specified.'
        ts_error = 'Specify either the template or the template stack{0}.'
        if hasattr(self.device, 'refresh_devices'):
            # Panorama connection.
            # Error if Panorama is not supported.
            if self.panorama_error is not None:
                module.fail_json(msg=self.panorama_error)

            # Spec: template stack.
            tmpl_required = False
            added_template = False
            if self.template_stack is not None:
                name = module.params[self.template_stack]
                if name is not None:
                    stacks = TemplateStack.refreshall(parent, name_only=True)
                    for ts in stacks:
                        if ts.name == name:
                            parent = ts
                            added_template = True
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Template stack',
                            name,
                        ))
                elif self.template is not None:
                    tmpl_required = True
                else:
                    module.fail_json(
                        msg=pano_mia_param.format(self.template_stack))

            # Spec: template.
            if self.template is not None:
                name = module.params[self.template]
                if name is not None:
                    if added_template:
                        module.fail_json(msg=ts_error.format(', not both'))
                    templates = Template.refreshall(parent, name_only=True)
                    for t in templates:
                        if t.name == name:
                            parent = t
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Template',
                            name,
                        ))
                elif tmpl_required:
                    module.fail_json(msg=ts_error.format(''))
                else:
                    module.fail_json(msg=pano_mia_param.format(self.template))

            # Spec: vsys importable.
            vsys_name = self.vsys_importable or self.vsys
            if vsys_name is not None:
                name = module.params[vsys_name]
                if name not in (None, 'shared'):
                    vo = Vsys(name)
                    parent.add(vo)
                    parent = vo

            # Spec: vsys_dg or device_group.
            dg_name = self.vsys_dg or self.device_group
            if dg_name is not None:
                name = module.params[dg_name]
                if name not in (None, 'shared'):
                    groups = DeviceGroup.refreshall(parent, name_only=True)
                    for dg in groups:
                        if dg.name == name:
                            parent = dg
                            break
                    else:
                        module.fail_json(msg=not_found.format(
                            'Device group',
                            name,
                        ))

            # Spec: rulebase.
            if self.rulebase is not None:
                if module.params[self.rulebase] in (None, 'pre-rulebase'):
                    rb = PreRulebase()
                    parent.add(rb)
                    parent = rb
                elif module.params[self.rulebase] == 'rulebase':
                    rb = Rulebase()
                    parent.add(rb)
                    parent = rb
                elif module.params[self.rulebase] == 'post-rulebase':
                    rb = PostRulebase()
                    parent.add(rb)
                    parent = rb
                else:
                    module.fail_json(msg=not_found.format(
                        'Rulebase', module.params[self.rulebase]))
        else:
            # Firewall connection.
            # Error if firewalls are not supported.
            if self.firewall_error is not None:
                module.fail_json(msg=self.firewall_error)

            # Spec: vsys or vsys_dg or vsys_importable.
            vsys_name = self.vsys_dg or self.vsys or self.vsys_importable
            if vsys_name is not None:
                self.device.vsys = module.params[vsys_name]

            # Spec: rulebase.
            if self.rulebase is not None:
                rb = Rulebase()
                parent.add(rb)
                parent = rb

        # Done.
        return parent
Esempio n. 16
0
def main():
    argument_spec = dict(
        ip_address=dict(required=True),
        password=dict(no_log=True),
        username=dict(default='admin'),
        api_key=dict(no_log=True),
        operation=dict(default='add', choices=['add', 'update', 'delete']),
        state=dict(choices=['present', 'absent']),
        if_name=dict(required=True),
        mode=dict(default='layer3',
                  choices=[
                      'layer3', 'layer2', 'virtual-wire', 'tap', 'ha',
                      'decrypt-mirror', 'aggregate-group'
                  ]),
        ip=dict(type='list'),
        ipv6_enabled=dict(),
        management_profile=dict(),
        mtu=dict(),
        adjust_tcp_mss=dict(),
        netflow_profile=dict(),
        lldp_enabled=dict(),
        lldp_profile=dict(),
        netflow_profile_l2=dict(),
        link_speed=dict(),
        link_duplex=dict(),
        link_state=dict(),
        aggregate_group=dict(),
        comment=dict(),
        ipv4_mss_adjust=dict(),
        ipv6_mss_adjust=dict(),
        enable_dhcp=dict(type='bool', default=True),
        create_default_route=dict(type='bool', default=False),
        create_dhcp_default_route=dict(type='bool', default=False),
        dhcp_default_route_metric=dict(),
        dhcp_default_route=dict(type='str', default="no"),
        zone_name=dict(required=True),
        vr_name=dict(default='default'),
        vsys_dg=dict(default='vsys1'),
        commit=dict(type='bool', default=True),
    )
    module = AnsibleModule(argument_spec=argument_spec,
                           supports_check_mode=False,
                           required_one_of=[['api_key', 'password']])
    if not HAS_LIB:
        module.fail_json(msg='Missing required libraries.')

    # Get the firewall / panorama auth.
    auth = [
        module.params[x]
        for x in ('ip_address', 'username', 'password', 'api_key')
    ]

    # Get the object params.
    spec = {
        'name': module.params['if_name'],
        'mode': module.params['mode'],
        'ip': module.params['ip'],
        'ipv6_enabled': module.params['ipv6_enabled'],
        'management_profile': module.params['management_profile'],
        'mtu': module.params['mtu'],
        'adjust_tcp_mss': module.params['adjust_tcp_mss'],
        'netflow_profile': module.params['netflow_profile'],
        'lldp_enabled': module.params['lldp_enabled'],
        'lldp_profile': module.params['lldp_profile'],
        'netflow_profile_l2': module.params['netflow_profile_l2'],
        'link_speed': module.params['link_speed'],
        'link_duplex': module.params['link_duplex'],
        'link_state': module.params['link_state'],
        'aggregate_group': module.params['aggregate_group'],
        'comment': module.params['comment'],
        'ipv4_mss_adjust': module.params['ipv4_mss_adjust'],
        'ipv6_mss_adjust': module.params['ipv6_mss_adjust'],
        'enable_dhcp': module.params['enable_dhcp'] or None,
        'create_dhcp_default_route': module.params['create_default_route']
        or None,
        'dhcp_default_route_metric':
        module.params['dhcp_default_route_metric'],
    }

    # Get other info.
    enable_dhcp = module.params['enable_dhcp']
    operation = module.params['operation']
    state = module.params['state']
    zone_name = module.params['zone_name']
    vr_name = module.params['vr_name']
    vsys_dg = module.params['vsys_dg']
    commit = module.params['commit']
    dhcp_default_route = module.params['dhcp_default_route']
    management_profile = module.params['management_profile']
    if_name = module.params['if_name']

    dhcpe = (
        '<entry name="%s"><layer3><dhcp-client><enable>yes</enable><create-default-route>%s</create-default-route>'
        '</dhcp-client><interface-management-profile>%s</interface-management-profile></layer3></entry>'
        % (if_name, dhcp_default_route, management_profile))
    dhcpx = (
        "/config/devices/entry[@name='localhost.localdomain']/network/interface/ethernet/entry[@name='%s']"
        % (if_name))

    # Open the connection to the PANOS device.
    con = PanDevice.create_from_device(*auth)

    # Set vsys if firewall, device group if panorama.
    if hasattr(con, 'refresh_devices'):
        # Panorama
        # Normally we want to set the device group here, but there are no
        # interfaces on Panorama.  So if we're given a Panorama device, then
        # error out.
        '''
        groups = panorama.DeviceGroup.refreshall(con, add=False)
        for parent in groups:
            if parent.name == vsys_dg:
                con.add(parent)
                break
        else:
            module.fail_json(msg="'{0}' device group is not present".format(vsys_dg))
        '''
        module.fail_json(msg="Ethernet interfaces don't exist on Panorama")
    else:
        # Firewall
        # Normally we should set the vsys here, but since interfaces are
        # vsys importables, we'll use organize_into_vsys() to help find and
        # cleanup when the interface is imported into an undesired vsys.
        # con.vsys = vsys_dg
        pass

    # Retrieve the current config.
    try:
        interfaces = EthernetInterface.refreshall(con,
                                                  add=False,
                                                  name_only=True)
        zones = Zone.refreshall(con)
        routers = VirtualRouter.refreshall(con)
        vsys_list = Vsys.refreshall(con)
    except PanDeviceError:
        e = get_exception()
        module.fail_json(msg=e.message)

    # Build the object based on the user spec.
    eth = EthernetInterface(**spec)
    con.add(eth)

    # Which action should we take on the interface?
    changed = False
    if state == 'present':
        if eth.name in [x.name for x in interfaces]:
            i = EthernetInterface(eth.name)
            con.add(i)
            try:
                i.refresh()
            except PanDeviceError as e:
                module.fail_json(msg='Failed "present" refresh: {0}'.format(e))
            if not i.equal(eth, compare_children=False):
                eth.extend(i.children)
                try:
                    eth.apply()
                    changed = True
                except PanDeviceError as e:
                    module.fail_json(
                        msg='Failed "present" apply: {0}'.format(e))
        else:
            try:
                eth.create()
                changed = True
            except PanDeviceError as e:
                module.fail_json(msg='Failed "present" create: {0}'.format(e))
        try:
            changed |= set_zone(con, eth, zone_name, zones)
            changed |= set_virtual_router(con, eth, vr_name, routers)
            if enable_dhcp is True:
                con.xapi.edit(xpath=dhcpx, element=dhcpe)
        except PanDeviceError as e:
            module.fail_json(msg='Failed zone/vr assignment: {0}'.format(e))
    elif state == 'absent':
        try:
            changed |= set_zone(con, eth, None, zones)
            changed |= set_virtual_router(con, eth, None, routers)
        except PanDeviceError as e:
            module.fail_json(
                msg='Failed "absent" zone/vr cleanup: {0}'.format(e))
            changed = True
        if eth.name in [x.name for x in interfaces]:
            try:
                eth.delete()
                changed = True
            except PanDeviceError as e:
                module.fail_json(msg='Failed "absent" delete: {0}'.format(e))
    elif operation == 'delete':
        if eth.name not in [x.name for x in interfaces]:
            module.fail_json(
                msg='Interface {0} does not exist, and thus cannot be deleted'.
                format(eth.name))

        try:
            con.organize_into_vsys()
            set_zone(con, eth, None, zones)
            set_virtual_router(con, eth, None, routers)
            eth.delete()
            changed = True
        except (PanDeviceError, ValueError):
            e = get_exception()
            module.fail_json(msg=e.message)
    elif operation == 'add':
        if eth.name in [x.name for x in interfaces]:
            module.fail_json(
                msg='Interface {0} is already present; use operation "update"'.
                format(eth.name))

        con.vsys = vsys_dg
        # Create the interface.
        try:
            eth.create()
            set_zone(con, eth, zone_name, zones)
            set_virtual_router(con, eth, vr_name, routers)
            changed = True
        except (PanDeviceError, ValueError):
            e = get_exception()
            module.fail_json(msg=e.message)
    elif operation == 'update':
        if eth.name not in [x.name for x in interfaces]:
            module.fail_json(
                msg=
                'Interface {0} is not present; use operation "add" to create it'
                .format(eth.name))

        # If the interface is in the wrong vsys, remove it from the old vsys.
        try:
            con.organize_into_vsys()
        except PanDeviceError:
            e = get_exception()
            module.fail_json(msg=e.message)
        if eth.vsys != vsys_dg:
            try:
                eth.delete_import()
            except PanDeviceError:
                e = get_exception()
                module.fail_json(msg=e.message)

        # Move the ethernet object to the correct vsys.
        for vsys in vsys_list:
            if vsys.name == vsys_dg:
                vsys.add(eth)
                break
        else:
            module.fail_json(msg='Vsys {0} does not exist'.format(vsys))

        # Update the interface.
        try:
            eth.apply()
            set_zone(con, eth, zone_name, zones)
            set_virtual_router(con, eth, vr_name, routers)
            changed = True
        except (PanDeviceError, ValueError):
            e = get_exception()
            module.fail_json(msg=e.message)
    else:
        module.fail_json(msg="Unsupported operation '{0}'".format(operation))

    # Commit if we were asked to do so.
    if changed and commit:
        try:
            con.commit(sync=True, exceptions=True)
        except PanDeviceError:
            e = get_exception()
            module.fail_json(msg='Performed {0} but commit failed: {1}'.format(
                operation, e.message))

    # Done!
    module.exit_json(changed=changed, msg='okey dokey')