Go

랭(Golang)에서 로컬 SSH 설정 파일 읽어 원격 서버 접속하기

드리프트2 2025. 5. 6. 13:36

고랭(Golang): 로컬 SSH 설정 읽어 원격 서버에 접속하기

SSH를 통해 원격 서버에 접속해야 하는 고랭(Go) 애플리케이션(application)을 개발할 때, 로컬 SSH 설정을 활용하면 전체 과정을 훨씬 간소화할 수 있습니다.

이 접근 방식을 사용하면 여러분의 고랭(Go) 애플리케이션(application)이 미리 정의된 연결 설정을 이용할 수 있게 되어, 일관성과 효율성을 높일 수 있습니다.

핵심 요약

  • 고랭(Go) 애플리케이션(application)은 ~/.ssh/config 파일을 활용하여 SSH 연결을 간소화할 수 있습니다.

  • github.com/kevinburke/ssh_config 패키지(package)는 고랭(Go)에서 SSH 설정 파일을 파싱(parsing)하는 데 도움을 줍니다.

  • golang.org/x/crypto/ssh 패키지(package)는 고랭(Go)에서 SSH 클라이언트(client) 구현을 가능하게 합니다.

로컬 SSH 설정 파일 이해하기

SSH 클라이언트(client) 설정 파일은 일반적으로 ~/.ssh/config 경로에 위치하며, 사용자가 다양한 호스트(host)에 대한 연결 파라미터(parameter)를 정의할 수 있게 해줍니다.

이 파일의 표준 항목은 다음과 같은 형태일 수 있습니다.

Host example
    HostName example.com
    User your-username
    Port 22
    IdentityFile ~/.ssh/id_rsa

 
이 설정에서 각 항목의 의미는 다음과 같습니다.

  • Host: 호스트(host)의 별칭입니다.

    SSH 명령어에서 단축키처럼 사용됩니다.

  • HostName: 원격 서버의 실제 도메인(domain) 또는 IP 주소입니다.

  • User: SSH 연결에 사용할 사용자 이름입니다.

  • Port: SSH 서비스가 실행 중인 포트(port)입니다 (기본값은 22).

  • IdentityFile: 인증에 사용할 개인 키(private key) 파일의 경로입니다.

이러한 설정을 구성해두면, 다음과 같은 간단한 명령어로 원격 서버에 접속할 수 있습니다.

ssh example

 

고랭(Go)에서 SSH 설정 통합하기

여러분의 고랭(Go) 애플리케이션(application)이 이러한 SSH 설정을 읽고 활용하게 하려면, 다음 단계를 따르십시오.

  1. SSH 설정 파일 읽기: ~/.ssh/config 파일을 열고 파싱(parsing)하여 필요한 호스트(host) 설정을 검색합니다.

  2. 호스트 정보 추출: 애플리케이션(application)이 연결해야 하는 특정 호스트(host) 항목을 식별하고 HostName, User, Port, IdentityFile과 같은 파라미터(parameter)들을 추출합니다.

  3. 고랭(Go)에서 SSH 클라이언트 설정: 추출된 파라미터(parameter)들을 사용하여 고랭(Go) 애플리케이션(application) 내에서 SSH 클라이언트(client)를 설정합니다.

고랭(Go)에서 솔루션 구현하기

고랭(Go)의 golang.org/x/crypto/ssh 패키지(package)는 SSH 클라이언트(client) 구현을 용이하게 합니다.

하지만 이 패키지(package)는 기본적으로 SSH 설정 파일을 파싱(parsing)하는 기능을 제공하지 않습니다.

이 간극을 메우기 위해, SSH 설정 파일 파싱(parsing) 기능을 제공하는 github.com/kevinburke/ssh_config 패키지(package)를 사용할 수 있습니다.

먼저, 패키지(package)를 설치합니다.

go get github.com/kevinburke/ssh_config

 
그런 다음, SSH 연결을 설정하기 위해 다음 코드를 구현합니다.

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"

    "github.com/kevinburke/ssh_config" // SSH 설정 파일 파싱 라이브러리
    "golang.org/x/crypto/ssh"         // SSH 클라이언트 라이브러리
)

