diff --git a/conf/pfsetacls/switch_acls.yml b/conf/pfsetacls/switch_acls.yml
index f9e50a071c87..bf9a6990c5f1 100644
--- a/conf/pfsetacls/switch_acls.yml
+++ b/conf/pfsetacls/switch_acls.yml
@@ -13,8 +13,9 @@
- name: Load new acl into Cisco Switch
cisco.ios.ios_acls:
- config: "{{ acls.parsed }}"
- state: replaced
+ config: "{{ acls.parsed }}"[% IF delete >= 1 %]
+ state: deleted[% ELSE %]
+ state: replaced[% END %]
when: ansible_network_os == 'cisco.ios.ios'
- name: Load new acl into Cisco WLC
@@ -31,3 +32,34 @@
arubanetworks.aoscx.aoscx_config:
src: "{{ acl_config }}"
when: ansible_network_os == 'arubanetworks.aoscx.aoscx'
+
+[% FOREACH role IN interfaces_delete.keys %]
+[% FOREACH interface IN interfaces_delete.$role %]
+ - name: remove acl on interface
+ cisco.ios.ios_config:
+ lines:
+ - no ip access-group [% role %] in
+ parents: "{{ item }}"
+ with_items:
+ - interface [% interface %]
+[% END %]
+[% END %]
+
+[% IF delete == 0 %]
+[% FOREACH role IN interfaces.keys %]
+[% FOREACH interface IN interfaces.$role %]
+ - name: Merge module attributes of given access-groups
+ cisco.ios.ios_acl_interfaces:
+ config:
+ - name: [% interface %]
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: [% role %]
+ direction: in[% IF delete >= 1 %]
+ state: deleted[% ELSE %]
+ state: merged[% END %]
+ when: ansible_network_os == 'cisco.ios.ios'
+[% END %]
+[% END %]
+[% END %]
diff --git a/html/pfappserver/lib/pfappserver/Form/Config/Switch.pm b/html/pfappserver/lib/pfappserver/Form/Config/Switch.pm
index 4f79241ecd20..70740ee9d9a4 100644
--- a/html/pfappserver/lib/pfappserver/Form/Config/Switch.pm
+++ b/html/pfappserver/lib/pfappserver/Form/Config/Switch.pm
@@ -145,6 +145,12 @@ has_field 'UrlMap' =>
label => 'Role by Web Auth URL',
default => undef,
);
+has_field 'InterfaceMap' =>
+ (
+ type => 'Toggle',
+ label => 'Interface to apply Role ACL',
+ default => undef,
+ );
has_field 'cliAccess' =>
(
type => 'Toggle',
@@ -512,6 +518,7 @@ addRoleMapping("AccessListMapping", "accesslist");
addRoleMapping("VpnMapping", "vpn");
addRoleMapping("NetworkMapping", "network");
addRoleMapping("NetworkFromMapping", "networkfrom");
+addRoleMapping("InterfaceMapping", "interface");
sub _validate_acl_switch {
my ($field) = @_;
diff --git a/html/pfappserver/root/src/views/Configuration/switchGroups/_components/TheForm.vue b/html/pfappserver/root/src/views/Configuration/switchGroups/_components/TheForm.vue
index 0a9e8a1af084..34b9f3172e98 100644
--- a/html/pfappserver/root/src/views/Configuration/switchGroups/_components/TheForm.vue
+++ b/html/pfappserver/root/src/views/Configuration/switchGroups/_components/TheForm.vue
@@ -177,6 +177,17 @@
/>
+
+
+
+
+
+
+
+
@@ -559,6 +570,7 @@ import {
FormGroupRoleMapVpn,
FormGroupRoleMapUrl,
FormGroupRoleMapVlan,
+ FormGroupRoleMapInterface,
FormGroupSnmpAuthProtocolTrap,
FormGroupSnmpAuthPasswordTrap,
FormGroupSnmpCommunityRead,
@@ -587,6 +599,7 @@ import {
FormGroupToggleUrlMap,
FormGroupToggleVlanMap,
FormGroupToggleNetworkMap,
+ FormGroupToggleInterfaceMap,
FormGroupType,
FormGroupUplink,
FormGroupUplinkDynamic,
@@ -640,6 +653,7 @@ const components = {
FormGroupRoleMapVpn,
FormGroupRoleMapUrl,
FormGroupRoleMapVlan,
+ FormGroupRoleMapInterface,
FormGroupSnmpAuthProtocolTrap,
FormGroupSnmpAuthPasswordTrap,
FormGroupSnmpCommunityRead,
@@ -668,6 +682,7 @@ const components = {
FormGroupToggleUrlMap,
FormGroupToggleVlanMap,
FormGroupToggleNetworkMap,
+ FormGroupToggleInterfaceMap,
FormGroupType,
FormGroupUplink,
FormGroupUplinkDynamic,
diff --git a/html/pfappserver/root/src/views/Configuration/switchGroups/_components/index.js b/html/pfappserver/root/src/views/Configuration/switchGroups/_components/index.js
index f4d44d0de49e..7dca29e18afb 100644
--- a/html/pfappserver/root/src/views/Configuration/switchGroups/_components/index.js
+++ b/html/pfappserver/root/src/views/Configuration/switchGroups/_components/index.js
@@ -46,6 +46,7 @@ export {
BaseFormGroupInput as FormGroupRoleMapVpn,
BaseFormGroupInput as FormGroupRoleMapUrl,
BaseFormGroupInput as FormGroupRoleMapVlan,
+ BaseFormGroupInput as FormGroupRoleMapInterface,
BaseFormGroupInput as FormGroupSnmpAuthProtocolTrap,
BaseFormGroupInputPassword as FormGroupSnmpAuthPasswordTrap,
BaseFormGroupInput as FormGroupSnmpCommunityRead,
@@ -83,6 +84,7 @@ export {
BaseFormGroupToggleNYDefault as FormGroupToggleUrlMap,
BaseFormGroupToggleNYDefault as FormGroupToggleVlanMap,
BaseFormGroupToggleNYDefault as FormGroupToggleNetworkMap,
+ BaseFormGroupToggleNYDefault as FormGroupToggleInterfaceMap,
BaseFormGroupToggleNYDefault as FormGroupVoipEnabled,
BaseFormGroupToggleNYDefault as FormGroupVoipLldpDetect,
BaseFormGroupToggleNYDefault as FormGroupVoipCdpDetect,
diff --git a/html/pfappserver/root/src/views/Configuration/switches/_components/TheForm.vue b/html/pfappserver/root/src/views/Configuration/switches/_components/TheForm.vue
index c61fe0a0e701..72361a7f5c0e 100644
--- a/html/pfappserver/root/src/views/Configuration/switches/_components/TheForm.vue
+++ b/html/pfappserver/root/src/views/Configuration/switches/_components/TheForm.vue
@@ -178,6 +178,21 @@
/>
+
+
+
+
+
+
+
+
+
+
@@ -487,6 +502,7 @@ import {
FormGroupRoleMapVpn,
FormGroupRoleMapUrl,
FormGroupRoleMapVlan,
+ FormGroupRoleMapInterface,
FormGroupSnmpAuthProtocolTrap,
FormGroupSnmpAuthPasswordTrap,
FormGroupSnmpCommunityRead,
@@ -515,6 +531,7 @@ import {
FormGroupToggleUrlMap,
FormGroupToggleVlanMap,
FormGroupToggleNetworkMap,
+ FormGroupToggleInterfaceMap,
FormGroupType,
FormGroupUplink,
FormGroupUplinkDynamic,
@@ -567,6 +584,7 @@ const components = {
FormGroupRoleMapVpn,
FormGroupRoleMapUrl,
FormGroupRoleMapVlan,
+ FormGroupRoleMapInterface,
FormGroupSnmpAuthProtocolTrap,
FormGroupSnmpAuthPasswordTrap,
FormGroupSnmpCommunityRead,
@@ -595,6 +613,7 @@ const components = {
FormGroupToggleUrlMap,
FormGroupToggleVlanMap,
FormGroupToggleNetworkMap,
+ FormGroupToggleInterfaceMap,
FormGroupType,
FormGroupUplink,
FormGroupUplinkDynamic,
diff --git a/html/pfappserver/root/src/views/Configuration/switches/_components/index.js b/html/pfappserver/root/src/views/Configuration/switches/_components/index.js
index a90f456d5678..76ca2342142a 100644
--- a/html/pfappserver/root/src/views/Configuration/switches/_components/index.js
+++ b/html/pfappserver/root/src/views/Configuration/switches/_components/index.js
@@ -47,6 +47,7 @@ export {
BaseFormGroupInput as FormGroupRoleMapVpn,
BaseFormGroupInput as FormGroupRoleMapUrl,
BaseFormGroupInput as FormGroupRoleMapVlan,
+ BaseFormGroupInput as FormGroupRoleMapInterface,
BaseFormGroupInput as FormGroupSnmpAuthProtocolTrap,
BaseFormGroupInputPassword as FormGroupSnmpAuthPasswordTrap,
BaseFormGroupInput as FormGroupSnmpCommunityRead,
@@ -78,6 +79,7 @@ export {
BaseFormGroupInput as FormGroupDownloadableAclsLimit,
BaseFormGroupInput as FormGroupAclsLimit,
BaseFormGroupToggleNYDefault as FormGroupToggleAccessListMap,
+ BaseFormGroupToggleNYDefault as FormGroupToggleInterfaceMap,
BaseFormGroupSwitch as FormGroupDeauthOnPrevious,
BaseFormGroupToggleNYDefault as FormGroupToggleRoleMap,
BaseFormGroupToggleNYDefault as FormGroupToggleVpnMap,
diff --git a/html/pfappserver/root/src/views/Configuration/switches/_composables/useForm.js b/html/pfappserver/root/src/views/Configuration/switches/_composables/useForm.js
index 779f05b001a7..b8787a91ef8a 100644
--- a/html/pfappserver/root/src/views/Configuration/switches/_composables/useForm.js
+++ b/html/pfappserver/root/src/views/Configuration/switches/_composables/useForm.js
@@ -158,6 +158,24 @@ const useForm = (props, context) => {
// inspect meta placeholder for `NetworkMap`
const { NetworkMap: { placeholder } = {} } = meta.value
return placeholder === 'Y'
+
+ const isInterfaceMap = computed(() => {
+ // inspect form value for `InterfaceMap`
+ const { InterfaceMap } = form.value
+ if (InterfaceMap !== null)
+ return InterfaceMap === 'Y'
+
+ // inspect meta placeholder for `InterfaceMap`
+ const { InterfaceMap: { placeholder } = {} } = meta.value
+ return placeholder === 'Y'
+ })
+
+ const roles = ref(baseRoles)
+ $store.dispatch('$_roles/all').then(allRoles => {
+ roles.value = [
+ ...roles.value,
+ ...allRoles.map(role => role.id)
+ ]
})
const isUsePushACLs = computed(() => {
@@ -204,6 +222,7 @@ const useForm = (props, context) => {
isUrlMap,
isVlanMap,
isNetworkMap,
+ isInterfaceMap,
roles,
isUsePushACLs,
diff --git a/lib/pf/ConfigStore/Switch.pm b/lib/pf/ConfigStore/Switch.pm
index 96717c55d49b..bfa7c5baf4e7 100644
--- a/lib/pf/ConfigStore/Switch.pm
+++ b/lib/pf/ConfigStore/Switch.pm
@@ -42,6 +42,7 @@ our %MappingKey = (
VpnMapping => 'vpn',
NetworkMapping => 'network',
NetworkFromMapping => 'networkfrom',
+ InterfaceMapping => 'interface',
);
our %MappingKey2 = (
@@ -52,6 +53,7 @@ our %MappingKey2 = (
VpnMapping => 'Vpn',
NetworkMapping => 'Network',
NetworkFromMapping => 'NetworkFrom',
+ InterfaceMapping => 'Interface',
);
=head2 Methods
@@ -107,7 +109,7 @@ sub _expandMapping {
# We put it back as a string so it works in the admin UI
my $toset = {};
while (my ($attr, $val) = each %$switch) {
- if ($attr =~ /(.*)(AccessList|Vlan|Url|Role|Vpn|Network|NetworkFrom)$/) {
+ if ($attr =~ /(.*)(AccessList|Vlan|Url|Role|Vpn|Interface|Network|NetworkFrom)$/) {
my $type = $2;
my $role = $1;
if ($type eq 'AccessList' && ref($val) eq 'ARRAY') {
@@ -131,7 +133,7 @@ sub _expandMapping {
$switch->{$attr} = $val;
}
- for my $k (qw(AccessListMapping VlanMapping UrlMapping ControllerRoleMapping VpnMapping NetworkMapping NetworkFromMapping)) {
+ for my $k (qw(AccessListMapping VlanMapping UrlMapping ControllerRoleMapping VpnMapping InterfaceMapping NetworkMapping NetworkFromMapping)) {
next if !exists $switch->{$k};
$switch->{$k} = [sort { $a->{role} cmp $b->{role} } @{$switch->{$k} // []}]
}
@@ -160,7 +162,7 @@ sub cleanupBeforeCommit {
sub _flattenRoleMappings {
my ( $switch ) = @_;
- for my $namespace (qw(AccessListMapping VlanMapping UrlMapping ControllerRoleMapping VpnMapping NetworkMapping NetworkFromMapping)) {
+ for my $namespace (qw(AccessListMapping VlanMapping UrlMapping ControllerRoleMapping VpnMapping InterfaceMapping NetworkMapping NetworkFromMapping)) {
my $list = $switch->{$namespace} // [];
for my $mapping (@$list) {
my $role = $mapping->{role};
@@ -171,7 +173,7 @@ sub _flattenRoleMappings {
sub _deleteRoleMappings {
my ( $switch ) = @_;
- delete @{$switch}{qw(AccessListMapping VlanMapping UrlMapping ControllerRoleMapping VpnMapping NetworkMapping NetworkFromMapping)};
+ delete @{$switch}{qw(AccessListMapping VlanMapping UrlMapping ControllerRoleMapping VpnMapping InterfaceMapping NetworkMapping NetworkFromMapping)};
}
=head2 _normalizeUplink
diff --git a/lib/pf/Switch.pm b/lib/pf/Switch.pm
index 9b3ae76d34d0..502109a52192 100644
--- a/lib/pf/Switch.pm
+++ b/lib/pf/Switch.pm
@@ -57,7 +57,7 @@ use pf::roles::custom $ROLES_API_LEVEL;
use pf::error::switch;
use pf::util;
use pf::util::radius qw(perform_disconnect);
-use List::MoreUtils qw(any all);
+use List::MoreUtils qw(any all uniq);
use List::Util qw(first);
use Scalar::Util qw(looks_like_number);
use pf::StatsD;
@@ -182,6 +182,7 @@ sub new {
'_VpnMap' => 'enabled',
'_NetworkMap' => 'enabled',
'_NetworkFromMap' => 'disabled',
+ '_InterfaceMap' => 'enabled',
'_UsePushACLs' => 'disabled',
'_UseDownloadableACLs' => 'disabled',
'_DownloadableACLsLimit' => 0,
@@ -189,6 +190,7 @@ sub new {
'_ACLsType' => undef,
'_networks' => undef,
'_networks_from' => undef,
+ '_interfaces' => undef,
map { "_".$_ => $argv->{$_} } keys %$argv,
}, $class;
return $self;
@@ -896,6 +898,42 @@ sub _parentRoleForNetwork {
return undef;
}
+=item getInterfaceByName
+
+Get the switch-specific interface names in role in switches.conf
+
+=cut
+
+sub getInterfaceByName {
+ my ($self, $roleName) = @_;
+ my $logger = $self->logger;
+
+
+ if (!defined($self->{'_interfaces'}) || !defined($self->{'_interfaces'}{$roleName})) {
+ my $parent = _parentRoleForInterface($roleName);
+ if (defined $parent && length($parent)) {
+ return $self->getInterfaceByName($parent);
+ }
+ # VPN name doesn't exist
+ $pf::StatsD::statsd->increment(called() . ".error" );
+ $logger->warn("No parameter ${roleName}Interface found in conf/switches.conf for the switch " . $self->{_id});
+ return undef;
+ }
+
+ # return if found
+ return $self->{'_interfaces'}->{$roleName} if (defined($self->{'_interfaces'}->{$roleName}));
+
+ # otherwise log and return undef
+ $logger->trace("(".$self->{_id}.") No parameter ${roleName}Interface found in conf/switches.conf");
+ return;
+}
+
+sub _parentRoleForInterface {
+ my ($name) = @_;
+ # not yet supported
+ return undef;
+}
+
=item getNetworkFromByName
Get the switch-specific network from names in role in switches.conf
@@ -4286,7 +4324,8 @@ Generate Ansible configuration to push ACLs
=cut
sub generateAnsibleConfiguration {
- my ($self) = @_;
+ my ($self,$oldSwitchConfig, $delete) = @_;
+ $delete //= $FALSE;
my %vars;
umask(0002);
my $tt = Template->new(
@@ -4315,6 +4354,7 @@ sub generateAnsibleConfiguration {
$vars{'switches'}{$switch_id}{'cliPwd'} = $self->{'_cliPwd'};
$vars{'switches'}{$switch_id}{'type'} = $self->{'_type'};
$vars{'switches'}{$switch_id}{'id'} = $switch_ip;
+ $vars{'switches'}{$switch_id}{'delete'} = $delete;
switch($self->{'_type'}) {
case /Cisco::ASA/ { $vars{'switches'}{$switch_id}{'ansible_network_os'} = "cisco.asa" }
case /Cisco::WLC/ { $vars{'switches'}{$switch_id}{'ansible_network_os'} = "aireos" }
@@ -4324,29 +4364,62 @@ sub generateAnsibleConfiguration {
foreach my $role (keys %ConfigRoles) {
my $acls = $self->getRoleAccessListByName($role);
- next if !defined($acls);
- my $out_acls;
- my $in_acls;
- while($acls =~ /([^\n]+)\n?/g) {
- my $acl_line = $1;
- if ($acl_line =~ /^(out\|)(.*)/) {
- $out_acls .= $2."\n";
- } elsif ($acl_line =~ /^(in\|)(.*)/) {
- $in_acls .= $2."\n";
+ my $interfaces = $self->getInterfaceByName($role);
+ if ($interfaces) {
+ my @interfaces = split(',',$interfaces);
+ if ($delete) {
+ $vars{'switches'}{$switch_id}{'interfaces_delete'}{$role} = \@interfaces;
} else {
- $in_acls .= $acl_line."\n";
+ $vars{'switches'}{$switch_id}{'interfaces'}{$role} = \@interfaces;
}
}
- my $implicit_acl = $self->implicit_acl();
- if ((!defined($in_acls) || $in_acls eq "") && $implicit_acl) {
- $vars{'switches'}{$switch_id}{'acls'}{$role} = $implicit_acl;
- } elsif (defined($in_acls)) {
- $vars{'switches'}{$switch_id}{'acls'}{$role} = $in_acls;
+ }
+ if (!$delete) {
+ # Remove useless acl on old interfaces
+ my %diff;
+ foreach my $old_role_interface (@{$oldSwitchConfig->{InterfaceMapping}}) {
+ #Old interface list
+ my @oldinterfaces = uniq split(',',$old_role_interface->{interface});
+ my $newinterfaces = $self->getInterfaceByName($old_role_interface->{role});
+ if ($newinterfaces) {
+ # New interface list
+ my @newinterfaces = uniq split(',',$newinterfaces);
+ @diff{ @oldinterfaces } = @oldinterfaces;
+ delete @diff{ @newinterfaces };
+ @oldinterfaces = uniq %diff;
+ }
+ if (@oldinterfaces) {
+ $vars{'switches'}{$switch_id}{'interfaces_delete'}{$old_role_interface->{role}} = \@oldinterfaces;
+ }
}
- if ((!defined($out_acls) || $out_acls eq "") && $implicit_acl) {
- $vars{'switches'}{$switch_id}{'acls'}{$role."out"} = $implicit_acl;
- } elsif (defined($out_acls)) {
- $vars{'switches'}{$switch_id}{'acls'}{$role."out"} = $out_acls;
+ }
+
+ foreach my $role (keys %ConfigRoles) {
+ my $acls = $self->getRoleAccessListByName($role);
+ if (defined($acls)) {
+ my $out_acls;
+ my $in_acls;
+ while($acls =~ /([^\n]+)\n?/g) {
+ my $acl_line = $1;
+ if ($acl_line =~ /^(out\|)(.*)/) {
+ $out_acls .= $2."\n";
+ } elsif ($acl_line =~ /^(in\|)(.*)/) {
+ $in_acls .= $2."\n";
+ } else {
+ $in_acls .= $acl_line."\n";
+ }
+ }
+ my $implicit_acl = $self->implicit_acl();
+ if ((!defined($in_acls) || $in_acls eq "") && $implicit_acl) {
+ $vars{'switches'}{$switch_id}{'acls'}{$role} = $implicit_acl;
+ } elsif (defined($in_acls)) {
+ $vars{'switches'}{$switch_id}{'acls'}{$role} = $in_acls;
+ }
+ if ((!defined($out_acls) || $out_acls eq "") && $implicit_acl) {
+ $vars{'switches'}{$switch_id}{'acls'}{$role."out"} = $implicit_acl;
+ } elsif (defined($out_acls)) {
+ $vars{'switches'}{$switch_id}{'acls'}{$role."out"} = $out_acls;
+ }
}
}
@@ -4354,7 +4427,7 @@ sub generateAnsibleConfiguration {
$tt->process("$conf_dir/pfsetacls/inventory.cfg", \%vars, "$var_dir/conf/pfsetacls/$switch_id/inventory.yml") or die $tt->error();
$tt->process("$conf_dir/pfsetacls/ansible.cfg", \%vars, "$var_dir/conf/pfsetacls/$switch_id/ansible.cfg") or die $tt->error();
- $tt->process("$conf_dir/pfsetacls/switch_acls.yml", \%vars, "$var_dir/conf/pfsetacls/$switch_id/switch_acls.yml") or die $tt->error();
+ $tt->process("$conf_dir/pfsetacls/switch_acls.yml", $vars{'switches'}{$switch_id}, "$var_dir/conf/pfsetacls/$switch_id/switch_acls.yml") or die $tt->error();
$tt->process("$conf_dir/pfsetacls/collections/requirements.yml", \%vars, "$var_dir/conf/pfsetacls/$switch_id/collections/requirements.yml") or die $tt->error();
find(\&pf::util::chown_pf, "$var_dir/conf/pfsetacls/$switch_id/");
if (-e "$var_dir/conf/pfsetacls/$switch_id/ansible.log") { unlink "$var_dir/conf/pfsetacls/$switch_id/ansible.log" };
diff --git a/lib/pf/UnifiedApi/Controller/Config.pm b/lib/pf/UnifiedApi/Controller/Config.pm
index 05a6c7c237eb..fe929087e727 100644
--- a/lib/pf/UnifiedApi/Controller/Config.pm
+++ b/lib/pf/UnifiedApi/Controller/Config.pm
@@ -397,6 +397,7 @@ sub create {
return 0;
}
+ my $old_config = $self->item_from_store($id);
$item = $self->cleanupItemForCreate($item);
(my $status, $item, my $form) = $self->validate_item($item);
if (is_error($status)) {
@@ -410,7 +411,7 @@ sub create {
$cs->create($id, $item);
return unless($self->commit($cs));
- $self->post_create($id);
+ $self->post_create($id, $old_config);
my $additional_out = $self->additional_create_out($form, $item);
$self->stash( $self->primary_key => $id );
$self->res->headers->location($self->make_location_url($id));
@@ -522,15 +523,22 @@ sub remove {
my $id = $self->id;
my $cs = $self->config_store;
+ my $old_item = $self->item_from_store($id);
+ $self->pre_remove($id, $old_item);
($msg, my $deleted) = $cs->remove($id, 'id');
if (!$deleted) {
return $self->render_error(422, "Unable to delete $id - $msg");
}
return unless($self->commit($cs));
+ $self->post_remove($id, $old_item);
return $self->render(json => {message => "Deleted $id successfully"}, status => 200);
}
+sub post_remove { }
+
+sub pre_remove { }
+
sub addFormWarnings {
my ($self, $form, $response) = @_;
if ($form->has_pf_warnings) {
@@ -568,7 +576,7 @@ sub update {
my $id = $self->id;
$cs->update($id, $new_data);
return unless($self->commit($cs));
- $self->post_update($id);
+ $self->post_update($id, $old_item);
$self->render(status => 200, json => $self->update_response($form));
}
diff --git a/lib/pf/UnifiedApi/Controller/Config/Roles.pm b/lib/pf/UnifiedApi/Controller/Config/Roles.pm
index 44ac81ed4cec..cdf4f39dc5c4 100644
--- a/lib/pf/UnifiedApi/Controller/Config/Roles.pm
+++ b/lib/pf/UnifiedApi/Controller/Config/Roles.pm
@@ -261,7 +261,7 @@ sub reassign_role_config_store_switch {
my $i = 0;
my $cachedConfig = $cs->cachedConfig;
for my $sect ($cachedConfig->Sections()) {
- for my $f (map { "${old}${_}" } qw(Role Url Vlan AccessList Vpn Network NetworkFrom) ) {
+ for my $f (map { "${old}${_}" } qw(Role Url Vlan AccessList Vpn Interface Network NetworkFrom) ) {
next if !$cachedConfig->exists($sect, $f);
$cachedConfig->delval($sect, $f);
$i |= 1;
diff --git a/lib/pf/UnifiedApi/Controller/Config/SwitchRole.pm b/lib/pf/UnifiedApi/Controller/Config/SwitchRole.pm
index d165635d20f4..364b75651889 100644
--- a/lib/pf/UnifiedApi/Controller/Config/SwitchRole.pm
+++ b/lib/pf/UnifiedApi/Controller/Config/SwitchRole.pm
@@ -63,7 +63,7 @@ sub makeMappings {
my %mapping;
while (my ($k, $v) = each %$patch) {
- next unless ($k =~ /(.*)(AccessList|Vlan|Url|Role|Vpn|Network|NetworkFrom)$/);
+ next unless ($k =~ /(.*)(AccessList|Vlan|Url|Role|Vpn|Interface|Network|NetworkFrom)$/);
my $type = $2;
my $role = $1;
if ($type eq 'Role') {
diff --git a/lib/pf/UnifiedApi/Controller/Config/Switches.pm b/lib/pf/UnifiedApi/Controller/Config/Switches.pm
index c058a944b7fe..8e61bb81b433 100644
--- a/lib/pf/UnifiedApi/Controller/Config/Switches.pm
+++ b/lib/pf/UnifiedApi/Controller/Config/Switches.pm
@@ -28,6 +28,7 @@ use pf::ConfigStore::SwitchGroup;
use pfappserver::Form::Config::Switch;
use pf::db;
use List::Util qw(first);
+use pf::constants qw($TRUE $FALSE);
BEGIN {
local $pf::db::NO_DIE_ON_DBH_ERROR = 1;
@@ -78,16 +79,24 @@ sub id {
}
sub post_update {
- my ($self, $switch_id) = @_;
+ my ($self, $switch_id, $old) = @_;
my $switch = pf::SwitchFactory->instantiate($switch_id);
if ($switch) {
- $switch->generateAnsibleConfiguration();
+ $switch->generateAnsibleConfiguration($old,$FALSE);
}
}
sub post_create {
- my ($self, $switch_id) = @_;
- $self->post_update($switch_id);
+ my ($self, $switch_id, $old) = @_;
+ $self->post_update($switch_id, $old);
+}
+
+sub pre_remove {
+ my ($self, $switch_id, $old) = @_;
+ my $switch = pf::SwitchFactory->instantiate($switch_id);
+ if ($switch) {
+ $switch->generateAnsibleConfiguration($old,$TRUE);
+ }
}
=head2 standardPlaceholder
@@ -121,6 +130,7 @@ sub cleanup_options {
my $accessListMapping = $placeholder->{AccessListMapping};
my $urlMapping = $placeholder->{UrlMapping};
my $vpnMapping = $placeholder->{VpnMapping};
+ my $interfaceMapping = $placeholder->{InterfaceMapping};
my $roleMapping = $placeholder->{ControllerRoleMapping};
my $networkMapping = $placeholder->{NetworkMapping};
my $networkMappingFrom = $placeholder->{NetworkMappingFrom};
@@ -130,6 +140,7 @@ sub cleanup_options {
$meta->{"${r}AccessList"} = mapping_meta($r, $accessListMapping, 'accesslist', $self->json_false);
$meta->{"${r}Url"} = mapping_meta($r, $urlMapping, 'url', $self->json_false);
$meta->{"${r}Vpn"} = mapping_meta($r, $vpnMapping, 'vpn', $self->json_false);
+ $meta->{"${r}Interface"} = mapping_meta($r, $interfaceMapping, 'interface', $self->json_false);
$meta->{"${r}Role"} = mapping_meta($r, $roleMapping, 'controller_role', $self->json_false);
$meta->{"${r}Network"} = mapping_meta($r, $networkMapping, 'network', $self->json_false);
$meta->{"${r}NetworkFrom"} = mapping_meta($r, $networkMappingFrom, 'networkfrom', $self->json_false);
@@ -152,6 +163,25 @@ sub mapping_placeholder {
return defined $m ? $m->{$f} : undef;
}
+sub validate_item {
+ my ($self, $item) = @_;
+ return 422, { message => "Duplicate interface detected" }, undef if $self->_duplicate_item($item);
+ return $self->SUPER::validate_item($item);
+}
+
+sub _duplicate_item {
+ my ($self, $item) = @_;
+ my @interfaces;
+ foreach my $entry (@{$item->{'InterfaceMapping'}}) {
+ push(@interfaces, split(',',$entry->{'interface'})) if (defined $entry->{'interface'});
+ }
+ my %duplicated;
+ foreach my $interface (@interfaces) {
+ next unless $duplicated{$interface}++;
+ return 1;
+ }
+}
+
=head1 AUTHOR
Inverse inc.
diff --git a/lib/pfconfig/namespaces/config/Switch.pm b/lib/pfconfig/namespaces/config/Switch.pm
index c990a88fbb0b..c0e55b5b92b3 100644
--- a/lib/pfconfig/namespaces/config/Switch.pm
+++ b/lib/pfconfig/namespaces/config/Switch.pm
@@ -86,11 +86,11 @@ sub build_child {
$self->updateReverseLookup($name, $switch, qw(group));
# transforming vlans and roles to hashes
- my %merged = ( Vlan => {}, Role => {}, AccessList => {} , Url => {} , Vpn => {} , Network => {}, NetworkFrom => {});
+ my %merged = ( Vlan => {}, Role => {}, AccessList => {} , Url => {} , Vpn => {} , Interface => {}, Network => {}, NetworkFrom => {});
my %roles;
- foreach my $key ( grep {/(Vlan|Role|AccessList|Url|Vpn|Network|NetworkFrom)$/} keys %{$switch} ) {
+ foreach my $key ( grep {/(Vlan|Role|AccessList|Url|Vpn|Interface|Network|NetworkFrom)$/} keys %{$switch} ) {
next unless my $value = $switch->{$key};
- if ( my ( $type_key, $type ) = ( $key =~ /^(.+)(Vlan|Role|AccessList|Url|Vpn|Network|NetworkFrom)$/ ) ) {
+ if ( my ( $type_key, $type ) = ( $key =~ /^(.+)(Vlan|Role|AccessList|Url|Vpn|Interface|Network|NetworkFrom)$/ ) ) {
$merged{$type}{$type_key} = $value;
$roles{$type_key} = undef;
}
@@ -108,6 +108,7 @@ sub build_child {
$switch->{networks} = $merged{Network};
$switch->{networks_from}= $merged{NetworkFrom};
+ $switch->{interfaces} = $merged{Interface};
$switch->{VoIPEnabled} = (
$switch->{VoIPEnabled} =~ /^\s*(y|yes|true|enabled|1)\s*$/i
? 1