So we are setting up and testing new internal systems, using Docker to deploy OpenVPN, Jenkins, and bind all on the same host. We want to restrict access to only come from traffic that originates from our VPN.
For our other internal systems (on different hosts), this works as expected. We can configure ufw to restrict based on the IP of our VPN server, and it works as expected, meaning we can't access when off the VPN, and we can when on the VPN.
However, for the services running within Docker on the same host as our OpenVPN server, traffic never appears to come from the VPN. Connected or not, it always appears to be from the actual IP address of the remote (connecting) client.
OpenVPN is setup using the popular Docker OpenVPN image. We're using the defaults, which means all traffic is supposed to be routed over the VPN connection.
What am I missing/forgetting?
That sounds like you have a masquerade/snat rule in the nat/postrouting chain (edit: of the vpn server host) that transforms source IP addresses from the client address to the VPN server host address for traffic bound to other hosts on the vpn server side.
But the postrouting chain would not be hit for traffic that is destined for the host that runs the vpn server. The last chain would be filter/input. This is the case even if traffic from the client comes into the host running the vpn server on its tun/tap dev for a service that is listening on the eth0 dev.
If this is the case, the easiest way to deal with that would be on the client side by snat/masquerading all traffic routed over the vpn to the client's tun/tap address.