name: VPN 연결 (F5 BIG-IP) description: F5 BIG-IP Edge 클라이언트(.deb)를 설치하고 TOTP를 로컬 생성해 VPN에 연결 inputs: deb-path: required: false default: "" description: F5 클라이언트 .deb 경로. 비어있으면 액션 폴더($GITHUB_ACTION_PATH)의 *.deb 자동 탐색 deb-url: required: false default: "https://gitea.pitap.at/public/commonDeploy/releases/download/setup01/linux_f5cli.x86_64.deb" description: .deb 다운로드 URL (deb-path가 비어있으면 이 URL에서 다운로드해서 설치) vpn-host: required: true description: F5 VPN 호스트 (예 https://vpn.example.com) vpn-user: required: true description: VPN 사용자 ID vpn-password: required: false default: "" description: VPN 정적 비밀번호 (OTP만 쓰는 설정이면 비워둠) otp-seed: required: true description: TOTP 시드 키 (이 값으로 매 실행 OTP 코드를 로컬 생성) otp-seed-format: required: false default: "base64" description: > OTP 시드 포맷. base32=구글OTP류 문자열(대문자/공백 자동 정규화), hex=16진수 문자열, base64=base64로 인코딩된 키(예 R1F5...== → 디코딩 후 사용). otp-mode: required: false default: "append" description: > OTP를 비밀번호 필드에 넣는 방식. append=정적PW+OTP 붙여쓰기 / replace=OTP만 / none=OTP미사용(정적PW만). F5 로그온 페이지 설정에 맞게 지정. timeout: required: false default: "60" description: 연결 완료 대기 최대 시간(초) connected-pattern: required: false default: "session established|Connection established|已连接|connected" description: > f5fpc --info 출력에서 '연결됨'으로 판단할 정규식. 클라이언트 버전/언어에 따라 다르니 `f5fpc --info` 실제 출력에 맞게 조정. runs: using: composite steps: - name: 의존성/클라이언트 설치 shell: bash env: ACTION_PATH: ${{ github.action_path }} DEB_PATH: ${{ inputs.deb-path }} DEB_URL: ${{ inputs.deb-url }} run: | set -eu # root가 아니면 sudo 사용 SUDO="" [ "$(id -u)" -ne 0 ] && SUDO="sudo" # oathtool(TOTP 생성) 설치 확인 if ! command -v oathtool >/dev/null 2>&1; then if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update && $SUDO apt-get install -y oathtool else echo "oathtool 설치 실패: apt 기반 러너가 아님" exit 1 fi fi # 설치할 .deb 결정: deb-path > deb-url > 액션 폴더의 *.deb DEB="" if [ -n "$DEB_PATH" ]; then DEB="$DEB_PATH" elif [ -n "$DEB_URL" ]; then DEB="/tmp/f5-client.deb" curl -fsSL -o "$DEB" "$DEB_URL" else DEB=$(ls "$ACTION_PATH"/*.deb 2>/dev/null | head -1 || true) fi if [ -z "$DEB" ] || [ ! -f "$DEB" ]; then echo ".deb를 찾을 수 없습니다 (deb-path/deb-url/액션폴더 확인)" exit 1 fi echo "F5 클라이언트 설치: $DEB" # dpkg 후 의존성 깨지면 apt-get install -f로 보정 $SUDO dpkg -i "$DEB" || $SUDO apt-get install -f -y # f5fpc 존재 확인 if ! command -v f5fpc >/dev/null 2>&1; then echo "f5fpc가 설치되지 않았습니다 (.deb 내용/PATH 확인)" exit 1 fi - name: TUN 디바이스 확인 shell: bash run: | set -eu # VPN 인터페이스 생성에는 /dev/net/tun + NET_ADMIN이 필요합니다. # 러너 컨테이너 옵션: --cap-add=NET_ADMIN --device /dev/net/tun if [ ! -c /dev/net/tun ]; then echo "/dev/net/tun 이 없습니다. 러너에 --device /dev/net/tun --cap-add=NET_ADMIN 필요" exit 1 fi - name: VPN 연결 shell: bash env: VPN_HOST: ${{ inputs.vpn-host }} VPN_USER: ${{ inputs.vpn-user }} VPN_PASS: ${{ inputs.vpn-password }} OTP_SEED: ${{ inputs.otp-seed }} OTP_SEED_FORMAT: ${{ inputs.otp-seed-format }} OTP_MODE: ${{ inputs.otp-mode }} TIMEOUT: ${{ inputs.timeout }} CONNECTED_PATTERN: ${{ inputs.connected-pattern }} run: | set -eu SUDO="" [ "$(id -u)" -ne 0 ] && SUDO="sudo" # TOTP 코드 로컬 생성 (API 불필요) # 시드 정규화: 앞뒤/중간 공백 제거 SEED=$(printf '%s' "$OTP_SEED" | tr -d '[:space:]') case "$OTP_SEED_FORMAT" in base32) # Base32는 대문자 필요 → 소문자 자동 변환 SEED=$(printf '%s' "$SEED" | tr 'a-z' 'A-Z') OTP=$(oathtool --totp -b "$SEED") ;; hex) OTP=$(oathtool --totp "$SEED") ;; base64) # base64가 raw secret을 감싼 경우와 base32 문자열을 감싼 경우를 모두 지원 DECODED_SEED=$(printf '%s' "$SEED" | base64 -d 2>/dev/null || true) if [ -z "$DECODED_SEED" ]; then echo "base64 디코딩 실패: 시드가 올바른 base64인지 확인"; exit 1 fi DECODED_BASE32=$(printf '%s' "$DECODED_SEED" | tr -d '[:space:]' | tr 'a-z' 'A-Z') if printf '%s' "$DECODED_BASE32" | grep -Eq '^[A-Z2-7]+=*$'; then OTP=$(oathtool --totp -b "$DECODED_BASE32") else SEED_HEX=$(printf '%s' "$SEED" | base64 -d 2>/dev/null | od -An -v -tx1 | tr -d ' \n') OTP=$(oathtool --totp "$SEED_HEX") fi ;; *) echo "잘못된 otp-seed-format: $OTP_SEED_FORMAT (base32|hex|base64)"; exit 1 ;; esac # OTP 결합 방식에 따라 password 필드 구성 case "$OTP_MODE" in append) PASSFIELD="${VPN_PASS}${OTP}" ;; replace) PASSFIELD="${OTP}" ;; none) PASSFIELD="${VPN_PASS}" ;; *) echo "잘못된 otp-mode: $OTP_MODE (append|replace|none)"; exit 1 ;; esac echo "VPN 연결 시도: ${VPN_HOST} (user=${VPN_USER}, otp-mode=${OTP_MODE})" # 참고: f5fpc 플래그는 클라이언트 버전마다 다를 수 있음 (`f5fpc --help`로 확인). # 이 클라이언트의 비동기 실행 옵션은 --nonblock이며, --nogui는 지원되지 않는 버전이 있음. if ! $SUDO f5fpc --start \ --host "$VPN_HOST" \ --user "$VPN_USER" \ --password "$PASSFIELD" \ --nocheck --nonblock; then echo "VPN 시작 명령 실패. f5fpc --help 출력과 입력값을 확인하세요." $SUDO f5fpc --info 2>/dev/null || true exit 1 fi # 연결은 백그라운드 데몬에서 비동기로 진행 → --info를 폴링해 완료 대기 echo "연결 완료 대기 중 (최대 ${TIMEOUT}초)..." DISCONNECTED_PATTERN="not connected|Client not connected|Please login|Unable to open message queue|disconnected|logon failed|로그인" for i in $(seq 1 "$TIMEOUT"); do INFO=$($SUDO f5fpc --info 2>&1 || true) if [ "$i" -eq 1 ] || [ $((i % 5)) -eq 0 ]; then echo "VPN 상태 확인 (${i}/${TIMEOUT}초):" if [ -n "$INFO" ]; then echo "$INFO" else echo "(f5fpc --info 출력 없음)" fi fi if echo "$INFO" | grep -Eqi "logon failed"; then echo "VPN 로그인 실패:" echo "$INFO" exit 1 fi if echo "$INFO" | grep -Eqi "$DISCONNECTED_PATTERN"; then sleep 1 continue fi if echo "$INFO" | grep -Eqi "$CONNECTED_PATTERN"; then echo "VPN 연결 완료" echo "$INFO" exit 0 fi sleep 1 done echo "VPN 연결 실패 (${TIMEOUT}초 초과). 마지막 상태:" $SUDO f5fpc --info 2>/dev/null || true exit 1