func main() {
    host := "example" // 여러분의 SSH 설정 파일에 정의된 별칭(alias)

    // SSH 설정 파일 경로 찾기
    homeDir, err := os.UserHomeDir()
    if err != nil {
        log.Fatalf("사용자 홈 디렉토리를 찾을 수 없습니다: %v", err)
    }
    sshConfigPath := filepath.Join(homeDir, ".ssh", "config")

    // SSH 설정 파일 열고 파싱하기
    configFile, err := os.Open(sshConfigPath)
    if err != nil {
        log.Fatalf("SSH 설정 파일을 열 수 없습니다: %v", err)
    }
    defer configFile.Close() // 함수 종료 시 파일 닫기

    sshConfig, err := ssh_config.Decode(configFile)
    if err != nil {
        log.Fatalf("SSH 설정 파일을 파싱할 수 없습니다: %v", err)
    }

    // 해당 호스트에 대한 관련 설정 추출하기
    hostname := sshConfig.Get(host, "HostName")
    if hostname == "" {
        log.Fatalf("호스트 '%s'에 대한 HostName을 찾을 수 없습니다", host)
    }

    user := sshConfig.Get(host, "User")
    if user == "" {
        // 설정 파일에 User가 명시되지 않은 경우, 현재 시스템 사용자를 기본값으로 사용
        currentUser, err := os.UserCurrent()
        if err != nil {
            log.Fatalf("현재 사용자를 가져올 수 없습니다: %v", err)
        }
        user = currentUser.Username
    }

    port := sshConfig.Get(host, "Port")
    if port == "" {
        port = "22" // 기본 SSH 포트
    }

    identityFile := sshConfig.Get(host, "IdentityFile")
    if identityFile == "" {
        // 설정 파일에 IdentityFile이 명시되지 않은 경우, 기본 키 경로 사용
        identityFile = filepath.Join(homeDir, ".ssh", "id_rsa")
    }
    // 경로에 ~가 포함된 경우 확장 (예: ~/.ssh/id_rsa -> /home/user/.ssh/id_rsa)
    if identityFile[:2] == "~/" {
        identityFile = filepath.Join(homeDir, identityFile[2:])
    }


    // 개인 키 읽기
    key, err := ioutil.ReadFile(identityFile)
    if err != nil {
        log.Fatalf("개인 키를 읽을 수 없습니다 (%s): %v", identityFile, err)
    }

    // 개인 키로부터 Signer 생성
    signer, err := ssh.ParsePrivateKey(key)
    if err != nil {
        log.Fatalf("개인 키를 파싱할 수 없습니다: %v", err)
    }

    // SSH 클라이언트 설정 구성
    clientConfig := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            // 공개 키 인증 사용
            ssh.PublicKeys(signer),
        },
        // 중요: 실제 운영 환경에서는 호스트 키 검증을 구현해야 합니다.<br /><br />
        // 여기서는 예제 단순화를 위해 검증을 건너뜁니다.<br /><br />
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    // SSH 서버에 연결
    addr := fmt.Sprintf("%s:%s", hostname, port)
    connection, err := ssh.Dial("tcp", addr, clientConfig)
    if err != nil {
        log.Fatalf("SSH 서버에 연결할 수 없습니다 (%s): %v", addr, err)
    }
    defer connection.Close() // 함수 종료 시 연결 닫기

    log.Println("SSH 연결 성공!")

    // 이제 'connection'을 사용하여 원격 서버와 상호작용할 수 있습니다.<br /><br />
    // 예를 들어, 세션 열기:
    session, err := connection.NewSession()
    if err != nil {
        log.Fatalf("세션을 생성할 수 없습니다: %v", err)
    }
    defer session.Close() // 세션 사용 후 닫기

    // 원격 서버에서 명령어 실행
    output, err := session.CombinedOutput("uname -a") // 표준 출력과 표준 에러를 함께 가져옴
    if err != nil {
        log.Fatalf("명령어 실행 실패: %v", err)
    }
    fmt.Println("원격 서버 명령어 실행 결과:")
    fmt.Println(string(output))
}

 

자주 묻는 질문 (FAQs)

(원문에는 이 헤더만 있고 내용은 없었습니다.

필요하다면 관련 질문과 답변을 추가할 수 있습니다.

)

주요 고려 사항

HostKeyCallback

위 예제에서는 단순화를 위해 ssh.InsecureIgnoreHostKey()를 사용했으며, 이는 호스트 키 검증(host key verification)을 건너뜁니다.

실제 운영 환경 애플리케이션(application)에서는 보안을 보장하기 위해 적절한 호스트 키 검증(host key verification)을 구현해야 합니다.

(예: golang.org/x/crypto/ssh/knownhosts 패키지 사용)

오류 처리

SSH 설정 파일이 없거나, 특정 항목이 누락되었거나, 잘못된 정보가 포함된 시나리오를 관리하기 위해 견고한 오류 처리(error handling)를 보장해야 합니다.

보안

항상 개인 키(private key)를 안전하게 보호하고, 애플리케이션(application) 내에 민감한 정보를 하드코딩(hardcoding)하지 마십시오.

결론

고랭(Go) 애플리케이션(application)을 로컬 SSH 설정과 통합함으로써, 기존 설정을 활용하고 개발 환경 전반에 걸쳐 일관성을 유지하면서 SSH 연결을 보다 효율적으로 관리할 수 있습니다.