The vpn client that ships with Cisco AnyConnect behaves like a conversation: it prints a prompt, waits for a line, then prints the next one. Feed that conversation in from a file or a pipe and the same binary runs unattended. The technique is one redirect plus an ordered list of answers, and it is the list, not the redirect, that decides whether the script is reliable.

Two things break it in practice. The prompt order shifts when a gateway asks something extra, and a stored password turns one typo into an account lockout. Treat the sequence as versioned data that you can diff after any gateway change. Fail loudly rather than retrying, because a loop is exactly how a mistyped password becomes a locked account.

One sequence, in the order the client asks

Run the client interactively once and write down every prompt in the order it appears. That transcript becomes the answer file. When you redirect the file into the binary the first line has to be the connect command itself, which is why the sequence begins with a hostname rather than a username.

connect vpn.example.com
0
<username>
<password>
y
exit

The bare 0 selects the first connection profile when the gateway offers a list; use whichever index your gateway prints. The y accepts a login banner, so delete that line if there is no banner and insert one wherever your gateway asks an additional question. The trailing exit is what lets the client terminate cleanly when the last answer has been consumed.

/opt/cisco/anyconnect/bin/vpn -s < anyconnect.txt

Non-interactive mode is not optional here. Without -s the client can take over the terminal and ignore redirected input; with it the answers are read from the stream and the process returns. Where the binary needs elevated privileges, wrap the invocation in sudo -n so the script fails immediately instead of blocking on a password prompt.

Where the answer file leaks

A file containing a password is a credential, so give it the permissions of one: mode 600, owned by the account that runs the job, and never inside a shared or synchronised directory. Create it with the mode already applied rather than fixing it afterwards, since a file is briefly world-readable between the write and the chmod.

install -m 600 /dev/null ~/.config/anyconnect.txt
chmod 700 ~/.config

The other half is argv. A password passed as an argument is visible to every local user who lists processes, and it also lands in shell history. Read it from a file descriptor or an environment variable instead, and quote the payload so the shell cannot expand anything inside it.

printf '%s\n' 'connect vpn.example.com' '0' "$VPN_USER" "$VPN_PASS" 'y' 'exit' \
  | sudo -n /opt/cisco/anyconnect/bin/vpn -s

Watch the quoting. Inside double quotes a password containing ! can be expanded by an interactive shell before the client ever sees it, and the failure arrives as a generic event-not-found error that looks like a protocol problem.

Prove the tunnel, then close it

A connected state only proves the client believes it has a session. Pick a host that answers only from inside the corporate network and probe it once before doing anything else. A failure there is a routing or policy fault, and running the login again will not change it.

/opt/cisco/anyconnect/bin/vpn state
curl -sS -o /dev/null -w '%{http_code}\n' --max-time 10 https://<internal-host>/
/opt/cisco/anyconnect/bin/vpn disconnect

Check the state output before and after the disconnect. A client left holding a stale session can refuse the next connect while the script still reports success, and that failure mode is invisible if you only look at exit codes. Compare the two readings rather than trusting the second one on its own. Aliases in a shell profile are enough for daily use, while anything scheduled deserves the same credential and identity review as any other unattended job.

When a fixed list stops being safe

An answer file encodes today's prompt order. A changed banner, a forced password change or a second-factor challenge turns it into a loop that can lock the account, which is why the script must exit on the first unexpected answer instead of restarting.

Capture what the client printed, return a non-zero status, and let the caller decide. Where the gateway demands a second factor, the honest answer is that an unattended login is not available for that profile, not that the sequence needs one more line.

Rotation belongs in the same review. If the credential is stored, decide who can read it, when it changes, and what removes it when the host is rebuilt. A script that works for a year because nobody touched the password is not a design, it is an accident waiting for a rotation policy.

Profiles that are not AnyConnect

Two older Cisco credential styles get confused with this one. A vpnc profile asks for an IPSec ID, which is really the group name, then a group secret, then your own credentials, and all three come from whoever runs the gateway. Legacy Cisco IPsec is a different protocol from AnyConnect, so vpnc cannot reach an AnyConnect-only service; openconnect --protocol=anyconnect is the open client for that job.

A .pcf file describes the older client profile. The host, the group name and the group password inside it can be re-entered by hand in a client that speaks Cisco IPsec, though the file is no universal import and any non-default port or tunnelling mode usually disappears in the transfer.

Start from what the gateway actually offers, then pick the client that speaks that protocol. Copying a profile from another site is how people end up debugging credentials that were never the problem.

Artaigilean co-cheangailte