diff --git a/src/components/standalone/firewall/rules/CreateOrEditFirewallRuleDrawer.vue b/src/components/standalone/firewall/rules/CreateOrEditFirewallRuleDrawer.vue index ebef6e0d2..85dd7da19 100644 --- a/src/components/standalone/firewall/rules/CreateOrEditFirewallRuleDrawer.vue +++ b/src/components/standalone/firewall/rules/CreateOrEditFirewallRuleDrawer.vue @@ -35,7 +35,8 @@ import { validatePortListOrRange, validateRequired, validateRequiredOption, - validateAnyOf + validateAnyOf, + validateMacAddress } from '@/lib/validation' import NeMultiTextInput from '../../NeMultiTextInput.vue' import { useObjects, type ObjectReference } from '@/composables/useObjects' @@ -68,11 +69,14 @@ const firewallConfig = useFirewallStore() const isRuleEnabled = ref(true) const name = ref('') const nameRef = ref() -const sourceType = ref<'source_address' | 'source_object' | 'source_any'>('source_address') +const sourceType = ref<'source_address' | 'source_object' | 'source_any' | 'source_mac_address'>( + 'source_address' +) const destinationType = ref<'destination_address' | 'destination_object' | 'destination_any'>( 'destination_address' ) const sourceAddresses = ref(['']) +const sourceMACAddresses = ref(['']) const destinationAddresses = ref(['']) const sourceAddressObject = ref('') const destinationAddressObject = ref('') @@ -98,6 +102,7 @@ const tags: Ref = ref([]) const isLoggingEnabled = ref(false) const errorBag = ref(new MessageBag()) const sourceAddressesErrors = ref([]) +const sourceMACAddressesErrors = ref([]) const destinationAddressesErrors = ref([]) const actionOptions = computed(() => { @@ -149,6 +154,10 @@ const sourceTypeOptions = ref([ { id: 'source_any', label: t('standalone.firewall_rules.any_source_address') + }, + { + id: 'source_mac_address', + label: t('standalone.firewall_rules.mac_address') } ]) @@ -280,6 +289,7 @@ watch( destinationType.value = 'destination_address' sourceAddresses.value = [''] sourceAddressObject.value = '' + sourceMACAddresses.value = [''] destinationAddresses.value = [''] destinationAddressObject.value = '' sourceZone.value = '*' @@ -337,6 +347,8 @@ watch( ) function mapSourceAddressesToUi(rule: FirewallRule) { + sourceMACAddresses.value = rule.src_mac?.length ? [...rule.src_mac] : [''] + if (!rule.src_ip) { return [] } @@ -346,6 +358,8 @@ function mapSourceAddressesToUi(rule: FirewallRule) { // source address is an object sourceType.value = 'source_object' sourceAddressObject.value = rule.ns_src + } else if (rule.src_mac?.length) { + sourceType.value = 'source_mac_address' } else { if (rule.src_ip.length) { sourceType.value = 'source_address' @@ -526,6 +540,8 @@ function validate() { sourceAddressesErrors.value.push('') }) + sourceMACAddressesErrors.value = sourceMACAddresses.value.map(() => '') + destinationAddressesErrors.value = [] destinationAddresses.value.forEach(() => { destinationAddressesErrors.value.push('') @@ -580,6 +596,18 @@ function validate() { isValidationOk = false focusElement(sourceAddressObjectRef) } + } else if (sourceType.value === 'source_mac_address') { + for (const [index, sourceMACAddress] of sourceMACAddresses.value.entries()) { + let sourceMACAddressValidation = validateRequired(sourceMACAddress) + if (sourceMACAddressValidation.valid) { + sourceMACAddressValidation = validateMacAddress(sourceMACAddress) + } + + if (!sourceMACAddressValidation.valid) { + sourceMACAddressesErrors.value[index] = t(sourceMACAddressValidation.errMessage as string) + isValidationOk = false + } + } } // source zone @@ -708,6 +736,7 @@ async function saveRule() { enabled: isRuleEnabled.value, src_ip: [], ns_src: '', + src_mac: [], src: '', dest_ip: [], ns_dst: '', @@ -741,6 +770,12 @@ async function saveRule() { } else if (sourceType.value === 'source_object') { // source address object ruleData.ns_src = sourceAddressObject.value + } else if (sourceType.value === 'source_mac_address') { + ruleData.src_mac = [ + ...new Set( + sourceMACAddresses.value.map((macAddress) => macAddress.toLowerCase().replace(/-/g, ':')) + ) + ] } // source zone @@ -894,6 +929,18 @@ async function saveRule() { :selected-label="t('ne_combobox.selected')" :user-input-label="t('ne_combobox.user_input_label')" /> + + { + found = ['src_mac', 'proto', 'dest_port', 'ns_tag'].some((attrName) => { const attrValue = rule[attrName as keyof FirewallRule] as string[] return attrValue?.some((value) => { diff --git a/src/components/standalone/firewall/rules/FirewallRulesTable.vue b/src/components/standalone/firewall/rules/FirewallRulesTable.vue index c0a05eeba..7d2210ad7 100644 --- a/src/components/standalone/firewall/rules/FirewallRulesTable.vue +++ b/src/components/standalone/firewall/rules/FirewallRulesTable.vue @@ -247,7 +247,7 @@ function searchStringInRule(rule: FirewallRule, queryText: string) { } // search in array attributes - found = ['proto', 'dest_port', 'ns_tag'].some((attrName) => { + found = ['src_mac', 'proto', 'dest_port', 'ns_tag'].some((attrName) => { const attrValue = rule[attrName as keyof FirewallRule] as string[] return attrValue?.some((value) => { diff --git a/src/components/standalone/firewall/rules/SourceOrDestinationRuleColumn.vue b/src/components/standalone/firewall/rules/SourceOrDestinationRuleColumn.vue index 9055b1d8a..0c265bde8 100644 --- a/src/components/standalone/firewall/rules/SourceOrDestinationRuleColumn.vue +++ b/src/components/standalone/firewall/rules/SourceOrDestinationRuleColumn.vue @@ -65,6 +65,9 @@ const domainSetObject = computed(() => { const addresses = computed(() => { if (props.columnType === 'source') { + if (props.rule.src_mac?.length) { + return props.rule.src_mac.map((macAddress) => ({ value: macAddress, label: '', type: 'mac' })) + } return props.rule.src_ip as RuleHost[] } else { return props.rule.dest_ip as RuleHost[] diff --git a/src/i18n/en.json b/src/i18n/en.json index 4dd00c8ab..cec1d8a32 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1715,7 +1715,11 @@ "inactive": "Inactive", "zone_no_longer_exists": "This rule is inactive because it uses a zone that no longer exists", "disabled_rule": "This rule has been manually disabled, but can be re-enabled", - "logging_enabled": "Logging enabled" + "logging_enabled": "Logging enabled", + "mac_address": "MAC address", + "add_source_mac_address": "Add source MAC address", + "source_mac_addresses": "Source MAC addresses", + "mac_address_placeholder": "e.g. 00:1A:2B:3C:4D:5E" }, "firewall": { "title": "Firewall" diff --git a/src/i18n/it.json b/src/i18n/it.json index 60b5193e9..e3f17fd7f 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -2177,7 +2177,11 @@ "inactive": "Inattiva", "zone_no_longer_exists": "Questa regola è stata disattivata perché utilizza una zona che non esiste più", "disabled_rule": "Questa regola è stata disabilitata manualmente, ma può essere riattivata", - "logging_enabled": "Logging abilitato" + "logging_enabled": "Logging abilitato", + "mac_address": "Indirizzo MAC", + "add_source_mac_address": "Aggiungi indirizzo MAC sorgente", + "source_mac_addresses": "Indirizzi MAC sorgente", + "mac_address_placeholder": "es. 00:1A:2B:3C:4D:5E" }, "certificates": { "title": "Certificati", diff --git a/src/stores/standalone/firewall.ts b/src/stores/standalone/firewall.ts index 218830809..2344132a1 100644 --- a/src/stores/standalone/firewall.ts +++ b/src/stores/standalone/firewall.ts @@ -59,6 +59,7 @@ export interface FirewallRule { src?: string // source zone src_ip?: string[] | RuleHost[] // source addresses ns_src?: string // source address object + src_mac?: string[] // source MAC addresses dest?: string // destination zone dest_ip?: string[] | RuleHost[] // destination addresses ns_dst?: string // destination address object