You add a client-connect script that rejects any connection where common_name differs from username, and it looks like a binding between a certificate and an account. On a server running username-as-common-name, that comparison has already lost its meaning by the time the script runs. Both values are the same string, so the test passes for a certificate issued to somebody else entirely.

The check is not wrong because the script is badly written. It is wrong because the value it reads was replaced earlier in the same connection.

What the comparison looks like it proves

Certificate authentication and password authentication are separate proofs, and a server can demand both without demanding that they belong to the same person. The gap is real: a valid device certificate plus any valid account is a different claim from that device certificate plus one named account. A script comparing common_name with username reads like the missing link between the two. It would be, if common_name still meant what the certificate said.

Whether it does depends on one server option, and that option is usually switched on for an unrelated reason, such as making per-user configuration files easier to name.

What username-as-common-name does to the name

With username-as-common-name enabled, OpenVPN substitutes the authenticated username for the certificate's common name once the username and password have been verified. The verification hook that runs during that check still receives the certificate common name, because the replacement has not happened yet. Everything afterwards, client-connect included, sees the username. So the later callback receives one identity twice and cannot compare it with the certificate that was presented.

StageWhat common_name holds
Username and password verification hookThe certificate common name, for example bob-device
After the replacementThe authenticated account, alice
client-connect callbackThe account again, alice

Trace one illustrative connection: a certificate issued for bob-device, a successful directory login as alice, and the option enabled. The verification stage can still see two different strings. The connect stage sees alice and alice.

That example shows why the comparison is empty. It does not show that the connection would otherwise be accepted, since certificate validation and password verification still run on their own.

Put the binding where both identities still exist

The rule has to be enforced at a stage that sees the certificate identity and the account at the same time, which in practice means the username and password verification hook or a plugin loaded there. A supported plugin that already performs password verification is usually the right place to add a pairing rule, because replacing it with a name comparison would drop the password check altogether.

Define the mapping before writing code, since equality is rarely the real policy. A certificate often identifies a device while the login identifies a person, and one device may legitimately serve several people. Store the relationship in a form whose issuance you control, and decide what happens on rename, on reissue and on domain-qualified names. Lowercasing or stripping a domain prefix without understanding the directory can merge two accounts that were meant to stay distinct.

Remember that the option also changes which per-client configuration applies, because client-config-dir is keyed by the common name.

Test the pairing with two valid credentials

Start with the pair your policy allows and confirm it connects. Then present the same valid certificate with a different but otherwise valid account: that attempt must fail, and it must fail because of the pairing rule rather than because of a mistyped password. A wrong password proves the password check works and says nothing about the certificate-to-account rule.

Check the missing-certificate and invalid-certificate paths as well, and confirm which identity the server uses for per-client settings. If the only way to make the comparison work is to disable username-as-common-name, price that change first, because it moves every client's configuration lookup back to the certificate name.

Nuŋlɔɖi siwo ku ɖe eŋu