Allow traffic from a given IP and deny it if doesn't match a dest IP:Port, fails

Hello, I’m here to ask if anyone is or has experienced the same as described in the title, using firewall rules for IPv4, or if it’s working as intended actually.
I’m on ROS/FW v7.6
I have hAP ac2

Let me explain it a little better:
I configured a vpn connection (a part from the one that I use for my own) for a friend of mine to share some services with him. So, I decided to create another address, just for him (organization purposes), even peers are assigned statically for his peers: 192.168.101.x/32.

To accomplish what was described above, I have created the following rules:

# Grant access to 192.168.10.2 and the service behind its port 8096.
add action=accept chain=forward dst-address=192.168.10.2 dst-port=8096 \
    protocol=tcp src-address=192.168.101.0/24
# Grant access to 192.168.10.3 and the service behind its port 10019.
add action=accept chain=forward dst-address=192.168.10.3 dst-port=10019 \
    protocol=tcp src-address=192.168.101.0/24
# Deny access to the rest of the network, also internet.
add action=reject chain=forward reject-with=icmp-network-unreachable \
    src-address=192.168.101.0/24

The above rules works as intented.

Here, how I have integrated them in the whole firewall config:

/ip firewall filter
add action=accept chain=input comment=\
    "defconf: accept established,related,untracked" connection-state=\
    established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
    invalid
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input comment=vpn dst-port=51820,51821 protocol=udp
add action=drop chain=input comment="defconf: drop all not coming from LAN" \
    in-interface-list=!LAN
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
    connection-state=established,related hw-offload=yes
add action=accept chain=forward comment=\
    "defconf: accept established,related, untracked" connection-state=\
    established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
    connection-state=invalid
add action=drop chain=forward comment=\
    "defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
    connection-state=new in-interface-list=WAN
add action=accept chain=forward dst-address=192.168.10.2 dst-port=8096 \
    protocol=tcp src-address=192.168.101.0/24
add action=accept chain=forward dst-address=192.168.10.3 dst-port=10019 \
    protocol=tcp src-address=192.168.101.0/24
add action=reject chain=forward reject-with=icmp-network-unreachable \
    src-address=192.168.101.0/24

But, I thought that the above rules could be reduced to just 2, including all the conditions inside a single rule, like:

# Deny access to every resource that is not 192.168.10.3:10019
add action=reject reject-with=icmp-network-unreachable chain=forward \
    dst-address=!192.168.10.3 dst-port=!10019 protocol=tcp src-address=192.168.101.2

Well, the user can’t connected to any device that is not 192.168.10.3:10019, but now he can get access to all the services available on 192.168.10.3, also to 192.168.10.3 itself (server admin webpage).

Here, how I have integrated them in the whole firewall config:

/ip firewall filter
add action=accept chain=input comment=\
    "defconf: accept established,related,untracked" connection-state=\
    established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
    invalid
add action=accept chain=input comment="defconf: accept ICMP" protocol=icmp
add action=accept chain=input comment=vpn dst-port=51820,51821 protocol=udp
add action=drop chain=input comment="defconf: drop all not coming from LAN" \
    in-interface-list=!LAN
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
    connection-state=established,related hw-offload=yes
add action=accept chain=forward comment=\
    "defconf: accept established,related, untracked" connection-state=\
    established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
    connection-state=invalid
add action=drop chain=forward comment=\
    "defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
    connection-state=new in-interface-list=WAN
add action=reject reject-with=icmp-network-unreachable chain=forward \
    dst-address=!192.168.10.3 dst-port=!10019 protocol=tcp src-address=192.168.101.2

Thank you!

If there are multiple conditions, they must all match, for rule to apply. So if you’re looking for dst-address=!192.168.10.3 dst-port=!10019, it won’t match for any port on 192.168.10.3 (or for port 10019 anywhere).

I generally try to avoid negations in conditions, because they easily lead into traps like this. Keep it simple and straightforward. If you want to simplify things, avoid repeating stuff, good tool is subchains (not that this example saves much):

/ip firewall filter
add chain=forward src-address=192.168.101.0/24 action=jump jump-target=vpn-in
add chain=vpn-in dst-address=192.168.10.2 protocol=tcp dst-port=8096 action=accept 
add chain=vpn-in dst-address=192.168.10.3 protocol=tcp dst-port=10019 action=accept 
add chain=vpn-in action=reject reject-with=icmp-network-unreachable

I also like default-deny firewall, where you allow only things you want and everything else is automatically blocked, see e.g. https://forum.mikrotik.com/viewtopic.php?t=180838 (it’s rather long, but you can see the basic idea right at the beginning).

Thank you very much, I never would have thought things were going that way.