
SwiftNIO SSH는 SwiftNIO를 사용한 SSH의 프로그래매틱 구현입니다.
이 프로젝트는 SwiftNIO를 사용한 SSH 지원을 포함합니다.
SwiftNIO SSH는 SSH의 프로그래매틱 구현입니다. 즉, 프로그래머가 SSH를 사용하는 엔드포인트를 구현할 수 있게 해주는 API 모음입니다. 중요한 점은 이것이 openssh보다 libssh2에 더 가깝다는 의미입니다. SwiftNIO SSH는 프로덕션 준비가 완료된 SSH 클라이언트와 서버를 제공하지 않지만, 이러한 클라이언트와 서버를 구축하기 위한 구성 요소를 제공합니다.
프로그래매틱 SSH 구현을 제공하는 데는 여러 이유가 있습니다. 그 중 하나는 SSH가 사용자 상호작용성과 독특한 관계를 가지고 있다는 점입니다. 기술 사용자들은 원격 머신에서 명령을 실행하거나 대화형 셸을 실행하기 위해 SSH와 대화형으로 상호작용하는 데 매우 익숙합니다. 이러한 요청에 프로그래매틱으로 응답할 수 있는 능력은 흥미로운 대안적 상호작용 방식을 가능하게 합니다. 이전 예로, Twisted의 Manhole은 프로그래매틱 SSH 구현인 conch를 사용하여 실행 중인 Python 서버 내에서 대화형 Python 인터프리터를 제공하거나, ssh-chat은 일반 SSH 셸 기능 대신 채팅방을 제공하는 SSH 서버입니다. TCP 포워딩에 대한 혁신적인 용도도 상상할 수 있습니다.
프로그래매틱 SSH를 제공하는 또 다른 좋은 이유는 서비스가 명령 실행을 포함하는 방식으로 다른 서비스와 상호작용해야 하는 경우가 드물지 않기 때문입니다. Process는 로컬 사용 사례에 대해 이 문제를 해결하지만, 때로는 호출해야 하는 명령이 원격에 있을 수 있습니다. Process가 이 호출을 실행하기 위해 ssh 클라이언트를 하위 프로세스로 시작할 수 있지만, SSH를 직접 호출하는 것이 훨씬 더 간단할 수 있습니다. 이것이 libssh2의 대상 사용 사례입니다. SwiftNIO SSH는 libssh2의 네트워킹 및 암호화 계층과 동등한 기능을 제공하므로, 의욕적인 사용자가 Swift 서비스 내에서 직접 SSH 세션을 구동할 수 있습니다.
SwiftNIO SSH의 최신 버전은 Swift 5.9 이상을 지원합니다. SwiftNIO SSH 릴리스에서 지원하는 최소 Swift 버전은 아래에 자세히 설명되어 있습니다.
SwiftNIO SSH는 다음과 같은 기능 세트로 SSHv2를 지원합니다:
SwiftNIO SSH는 SwiftNIO ChannelHandler인 NIOSSHHandler를 제공합니다. 이 핸들러는 SSH 프로토콜의 대부분을 직접 구현합니다. 사용자는 SSH 메시지를 직접 생성할 것으로 예상되지 않습니다. 대신 하위 채널과 델리게이트를 통해 NIOSSHHandler와 상호작용합니다.
SSH는 다중화 프로토콜입니다. 각 SSH 연결은 적절히 '채널'이라고 불리는 여러 양방향 통신 채널로 세분화됩니다. SwiftNIO SSH는 '하위 채널' 추상화를 사용하여 이 구조를 반영합니다. 피어가 새 SSH 채널을 생성하면 SwiftNIO SSH는 해당 SSH 채널의 모든 트래픽을 나타내는 새 NIO Channel을 생성합니다. 이 하위 Channel 내에서는 모든 이벤트가 서로에 대해 엄격하게 순서가 지정되지만, 다른 Channel의 이벤트는 구현에 의해 자유롭게 인터리브될 수 있습니다.
활성 SSH 연결은 따라서 다음과 같습니다:
┌ ─ NIO Channel ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ ┌────────────────────────────────┐ │
│ │
│ │ │ │
│ │
│ │ │ │
│ │
│ │ NIOSSHHandler │───────────────────────┐
│ │ │ │ │
│ │ │
│ │ │ │ │
│ │ │
│ └────────────────────────────────┘ │ │
│
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
│
│
│
│
▼
┌── SSH Child Channel ─────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ├───┐
│ │ │ │ │ │ │
│ │ │ │ │ │ ├───┐
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ User Handler │ │ User Handler │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ └────────────────────────────────┘ └────────────────────────────────┘ │ │ │
│ │ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
SSH 채널은 채널 유형으로 호출됩니다. NIOSSH는 세 가지 유형을 지원합니다: session, directTCPIP, forwardedTCPIP. 가장 일반적인 채널 유형은 session입니다. session은 특정 명명된 프로그램이나 셸 등 프로그램 호출을 나타내는 데 사용됩니다. 다른 두 채널 유형은 TCP 포트 포워딩과 관련이 있으며, 나중에 논의됩니다.
SSH 채널은 단일 데이터 유형인 SSHChannelData로 작동합니다. 이 구조는 SSH가 일반 채널 데이터와 '확장' 채널 데이터를 모두 지원한다는 사실을 캡슐화합니다. 일반 채널 데이터(SSHChannelData.DataType.channel)는 대부분의 핵심 데이터에 사용됩니다. session 채널에서 .channel 데이터 유형은 표준 입력과 표준 출력에 사용되고, .stdErr 데이터 유형은 표준 오류에 사용됩니다(당연히). TCP 포워딩 채널에서는 .channel 데이터 유형만 사용되며 포워딩된 데이터를 나타냅니다.
session 채널은 명령의 호출을 나타냅니다. 채널이 정확히 어떻게 작동하는지는 여러 인바운드 사용자 이벤트를 통해 전달됩니다. 다음 이벤트가 중요합니다:
SSHChannelRequestEvent.PseudoTerminalRequest: 의사 터미널 할당을 요청합니다.SSHChannelRequestEvent.EnvironmentRequest: 명령 호출을 위한 단일 환경 변수를 요청합니다. 항상 명령 자체보다 먼저 전송됩니다.SSHChannelRequestEvent.ShellRequest: 호출할 명령이 인증된 사용자의 셸이도록 요청합니다.SSHChannelRequestEvent.ExecRequest: 특정 명령의 호출을 요청합니다.SSHChannelRequestEvent.ExitStatus: 원격 명령이 종료되었음을 알리고 종료 코드를 전달합니다.SSHChannelRequestEvent.ExitSignal: 원격 명령이 신호에 응답하여 종료되었음을 나타내고 해당 신호가 무엇인지 전달합니다.SSHChannelRequestEvent.SignalRequest: 원격 명령에 신호를 보내는 데 사용됩니다.SSHChannelRequestEvent.LocalFlowControlRequest: 클라이언트가 Ctrl-Q/Ctrl-S 흐름 제어를 자체적으로 수행할 수 있는지 여부를 나타내는 데 사용됩니다.SSHChannelRequestEvent.WindowChangeRequest: 클라이언트의 터미널 창 크기 변경을 할당된 의사 터미널에 전달하는 데 사용됩니다.SSHChannelRequestEvent.SubsystemRequest: 특정 하위 시스템의 호출을 요청하는 데 사용됩니다. 이 의미는 개별 사용 사례에 따라 다릅니다.이 이벤트는 포트 포워딩 메시지에서 사용되지 않습니다. .session 유형 채널을 지원하는 SSH 구현은 다양한 방식으로 이러한 이벤트의 대부분 또는 전체를 처리할 준비가 되어 있어야 합니다.
이러한 각 이벤트에는 wantReply 필드도 있습니다. 이는 요청이 성공 또는 실패를 나타내는 응답이 필요한지 여부를 나타냅니다. 필요한 경우 다음 두 이벤트가 사용됩니다:
ChannelSuccessEvent, 성공을 전달합니다.ChannelFailureEvent, 실패를 전달합니다.SSH 네트워크 프로토콜은 하위 채널에서 반 폐쇄를 광범위하게 사용합니다. NIO Channel은 일반적으로 기본적으로 반 폐쇄 지원이 비활성화되어 있으며, SwiftNIO SSH도 하위 채널에서 이 기본값을 따릅니다. 그러나 이 설정을 기본값으로 두면 SSH 하위 채널이 매우 예기치 않게 동작합니다. 이러한 이유로 모든 하위 채널에서 반 폐쇄 지원을 활성화하는 것이 강력히 권장됩니다:
channel.setOption(ChannelOptions.allowRemoteHalfClosure, true)
그러면 표준 NIO 반 폐쇄 지원이 사용됩니다. 원격 피어가 EOF를 보내면 인바운드 사용자 이벤트인 ChannelEvent.inputClosed로 전달됩니다. 직접 EOF를 보내려면 close(mode: .output)를 호출하십시오.
사용자 인증은 SSH의 중요한 부분입니다. 이를 관리하기 위해 SwiftNIO SSH는 두 개의 델리게이트 프로토콜인 NIOSSHClientUserAuthenticationDelegate와 NIOSSHServerUserAuthenticationDelegate를 사용합니다. 클라이언트와 서버는 사용자 인증을 관리하기 위해 이러한 델리게이트 프로토콜의 구현을 제공해야 합니다.
클라이언트 프로토콜은 간단합니다. SwiftNIO SSH는 델리게이트에서 nextAuthenticationType(availableMethods:nextChallengePromise:) 메서드를 호출합니다. availableMethods는 서버가 허용 가능하다고 제안한 인증 방법을 전달하는 NIOSSHAvailableUserAuthenticationMethods의 인스턴스입니다. 델리게이트는 nextChallengePromise를 새 인증 요청으로 완료하거나, 클라이언트가 시도할 것이 없음을 나타내기 위해 nil로 완료할 수 있습니다.
서버 프로토콜은 더 복잡합니다. 델리게이트는 델리게이트가 지원하는 인증 방법을 전달하는 supportedAuthenticationMethods 속성을 제공해야 합니다. 그런 다음 클라이언트가 사용자 인증 요청을 보낼 때마다 requestReceived(request:responsePromise:) 메서드가 호출됩니다. 클라이언트가 병렬로 인증 요청을 보낼 수 있으므로 이 메서드는 병렬로 여러 번 호출될 수 있습니다. responsePromise는 인증 결과로 성공해야 합니다. 세 가지 결과가 있습니다: .success와 .failure는 간단하지만, 서버는 원칙적으로 .partialSuccess(remainingMethods:)를 사용하여 여러 도전을 요구할 수 있습니다.
직접 포트 포워딩은 클라이언트에서 서버로의 포트 포워딩입니다. 이 모드에서 전통적으로 클라이언트는 로컬 포트에서 수신 대기하고 인바운드 연결을 서버로 포워딩합니다. 서버에 이러한 연결을 특정 호스트 및 포트로의 아웃바운드 연결로 포워딩하도록 요청합니다.
이러한 채널은 .directTCPIP 채널 유형을 사용하여 클라이언트가 직접 열 수 있습니다.
원격 포트 포워딩은 덜 일반적인 상황으로, 클라이언트가 서버에 특정 주소와 포트에서 수신 대기하고 모든 인바운드 연결을 클라이언트로 포워딩하도록 요청합니다. 클라이언트가 이 동작을 요청해야 하므로 글로벌 요청을 사용하여 요청합니다.
글로벌 요청은 NIOSSHHandler.sendGlobalRequest를 사용하여 시작되며, GlobalRequestDelegate를 통해 수신 및 처리됩니다. 현재 지원되는 글로벌 요청은 두 가지입니다:
GlobalRequest.TCPForwardingRequest.listen(host:port:): 특정 호스트와 포트에서 수신 대기하도록 서버에 요청합니다.GlobalRequest.TCPForwardingRequest.cancel(host:port:): 특정 호스트와 포트에서 수신 대기를 취소하도록 요청합니다.서버는 GlobalRequestDelegate를 사용하여 이러한 요청을 통지받고 응답할 수 있습니다. 여기서 구현할 메서드는 tcpForwardingRequest(_:handler:promise:)입니다. 이 델리게이트 메서드는 글로벌 요청이 수신될 때마다 호출됩니다. 요청에 대한 응답은 promise에 전달됩니다.
그런 다음 포워딩된 채널은 .forwardedTCPIP 채널 유형을 사용하여 서버에서 클라이언트로 전송됩니다.
| SwiftNIO SSH | 최소 Swift 버전 |
|---|
0.0.0 ..< 0.3.0 | 5.1 |
0.3.0 ..< 0.4.0 | 5.2 |
0.4.0 ..< 0.5.0 | 5.4 |
0.5.0 ..< 0.6.2 | 5.5.2 |
0.6.2 ..< 0.9.0 | 5.6 |
0.9.0 ..< 0.9.2 | 5.8 |
0.9.2 ..< 0.10.0 | 5.9 |
0.10.0 ... 0.12.0 | 5.10 |
0.12.0 ..< 0.13.0 | 6.0 |
0.13.0 ..< | 6.1 |