0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【小ネタ】MacのCLIからCisco AnyConnectでVPN接続する〜AIでキレイにしたやつ〜

Posted at

タイトル通りの小ネタです。
MacのCLIからCisco AnyConnectでVPNをポチれるツールです。

スクリーンショット 2025-06-23 14.35.55.png

以下の記事参考にしました。
先人に感謝です、ありがとうございます。

そして、コード作成はClaude+Bedrockです。
サクッとオプションやエラーハンドリングを実装出来ています。
いや、ちょっとヤバい時代ですね。

README

アーキテクチャ

  • vpn.sh: メインの自動化スクリプト
    • コマンドライン引数による機能選択
    • securityコマンドを使用したmacOSキーチェーンとの統合
    • Cisco AnyConnectプロセスのライフサイクル管理
  • vpn_completion.sh: タブ補完用

設定項目

スクリプト内の設定変数:

  • VPN_USER: VPNユーザー名
  • VPN_SERVER: VPNサーバーアドレス

必要に応じて変更可能な変数:

  • cisco_vpn: Cisco AnyConnectのインストールパス(標準: /opt/cisco/anyconnect/bin/vpn
  • KEYCHAIN_ACCOUNT: キーチェーンアカウント名(標準: account_name
  • KEYCHAIN_SERVICE: キーチェーンサービス名(標準: cisco_vpn_password

セットアップ

実行権限の設定

# パスは実際にvpn.shを配置した場所に変更すること

chmod +x /path/to/vpn.sh

エイリアス設定

# ~/.bash_profile または ~/.zshrc に追加
# パスは実際にvpn.shを配置した場所に変更すること
alias vpn='/path/to/vpn.sh'

設定後は以下のようにコマンドで実行可能

vpn              # VPN接続(初回時は自動でパスワード設定)
vpn connect      # VPN接続
vpn disconnect   # VPN切断
vpn status       # 接続状態確認
vpn rotate       # パスワード更新
vpn remove       # パスワード削除

タブ補完設定

# bash_profileまたはzshrcに追加
# パスは実際にvpn_completion.shを配置した場所に変更すること
source /path/to/vpn_completion.sh

シェルスクリプト

vpn.sh
#!/bin/bash
set -eu

# 設定
VPN_USER='hoge'
VPN_SERVER='hoge.com/hoge'

cisco_vpn='/opt/cisco/anyconnect/bin/vpn'
KEYCHAIN_ACCOUNT='account_name'
KEYCHAIN_SERVICE='cisco_vpn_password'

# ====================
# ユーティリティ関数
# ====================

# Cisco AnyConnectのインストール状態をチェックする関数
check_cisco_installed() {
    if [ ! -e $cisco_vpn ]; then
        echo "Cisco AnyConnectがインストールされていません" >&2
        exit 1
    fi
}

# VPN接続成功時の処理関数
connection_success() {
    echo "VPNに接続しました。"
    exit 0
}

# ====================
# キーチェーン関数
# ====================

# キーチェーンからパスワードを取得する関数
get_password() {
    security find-generic-password -a "$KEYCHAIN_ACCOUNT" -s "$KEYCHAIN_SERVICE" -w 2>/dev/null
}

# キーチェーンパスワード設定関数(共通)
set_keychain_password() {
    local prompt_msg="$1"
    local success_msg="$2"

    echo "$prompt_msg"
    echo -n "VPNパスワードを入力してください: "
    read -s password
    echo

    # -Uオプションで存在する場合は更新、ない場合は追加
    if security add-generic-password -U -a "$KEYCHAIN_ACCOUNT" -s "$KEYCHAIN_SERVICE" -w "$password" 2>/dev/null; then
        echo "$success_msg"
    else
        echo "エラー: パスワードの保存/更新に失敗しました。"
        exit 1
    fi
}

# キーチェーンパスワード初期設定関数(内部用)
setup_keychain() {
    set_keychain_password "キーチェーンにVPNパスワードを設定します。" "パスワードがキーチェーンに保存されました。"
}

# キーチェーンパスワードローテート関数
rotate_keychain() {
    set_keychain_password "キーチェーンのVPNパスワードをローテートします。" "パスワードが更新されました。"
}

# パスワードが利用可能か確認する関数(必要に応じて対話的セットアップ)
ensure_password() {
    if PASS=$(get_password 2>/dev/null) && [ -n "$PASS" ]; then
        return 0
    fi

    echo "キーチェーンにパスワードが設定されていません。"
    echo "VPNに接続するにはパスワードの設定が必要です。"
    echo ""
    echo -n "パスワードを設定しますか? (y/n): "
    read -r response
    if [[ "$response" =~ ^[Yy]$ ]]; then
        setup_keychain
        if PASS=$(get_password 2>/dev/null) && [ -n "$PASS" ]; then
            return 0
        else
            echo "エラー: パスワードの設定に失敗しました。"
            exit 1
        fi
    else
        echo ""
        echo "パスワードが設定されていないため、接続を中止します。"
        echo "次回接続時にパスワード設定が再度促されます。"
        exit 1
    fi
}

# キーチェーンパスワードを削除する関数
remove_keychain() {
    echo "キーチェーンからVPNパスワードを削除します。"
    echo "削除すると次回VPN接続時に再度パスワードの入力が必要になります。"
    echo "本当に削除しますか? (y/n)"
    read -r response
    if [[ "$response" =~ ^[Yy]$ ]]; then
        if security delete-generic-password -a "$KEYCHAIN_ACCOUNT" -s "$KEYCHAIN_SERVICE" 2>/dev/null; then
            echo "パスワードがキーチェーンから削除されました。"
        else
            echo "エラー: パスワードの削除に失敗しました。パスワードが存在しない可能性があります。"
            exit 1
        fi
    else
        echo "削除をキャンセルしました。"
    fi
}

# ====================
# VPNヘルパー関数
# ====================

# VPNが接続されているかチェックする関数(内部用)
is_vpn_connected() {
    [ -e $cisco_vpn ] || return 1
    $cisco_vpn state 2>/dev/null | grep -q "Connected"
}

# VPN接続を試行する関数
# ユーザー名とパスワードをCisco VPNクライアントに送信して接続を開始
attempt_vpn_connection() {
    printf "${VPN_USER}\n${PASS}" | $cisco_vpn -s connect "$VPN_SERVER" 2>&1
}

# 接続成功をチェックする関数
is_connection_successful() {
    echo "$1" | grep -q "state: Connected"
}

# 認証失敗をチェックする関数
has_auth_failure() {
    echo "$1" | grep -qi "authentication failed\|login failed\|connection failed\|access denied"
}

# ====================
# メインVPN関数
# ====================

# VPN状態をチェックする関数(statusコマンド用)
check_vpn_status() {
    check_cisco_installed
    if is_vpn_connected; then
        echo "VPN接続中"
    else
        echo "VPN未接続"
    fi
}

# VPNを切断する関数
disconnect_vpn() {
    check_cisco_installed
    echo "VPNから切断中..."

    if $cisco_vpn disconnect > /dev/null 2>&1; then
        echo "VPNから切断しました。"
    else
        echo "VPN切断中にエラーが発生しました。"
        exit 1
    fi
}

# VPNを接続する関数
connect_vpn() {
    # パスワードが利用可能か確認(必要に応じて対話的セットアップ)
    ensure_password
    check_cisco_installed

    # 干渉する可能性のあるCisco GUIプロセスをクリーンアップ
    if pgrep -f "Cisco AnyConnect" > /dev/null; then
        echo "既存のCisco AnyConnect GUIプロセスを終了します..."
        if pkill -f "Cisco AnyConnect"; then
            echo "GUIプロセスを終了しました。"
            sleep 2  # プロセスクリーンアップを待つ
        else
            echo "警告: GUIプロセス終了に失敗しました。手動で終了してください。"
        fi
    fi

    # 既に接続済みの場合は再接続不要
    if is_vpn_connected; then
        echo "VPNは既に接続されています。"
        exit 0
    fi

    echo "VPNに接続中..."
    # VPN接続を実行(ここで実際の接続試行が行われる)
    output=$(attempt_vpn_connection)
    exit_code=$?

    # "Connected"状態を確認して接続成功をチェック
    if is_connection_successful "$output"; then
        connection_success
    fi

    # 再試行が必要な重大エラーをチェック
    if [ $exit_code -ne 0 ] || has_auth_failure "$output"; then
        echo "VPN接続中にエラーが発生しました。プロセスを終了して再試行します。"
        echo "エラー詳細: $output"
        pkill vpn 2>/dev/null
        pkill cisco 2>/dev/null
        # プロセス終了を待つ
        sleep 3
        output=$(attempt_vpn_connection)

        if is_connection_successful "$output"; then
            connection_success
        elif has_auth_failure "$output"; then
            echo "再試行後もエラーが発生しました。接続に失敗しました。"
            echo "エラー詳細: $output"
            exit 1
        fi
    fi

    # 警告は表示するがエラーとして扱わない
    if echo "$output" | grep -qi "warning\|cert.*expired"; then
        echo "警告が発生しましたが、接続を継続します。"
        echo "警告内容:"
        echo "$output" | grep -i "warning\|cert.*expired"
    fi

    # 接続が実際に成功したかチェック
    sleep 2
    if is_vpn_connected; then
        connection_success
    else
        echo "接続処理は完了しましたが、VPN状態の確認に失敗しました。"
        echo "手動で接続状態を確認してください: $0 status"
        exit 1
    fi
}

# ====================
# UI関数
# ====================

# 使用方法を表示する関数
show_usage() {
    echo "使用方法: $0 [オプション]"
    echo "オプション:"
    echo "  connect     VPNに接続 (デフォルト、初回時は自動でパスワード設定)"
    echo "  disconnect  VPNから切断"
    echo "  rotate      キーチェーンのパスワードを更新"
    echo "  remove      キーチェーンからパスワードを削除"
    echo "  status      VPN接続状態を確認"
    echo "  -h, --help  このヘルプを表示"
}

# ====================
# メインスクリプト
# ====================

# コマンドライン引数を解析
if [ $# -eq 0 ]; then
    # デフォルトアクションは接続
    connect_vpn
else
    case "$1" in
        connect)
            connect_vpn
            ;;
        disconnect)
            disconnect_vpn
            ;;
        rotate)
            rotate_keychain
            ;;
        remove)
            remove_keychain
            ;;
        status)
            check_vpn_status
            ;;
        -h|--help)
            show_usage
            ;;
        *)
            echo "不明なオプション: $1"
            show_usage
            exit 1
            ;;
    esac
fi

タブ補完

vpn_completion.sh
#!/bin/bash
# vpn.sh用のBash補完設定

_vpn_completion() {
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    
    # 利用可能なオプション
    opts="connect disconnect rotate remove status"
    
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    return 0
}

# vpn.sh用の補完を登録
complete -F _vpn_completion vpn.sh
complete -F _vpn_completion ./vpn.sh
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?