You've got an asymmetric routing situation involving NAT, here, and that isn't going to work.
Of course, if your target instance has a public IP anyway, it would seem that there's not much of a bastion effect going on at the bastion host... but assuming you have a logical reason, here's my take on the problem:
Let's call the client machine C
, the bastion B
, and the target instance T
.
Traffic arrives: source IP = C, dest IP = B.
B translates: source IP = C, dest IP = T. Send to T.
T receives: source IP = C, dest IP = T.
T replies: source IP = T, dest IP = C.
Where does the reply traffic go? Not to the bastion -- it goes to the Internet Gateway... a reply packet to a TCP flow the gateway has never heard of. The gateway, by all rights, should drop it or send a TCP RST to the sending instance T to tear down this invalid connection.
The client C should not see this response, but even if it did, the client now sees...
Source IP = T, dest IP = C.
This is not expected by the client, or by any intermediate stateful firewalls, so the traffic is dropped or rejected.
With your private-addressed machines, their default VPC route (I assume) points to the bastion host, so there's no asymmetric situation, here. The bastion can translate the addresses in the opposite direction on the way back to C and everything works.
Now, theoretically, you should be able to make this setup work by adding a second rule to the bastion, so that these ssh connections adopt the bastion host's IP address as their source address.
Leaving the DNAT
rule in place, you'd need something like this...
iptables -t nat -A POSTROUTING -d 10.10.9.23/32 -p tcp --dport 22 -j SNAT
Of course, I just made that up, since I've never had occasion to do this... but it the logic, at least, is sound.
Now, assuming this (or something really similar) works, you've solved your problem of reachability... and created a new problem: the source IP address (in the logs on the inside server) will always be the bastion host. We didn't have much choice, but to do this, to make the translates connection routable back to the Internet... but you might be better off just accessing the machine with its public address.
Another way to accomplish this is with HAProxy on the bastion. The source address seen by the T server will still be the bastion host's inside address... but now you have nice log files on the bastion host for IP address tracking. On the occasion that I have needed to expose an internal TCP service to the Internet, this is my preferred approach, over DNAT
, because of the logs, access control, and connection stats the proxy makes available. (HAProxy is both an http-aware load balancer and a payload-agnostic TCP load balancer. I'm not affiliated with the product, just a fan).