1. 외부 SSH 클라이언트 설치가 금지된 공교육과 국방망의 난제

학교 컴퓨터실의 리눅스 실습 서버(Ubuntu, Debian)나 군사 폐쇄망의 네트워크 장비(스위치, 라우터, 방화벽)를 유지보수할 때 가장 번거로운 작업은 SSH 클라이언트 환경 구축입니다. 많은 환경에서 보안 감사 정책이나 관리자 권한(UAC) 제한으로 인해 PuTTY, MobaXterm, Xshell 같은 서드파티 상용/포터블 소프트웨어의 설치나 실행이 엄격히 차단되어 있습니다.

설령 프로그램을 복사해 실행하더라도, 접속 세션 비밀번호나 SSH 개인키(Private Key)가 평문 텍스트 파일이나 윈도우 레지스트리에 무방비로 노출되어 자격증명 탈취(Credential Dumping) 공격에 취약해지는 구조적 결함이 있었습니다.

ZeliDesk의 ZeliSSH는 외부 종속성을 완전히 배제하고, "순수 C# 관리 코드 기반의 Renci.SshNet ShellStream 비동기 가상 터미널, VT100/xterm ANSI 이스케이프 시퀀스 정규식 실시간 정제, Windows DPAPI 커널 암호화 기반 자격증명 영속화, 및 500KB 링 버퍼 메모리 가드"를 통해 윈도우 데스크톱 내에 자족적인 보안 셸 거버넌스를 완성했습니다.

2. 외부 EXE 종속성 제로: Renci.SshNet 기반 가상 셸 스트림 파이프라인

ZeliSSHWindow.xaml.cs는 OS 프로세스 래핑 방식이 아닌, 순수 .NET 매니지드 라이브러리인 Renci.SshNet을 직접 구동합니다. 이를 통해 백그라운드에서 실행되는 가상 터미널 에뮬레이션(`xterm`, 120열 30행)을 단일 프로세스 메모리 공간 내에서 초경량으로 연결합니다.

C# — ZeliSSH/Views/ZeliSSHWindow.xaml.cs (SshClient 연결 및 ShellStream 비동기 디스패치)
private async void BtnConnect_Click(object sender, RoutedEventArgs e)
{
    if (_client != null && _client.IsConnected)
    {
        Disconnect();
        return;
    }

    string host = TxtHost.Text.Trim();
    string user = TxtUser.Text.Trim();
    string pass = TxtPass.Password;
    if (!int.TryParse(TxtPort.Text.Trim(), out int port)) port = 22;
    string keyPath = PnlKeyFile.Visibility == Visibility.Visible ? TxtKeyFilePath.Text : null;

    // 접속 프로필 자동 영속화 (비밀번호 DPAPI 암호화 포함)
    SaveProfile(host, port.ToString(), user, pass, keyPath);

    BtnConnect.IsEnabled = false;
    SetStatus("connecting");
    AppendLog(string.Format(Loc.Get("SSH_Connecting"), user, host, port));

    await Task.Run(() =>
    {
        try
        {
            // 1) 인증 방식 분기: 개인키(PEM/PPK/KEY) 또는 패스워드 인증
            if (!string.IsNullOrEmpty(keyPath) && File.Exists(keyPath))
            {
                var keyFile = string.IsNullOrEmpty(pass) 
                    ? new PrivateKeyFile(keyPath) 
                    : new PrivateKeyFile(keyPath, pass); // 암호화된 개인키 복호 지원
                var methods = new AuthenticationMethod[] { new PrivateKeyAuthenticationMethod(user, keyFile) };
                var connectionInfo = new ConnectionInfo(host, port, user, methods);
                _client = new SshClient(connectionInfo);
            }
            else
            {
                _client = new SshClient(host, port, user, pass);
            }

            _client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(10);
            _client.Connect();

            // 2) 가상 터미널(xterm) 셸 스트림 생성 (120x30, 960x720 픽셀, 4KB 버퍼)
            _shellStream = _client.CreateShellStream("xterm", 120, 30, 960, 720, 4096);
            _cts = new CancellationTokenSource();

            // 3) 백그라운드 비동기 읽기 루프 기동
            _ = Task.Run(() => ReadStreamLoop(_cts.Token));

            DispatcherQueue.TryEnqueue(() =>
            {
                AppendLog(Loc.Get("SSH_ConnectSuccess") + "\n");
                BtnConnect.Content = Loc.Get("SSH_Disconnect");
                SetStatus("connected");
            });
        }
        catch (Exception ex)
        {
            DispatcherQueue.TryEnqueue(() =>
            {
                AppendLog(string.Format(Loc.Get("SSH_NetworkError"), ex.Message));
                SetStatus("error");
            });
            DisconnectCore();
        }
    });

    BtnConnect.IsEnabled = true;
}
🛡️ 하이브리드 키페어 인증: 암호가 걸린 개인키(Passphrase Protected Key) 완벽 수용

