Update 2025-12-17 20:47:56
All checks were successful
Deploy VConnect API / Test Build (push) Successful in 25s
Deploy VConnect API / Deploy to Server (push) Successful in 9s

This commit is contained in:
unknown
2025-12-17 20:47:56 +09:00
parent c22150f693
commit 8a6454aa6f
2 changed files with 80 additions and 32 deletions

View File

@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
from app.api.auth import get_current_user from app.api.auth import get_current_user
from app.schemas.auth import CurrentUser from app.schemas.auth import CurrentUser
from app.services.temp_ssh_password_service import temp_ssh_password_manager from app.services.temp_ssh_password_service import temp_ssh_password_manager
from app.config import settings
import os import os
router = APIRouter() router = APIRouter()
@@ -21,15 +22,38 @@ async def get_ssh_credentials(current_user: CurrentUser = Depends(get_current_us
Returns: Returns:
SSH 연결 정보 및 임시 비밀번호 SSH 연결 정보 및 임시 비밀번호
""" """
# 1. 정적 자격증명 확인 (개발 환경 또는 정적 비밀번호 사용 시)
if settings.SSH_PASSWORD:
ssh_host = settings.SSH_HOST or "api.mouse84.com"
ssh_port = settings.SSH_PORT
ssh_username = settings.SSH_USERNAME or current_user.username
# 만료 시간 (24시간)
expires_at = datetime.utcnow() + timedelta(hours=24)
return {
"ssh_host": ssh_host,
"ssh_port": ssh_port,
"ssh_username": ssh_username,
"ssh_password": settings.SSH_PASSWORD,
"expires_at": expires_at.isoformat(),
"expires_in_seconds": 86400
}
# 2. 임시 비밀번호 생성 (기본 동작)
# .env 설정을 우선 사용 (username이 지정된 경우 해당 계정으로 임시 비밀번호 생성)
target_username = settings.SSH_USERNAME or current_user.username
# 임시 비밀번호 생성 (1시간 유효) # 임시 비밀번호 생성 (1시간 유효)
temp_password = temp_ssh_password_manager.generate_password( temp_password = temp_ssh_password_manager.generate_password(
username=current_user.username, username=target_username,
validity_hours=1 validity_hours=1
) )
# SSH 서버 정보 (외부 접속용) # SSH 서버 정보 (설정값 우선)
ssh_host = os.getenv("SSH_HOST", "api.mouse84.com") # 외부 DDNS ssh_host = settings.SSH_HOST or "api.mouse84.com"
ssh_port = int(os.getenv("SSH_PORT", "54054")) # 외부 포트 (내부 22로 포워딩) ssh_port = settings.SSH_PORT or 54054
# 만료 시간 계산 # 만료 시간 계산
expires_at = datetime.utcnow() + timedelta(hours=1) expires_at = datetime.utcnow() + timedelta(hours=1)
@@ -37,7 +61,7 @@ async def get_ssh_credentials(current_user: CurrentUser = Depends(get_current_us
return { return {
"ssh_host": ssh_host, "ssh_host": ssh_host,
"ssh_port": ssh_port, "ssh_port": ssh_port,
"ssh_username": current_user.username, "ssh_username": target_username,
"ssh_password": temp_password, "ssh_password": temp_password,
"expires_at": expires_at.isoformat(), "expires_at": expires_at.isoformat(),
"expires_in_seconds": 3600 "expires_in_seconds": 3600

View File

@@ -1,14 +1,14 @@
import secrets import secrets
import hashlib import hashlib
from typing import Dict, Optional from typing import Dict, List, Optional
from datetime import datetime, timedelta from datetime import datetime, timedelta
class TempSshPasswordManager: class TempSshPasswordManager:
"""임시 SSH 비밀번호 관리""" """임시 SSH 비밀번호 관리"""
def __init__(self): def __init__(self):
# 메모리 기반 저장소 (프로덕션에서는 Redis 사용 권장) # 메모리 기반 저장소 (username -> list of {hash, expires_at})
self._passwords: Dict[str, dict] = {} self._passwords: Dict[str, List[dict]] = {}
def generate_password(self, username: str, validity_hours: int = 1) -> str: def generate_password(self, username: str, validity_hours: int = 1) -> str:
""" """
@@ -21,6 +21,9 @@ class TempSshPasswordManager:
Returns: Returns:
임시 비밀번호 임시 비밀번호
""" """
# 메모리 정리 (생성 시마다 만료된 것 정리)
self.cleanup_expired()
# 안전한 랜덤 비밀번호 생성 (32자) # 안전한 랜덤 비밀번호 생성 (32자)
temp_password = secrets.token_urlsafe(32) temp_password = secrets.token_urlsafe(32)
@@ -30,49 +33,70 @@ class TempSshPasswordManager:
# 만료 시간 계산 # 만료 시간 계산
expires_at = datetime.utcnow() + timedelta(hours=validity_hours) expires_at = datetime.utcnow() + timedelta(hours=validity_hours)
# 저장 # 새 토큰 정보
self._passwords[username] = { token_data = {
"password_hash": password_hash, "password_hash": password_hash,
"expires_at": expires_at, "expires_at": expires_at,
"created_at": datetime.utcnow() "created_at": datetime.utcnow()
} }
# 해당 사용자에 토큰 추가 (리스트 초기화)
if username not in self._passwords:
self._passwords[username] = []
self._passwords[username].append(token_data)
return temp_password return temp_password
def verify_password(self, username: str, password: str) -> bool: def verify_password(self, username: str, password: str) -> bool:
""" """
비밀번호 검증 비밀번호 검증 (다중 토큰 지원)
Args:
username: 사용자명
password: 검증할 비밀번호
Returns:
유효 여부
""" """
if username not in self._passwords: if username not in self._passwords:
return False return False
stored = self._passwords[username] input_hash = hashlib.sha256(password.encode()).hexdigest()
now = datetime.utcnow()
# 만료 확인 # 유효한 토큰 중 하나라도 일치하면 성공
if datetime.utcnow() > stored["expires_at"]: # (리스트 복사본으로 순회하지 않고, 인덱스로 접근하거나 필터링)
del self._passwords[username] valid_tokens = []
return False is_valid = False
# 비밀번호 확인 for token in self._passwords[username]:
password_hash = hashlib.sha256(password.encode()).hexdigest() # 만료된 토큰은 제외 (Clean up on read)
return password_hash == stored["password_hash"] if now > token["expires_at"]:
continue
valid_tokens.append(token)
if token["password_hash"] == input_hash:
is_valid = True
# 리스트 업데이트 (만료된 것 제거됨)
self._passwords[username] = valid_tokens
return is_valid
def cleanup_expired(self): def cleanup_expired(self):
"""만료된 비밀번호 정리""" """만료된 비밀번호 정리"""
now = datetime.utcnow() now = datetime.utcnow()
expired = [ users_to_check = list(self._passwords.keys())
username for username, data in self._passwords.items()
if now > data["expires_at"] for username in users_to_check:
] # 유효한 토큰만 필터링
for username in expired: self._passwords[username] = [
del self._passwords[username] token for token in self._passwords[username]
if now <= token["expires_at"]
]
# 토큰이 하나도 없으면 사용자 키 삭제
if not self._passwords[username]:
del self._passwords[username]
def get_active_count(self) -> int:
"""현재 활성화된 토큰 수 (디버깅용)"""
return sum(len(tokens) for tokens in self._passwords.values())
# 싱글톤 인스턴스 # 싱글톤 인스턴스
temp_ssh_password_manager = TempSshPasswordManager() temp_ssh_password_manager = TempSshPasswordManager()