Worth pinning down two things before you change the script, because "not catching $common_name" has two very different causes.
First, which hook is connect.sh actually attached to? If it is on --auth-user-pass-verify, that hook only runs when the client sends a username and password. Take the credentials away and the script never executes at all, so nothing is missing from the environment; the script simply is not running. If it is on --client-connect, it runs on every successful client connection regardless of auth method, and common_name will be there. Quick way to settle it: put "env >> /tmp/ovpn-env.log" as the first line of the script and reconnect once, then look at what was actually exported.
Second, and this is the one that usually bites when moving away from user/pass: if the old config had username-as-common-name, then common_name was the username, not the certificate CN. Dropping user/pass auth makes common_name revert to the real CN in the client certificate. Your rule "if $common_name = userXYZ" was matching a username and is now being handed a cert CN that looks nothing like it. If that is the case the fix is not in the script, it is in what you name the CNs when you issue the client certificates.
That also raises the question that decides whether this approach can work at all: does each client have its own certificate with a distinct CN? Certificate-only auth can only give you per-client rules if it does. If several clients share one certificate they all present the same CN and there is nothing left to key iptables on. Keep duplicate-cn off so CNs stay unique.
On the CCD question, you are looking in the right place, and it may remove the need for the dynamic rules entirely. With client-config-dir pointed at a directory and a file named exactly after each client's CN containing something like "ifconfig-push 10.8.0.10 255.255.255.0", each client lands on a fixed tunnel IP every time. Your SNAT rules then become static iptables entries keyed on source IP, written once, with no script and no teardown race on disconnect.
If you would rather keep the script, use ifconfig_pool_remote_ip rather than reconstructing the address yourself. That is the tunnel IP assigned to the client in the client-connect environment, and it is what the SNAT rule actually needs. Also double-check script-security 2 is still set; that is easy to lose when a config gets rewritten.
So: which hook is connect.sh on, and does each client have its own certificate?