국방 및 엔터프라이즈 리눅스 서버는 패스프레이즈(Passphrase)로 암호화된 RSA/ED25519 개인키를 요구합니다. ZeliSSH는 new PrivateKeyFile(keyPath, pass) 생성자를 통해 키 파일 경로와 비밀번호 입력 필드를 지능적으로 연동하여, 별도의 키 변환 도구(PuTTYgen 등) 없이도 최신 보안 규격 키페어를 즉시 로드합니다.

3. 터미널 텍스트 무결성: 컴파일된 정규식 기반 ANSI 이스케이프 정제

리눅스 셸(bash, zsh)은 색상 강조, 커서 이동, 화면 지우기 등을 위해 수많은 VT100/xterm ANSI 이스케이프 코드(`\x1B[0;32m`, `\x1B[2J` 등)를 클라이언트로 전송합니다. 이를 단순 문자열로 UI 텍스트박스에 렌더링하면 알아볼 수 없는 특수문자가 화면을 어지럽힙니다.

ZeliSSH는 고성능 컴파일 정규식 AnsiRegex를 통해 초당 수천 줄의 스트림 데이터에서도 CPU 오버헤드 없이 제어 문자를 완벽히 제거합니다.

C# — ZeliSSH/Views/ZeliSSHWindow.xaml.cs (ANSI 이스케이프 정제 및 개행 정규화)
// 컴파일된 정규식으로 인스턴스화하여 처리 처리량 극대화
private static readonly Regex AnsiRegex = 
    new Regex(@"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])", RegexOptions.Compiled);

private void ReadStreamLoop(CancellationToken token)
{
    var reader = new StreamReader(_shellStream);
    char[] buffer = new char[4096];

    try
    {
        while (!token.IsCancellationRequested && _shellStream != null && _shellStream.CanRead)
        {
            int bytesRead = reader.Read(buffer, 0, buffer.Length);
            if (bytesRead > 0)
            {
                string data = new string(buffer, 0, bytesRead);

                // ★ 핵심 정제: VT100/xterm ANSI 제어 코드 일괄 소거
                data = AnsiRegex.Replace(data, "");

                // CRLF, CR 개행 문자를 표준 LF로 정규화
                data = data.Replace("\r\n", "\n").Replace('\r', '\n');

                DispatcherQueue.TryEnqueue(() =>
                {
                    AppendLog(data);
                });
            }
            else
            {
                Task.Delay(50).Wait();
            }
        }
    }
    catch (Exception)
    {
        // 외부에서 소켓 연결을 끊었을 때 발생하는 정상 예외 무시
    }
}

4. 메모리 팽창 방어: 500KB 슬라이딩 링 버퍼와 부드러운 자동 스크롤

장시간 서버 로그(`tail -f /var/log/syslog` 등)를 모니터링하다 보면 수백 메가바이트의 텍스트가 메모리에 누적되어 WinUI 3 UI 스레드가 프리징되는 참사가 일어납니다. ZeliSSH는 엄격한 500KB 상한 링 버퍼를 적용하여 최대 메모리 점유율을 언제나 1MB 미만으로 철저히 통제합니다.

C# — ZeliSSH/Views/ZeliSSHWindow.xaml.cs (500KB 링 버퍼 및 ScrollViewer 동기화)
private string _logBuffer = "";

private void AppendLog(string text)
{
    _logBuffer += text;

    // ★ 500KB 초과 시 앞부분 절반을 원자적으로 절삭(Trim)하여 UI 프리징 원천 방어
    const int maxLen = 500_000;
    if (_logBuffer.Length > maxLen)
    {
        _logBuffer = string.Format("... ({0}) ...\n", Loc.Get("SSH_LogTrimmed")) +
                     _logBuffer.Substring(_logBuffer.Length - maxLen / 2);
    }

    TxtLog.Text = _logBuffer;

    // 텍스트 갱신 후 뷰포트 레이아웃 강제 동기화 및 바닥 스크롤
    LogScrollViewer.UpdateLayout();
    LogScrollViewer.ChangeView(null, LogScrollViewer.ScrollableHeight, null);
}

