When you execute stdin.write_all(b"username"), all it will do is write the bytes b"username" to some buffer your kernel has that is eventually connected to the child process's stdin. If you want to make sure the child process to receive those bytes, you will also need to do a stdin.flush()? to "flush" any buffers between your code and the child process.
Another thing you might need to do is send a newline character after each write. At the moment, all the child process is seeing is b"usernamepassword" and it's probably still waiting for you to finish entering the username.
let mut child = Command::new("openvpn3")
.stdin(Stdio::piped())
.args([
"session-start",
"--config",
"/home/user/path-to-config.ovpn",
])
.spawn()?;
let mut stdin = child.stdin.take().unwrap();
// the writeln!() macro is like println!(), except it'll write
// to some writer object instead of stdout. By using
// writeln!() instead of write!(), we'll automatically send
// a newline character with each write.
writeln!(stdin, "username")?;
writeln!(stdin, "username")?;
// Make sure any buffered bytes are sent through to the child process
stdin.flush()?;
child.wait()?.success();
The way operating systems keep track of processes is to build a big tree structure and on startup, your kernel will spawn a single program which is responsible for bringing the rest of the system up.
Whenever one program starts another program, that new program is attached as a child node on the process tree (hence the phrase, "child process") and the kernel plumbs things up so you can talk to each other's stdin/stdout/stderr (the mechanism is called a "pipe").
The std::process::Child object gives you access to the parent's side of these pipes, which is why a write to child.stdin or read from child.stdout will let you write/read the child process's stdin/stdout.
Another issue is that you piped stdin of the child to your own process, so it is no longer connected to the terminal. As such your program is now responsible for asking for the key and forwarding to the child process rather than having the terminal input go directly to the child process.
The problem that you mentioned is that I piped the stdin of the child process to my own process (which is the program itself, right?) and I need a way to pass the buffer of my process stdin pipe to the child process?
The easiest thing is probably to read_line from your program standard input and then writeln! it to the child's input, as you did before with login and password.