OpenConnectによるVPN接続

概要

  • Cisco AnyConnectではなく、互換クライアントのOpenConnectを利用してVPN接続を行う方法についてのメモ
  • Cisco AnyConnectはVirtualboxの仮想NICと相性が悪く、VPN接続中はルーティングを全てトンネル側に向けてしまうため、仮想環境のネットワークに接続できない問題がある
  • Virtualboxのネットワークに接続できないと、Vagrant上の開発環境や、Genymotion(Androidエミュレータ)が利用できなくなるので困る
  • 問題の報告は以前から上がっているようだけれど、改善の気配はないみたい
  • 動作確認環境は Mac OS X 10.10.5 (Yosemite)

事前準備

必要なアプリケーションのインストール

仮想ネットワークデバイス TUN/TAP
  • TunTap - Home
  • Yosemite以降、未署名のKernel Extensionがインストールできなくなったため sudo nvram boot-args="kext-dev-mode=1" などの回避手段が取られていたが、2014/11/18から署名済パッケージが配布されているため、現在はインストール上の問題は発生しない
$ brew cask install tuntap
Cisco互換のVPNクライアント(?) vnpc
  • OpenConnectはVPNサーバとの通信機能だけを持っており、ルーティングやDHCPなどの諸々はサポートしないため、vpncのスクリプトを補助的に利用する(多分)
$ brew install vpnc
OpenConnect
$ brew install openconnect

各種証明書のエクスポート

  • クライアント証明をKeyChainからエクスポートする
    • KeyChain Access.app の login にあるユーザ名の証明書を選択し、右クリックメニューなどからExportする
    • 出力先は適当な場所でよいけれど、自分は ~/.cisco/certificate/client/cert.p12 などに設置した
    • 証明書の出力時にパスワード入力を求められるので、任意のパスワード文字列を設定する
  • サーバ証明書をKeyChainからエクスポートする

接続方法

以下の様なコマンドで接続できる。

$ sudo openconnect \
    --user=username \
    --authgroup=vpn \
    --script=/usr/local/etc/vpnc-script \
    --cafile=~/.cisco/certificate/server/ca.example.com.cer \
    --certificate=~/.cisco/certificate/client/cert.p12 \
    vpn.example.com
  • --user は、各自接続ユーザ名に置き換える
  • --script は、vpncでインストールされたパスを指定する
  • --cafile--certificate は、前項でエクスポートした各証明書パスを指定する
  • vpn.example.com は、接続先のVPNサーバ名に置き換える
  • --authgroupは環境によって不要かもしれない(指定値は接続先VPN管理者に確認する)
  • 設定項目はファイルに切り出して接続時に読み込ませることもできる

接続終了

  • 接続終了は Ctrl + C などで切断する
  • 起動時に -b オプションを付加するとバックグランドで実行されるので、その場合は kill などで殺す

問題点など

signer not found のエラーが出る

  • CA証明書指定が間違っているかもしれない
  • とりあえず yes とか答えてスルーすることもできるが、当然微妙
  • 雑に済ますなら --no-cert-check オプションでスキップすることもできる
SSL negotiation with vpn.example.com
Server certificate verify failed: signer not found
 
Certificate from VPN server "vpn.example.com" failed verification.
Reason: signer not found
Enter 'yes' to accept, 'no' to abort; anything else to view: yes

パスワード入力を最大3回求められる

  • 「sudo」「クライアント証明書」「VPNサーバへのログイン」のそれぞれで、パスワード入力が求められる
  • セキュリティ上のリスクを承知の上で、以下の様なパスワード回避手段を採用することができる
openconnect実行にsudoパスワードを省略する
$ sudo visudo -f /etc/sudoers
---
 
# 下記の1行を追加する
%admin  ALL=(ALL) NOPASSWD: /usr/local/bin/openconnect
クライアント証明書からパスワードを除去する
$ cd ~/.cisco/certificate/client/
$ openssl pkcs12 -in cert.p12 -nodes -out temp.pem
Enter Import Password: (エクスポート時のパスワードを入力)
MAC verified OK
 
$ openssl pkcs12 -export -in temp.pem -out cert_unprotected.p12
Enter Export Password: (何も入力せずにEnter)
Verifying - Enter Export Password: (何も入力せずにEnter)
 
$ rm temp.pem
VPNサーバへのログインパスワードを標準入力で渡す
  • 標準入力からパスワードを受け取ることができるので、起動時のコマンドに含めてしまう
echo -n "password" | sudo openconnect --passwd-on-stdin vpn.example.com

切断後にネット接続できなくなることがある

  • 何故か自然復旧することもあるけれど、たいてい接続できない状態のままになる
  • pingを打っても下記のようなエラーが表示される感じ
$ ping 192.168.0.1
PING 192.168.0.1 (192.168.0.1): 56 data bytes
ping: sendto: No route to host
  • 原因はDefault GatewayRTF_IFSCOPEが立っているので、通常の接続時にgatewayとして認識してくれないみたい
    • ルーティングテーブルを出力すると、FlagsIが含まれていることが確認できる
$ netstat -nr
Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
default            192.168.211.254    UGScI           0        0     en1
127                127.0.0.1          UCS             0        0     lo0
127.0.0.1          127.0.0.1          UH              2   327379     lo0
...
  • netstatのドキュメントによると、IRTF_IFSCOPEを示すとのこと

    I RTF_IFSCOPE Route is associated with an interface scope

  • 説明はちゃんと理解できていないけど、そのインターフェース専用のgatewayとして設定されるということなのかな…

    A route which is marked with the RTF_IFSCOPE flag is instantiated for the corresponding interface.

  • いずれにしてもこれがあると都合が悪いので正常に戻したい
  • 復旧手段
    • RTF_IFSCOPEのついたgatewayを削除して、再度登録しなおせば復旧する
    • 現在のgatewayの値は、以下のコマンドで確認できる
$ route -n get -ifscope en1 default
   route to: default
destination: default
       mask: default
    gateway: 192.168.211.254
  interface: en1
      flags: <UP,GATEWAY,DONE,STATIC,PRCLONING>
 recvpipe  sendpipe  ssthresh  rtt,msec    rttvar  hopcount      mtu     expire
       0         0         0         0         0         0      1500         0 
  • 削除〜追加のコマンドは、このような感じ(en1は各環境のインターフェース名にあわせる)
$ DEFAULT_GATEWAY=`route -n get -ifscope en1 default | grep gateway | awk '{ print $2 }'`
$ sudo route delete default -ifscope en1
$ sudo route add default $DEFAULT_GATEWAY
  • ルーティングテーブルを出力すると、FlagsからIがなくなっていることが分かる
$ netstat -nr
Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
default            192.168.211.254    UGSc          210        2     en1
127                127.0.0.1          UCS             0        0     lo0
127.0.0.1          127.0.0.1          UH              2      104     lo0
...

それでは、快適なVPNライフをお過ごしください。