5. 국방급 자격증명 보안: Windows DPAPI 기반 프로필 암호화

사용자가 자주 접속하는 리눅스 서버의 계정 정보를 저장할 때, 패스워드를 평문 JSON으로 남겨두는 것은 악성코드나 로컬 사용자에 의한 자격증명 탈취로 이어집니다. ZeliSSH는 SecretBox.cs와의 긴밀한 연동을 통해 Windows DPAPI 커널 암호화를 강제합니다.

C# — ZeliSSH/Views/ZeliSSHWindow.xaml.cs (SecretBox 연동 프로필 암호화 영속화)
private void PersistProfiles()
{
    try
    {
        // ★ 메모리의 평문 비밀번호를 저장 직전에 Windows DPAPI로 커널 암호화
        var toSave = _profiles.Select(p => new SshProfile
        {
            Host = p.Host,
            Port = p.Port,
            User = p.User,
            Pass = ZeliDesk.Services.SecretBox.Protect(p.Pass), // "dpapi:Base64..."
            KeyPath = p.KeyPath
        }).ToList();

        var settings = ApplicationData.Current.LocalSettings;
        settings.Values["ZeliSSH_Profiles"] = JsonSerializer.Serialize(toSave);
    }
    catch { }
}

private void LoadProfiles()
{
    try
    {
        var settings = ApplicationData.Current.LocalSettings;
        string savedJson = settings.Values["ZeliSSH_Profiles"] as string;
        if (!string.IsNullOrEmpty(savedJson))
        {
            var loaded = JsonSerializer.Deserialize<List<SshProfile>>(savedJson);
            if (loaded != null)
            {
                // 로드 시 복호화 (평문 레거시 데이터는 통과 후 다음 저장 때 자동 암호화 마이그레이션)
                foreach (var p in loaded) 
                    p.Pass = ZeliDesk.Services.SecretBox.Unprotect(p.Pass);
                foreach (var p in loaded) 
                    _profiles.Add(p);
            }
        }
    }
    catch { }
}

6. 종합 아키텍처 다이어그램

ZeliSSH의 내부 가상 터미널 스트리밍과 보안 거버넌스 전체 흐름도입니다:

ZeliSSH 온디바이스 원격 셸 아키텍처 토폴로지
┌────────────────────────────────────────────────────────────────────────────────────────┐
│                                ZeliSSH Engine Host                                     │
│                                                                                        │
│  [ Profile Storage ] ────► SecretBox.Protect(Pass) ➔ Windows DPAPI Kernel Encryption   │
│                                      │                                                 │
│                                      ▼                                                 │
│  [ Renci.SshNet Client ] ──► SshClient(ConnectionInfo with PrivateKey / Password)      │
│                                      │                                                 │
│                                      ▼                                                 │
│  [ Virtual xterm ShellStream ] ──► CreateShellStream("xterm", 120, 30, 960, 720)       │
│                                      │                                                 │
│                                      ▼ (4096-Char Buffer Loop)                         │
│  [ AnsiRegex Sanitizer ] ──► Strip VT100 Escape Codes (\x1B[...]) ➔ Clean Text Stream │
│                                      │                                                 │
│                                      ▼                                                 │
│  [ 500KB Ring Buffer Guard ] ──► Max 500,000 Chars FIFO Trim ➔ UI Zero-Freezing        │
│                                      │                                                 │
│                                      ▼                                                 │
│  [ Terminal UI HUD ] ◄─────── 5 Custom Themes (Matrix, Amber, Ocean, Snow, Dracula)    │
└────────────────────────────────────────────────────────────────────────────────────────┘

7. 결론: 가장 안전하고 가벼운 원격 셸이 업무의 연속성을 보장한다

리눅스 서버 관리는 네트워크 엔지니어와 정보 교사, 군 전산 장교들에게 가장 일상적이면서도 중요한 업무입니다. 그럼에도 불구하고 외부 소프트웨어 다운로드 통제와 보안 규제로 인해 원격 셸 하나를 띄우기 위해 수많은 행정적 장벽을 거쳐야 했습니다.

ZeliSSH는 외부 바이너리 종속성 0개, 커널 DPAPI 기반 자격증명 암호화, 그리고 ANSI 이스케이프 정제를 탑재한 100% 순수 매니지드 C# 아키텍처로 윈도우 데스크톱 환경에서 가장 안전하고 즉각적인 원격 서버 제어 솔루션을 구현했습니다. ZeliDesk의 탄탄한 유틸리티 생태계는 교단과 전선의 디지털 안정성을 가장 견고하게 수호하고 있습니다.