Go에서 SSL/TLS를 사용하여 gRPC 연결을 보호하는 방법
그러나 우리가 사용자에게 로그인하는 API는 안전하지 않다. 이것은 사용자 이름과 비밀번호가 명문으로 발송되고 클라이언트와 서버 간의 통신을 탐지하는 모든 사람이 읽을 수 있다는 것을 의미한다.
따라서 오늘 TLS를 사용하여 gRPC 연결을 보호하는 방법을 배울 것입니다.
안 읽어봤으면계속하기 전에 TLS를 자세히 이해하기 위해 먼저 읽으시기 바랍니다.
연결
Github 저장소: pcbook-go 및 pcbook-java
Gitlab 저장소: pcbook-go 및 pcbook-java
gRPC 연결 유형
gRPC 연결에는 다음과 같은 3가지 유형이 있습니다.
TLS 인증서 생성
우선 TLS 인증서를 생성하기 위해
cert/gen.sh
스크립트를 작성합니다.rm *.pem
# 1. Generate CA's private key and self-signed certificate
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/C=FR/ST=Occitanie/L=Toulouse/O=Tech School/OU=Education/CN=*.techschool.guru/[email protected]"
echo "CA's self-signed certificate"
openssl x509 -in ca-cert.pem -noout -text
# 2. Generate web server's private key and certificate signing request (CSR)
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=FR/ST=Ile de France/L=Paris/O=PC Book/OU=Computer/CN=*.pcbook.com/[email protected]"
# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf
echo "Server's signed certificate"
openssl x509 -in server-cert.pem -noout -text
나는 너희들이 이 각본이 어떻게 작동하는지 이해하기 위해 읽도록 격려한다.기본적으로 이 스크립트는 세 가지 부분을 포함한다.
cd
cert 폴더에 가서 gen.sh
을 실행하고 이 폴더를 떠나기만 하면 됩니다....
cert:
cd cert; ./gen.sh; cd ..
.PHONY: gen clean server client test cert
지금 우리 대합실에서 한번 해 봅시다.make cert
모든 파일을 성공적으로 다시 생성했습니다.다음으로, 서버 측 TLS와의 gRPC 연결을 보호하는 방법을 보여 드리겠습니다.
서버 측 TLS 구현
cmd/server/main.go
파일을 엽니다.TLS 자격 증명을 로드하는 함수를 추가합니다.객체TranportCredentials
또는 error
로 돌아갑니다.func loadTLSCredentials() (credentials.TransportCredentials, error) {
// Load server's certificate and private key
serverCert, err := tls.LoadX509KeyPair("cert/server-cert.pem", "cert/server-key.pem")
if err != nil {
return nil, err
}
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.NoClientCert,
}
return credentials.NewTLS(config), nil
}
서버 측 TLS에 대해 서버의 인증서와 개인 키를 불러와야 합니다.따라서 tls.LoadX509KeyPair()
함수를 사용하여 server-cert.pem
폴더에서 server-key.pem
및 cert
파일을 로드합니다.오류가 있으면 되돌려줍니다.그렇지 않으면 전송 자격 증명이 생성됩니다.우리는 서버 인증서로
tls.Config
대상을 만들고 ClientAuth
필드를 NoClientCert
로 설정합니다. 왜냐하면 우리는 서버 측 TLS만 사용하기 때문입니다.마지막으로, 우리는 이 설정 호출 credentials.NewTLS()
을 사용하고 호출자에게 되돌려줍니다.main()
함수에서 TLS 자격 증명 객체를 가져오려면 loadTLSCredentials()
을 호출합니다.만약 오류가 발생한다면, 우리는 치명적인 일지 하나만 쓸 것이다.그렇지 않으면 TLS 자격 증명을 gRPC 서버에 추가하기 위해 grpc.Creds()
옵션을 사용합니다.func main() {
...
tlsCredentials, err := loadTLSCredentials()
if err != nil {
log.Fatal("cannot load TLS credentials: ", err)
}
grpcServer := grpc.NewServer(
grpc.Creds(tlsCredentials),
grpc.UnaryInterceptor(interceptor.Unary()),
grpc.StreamInterceptor(interceptor.Stream()),
)
...
}
서버가 그렇습니다.종점에서 운행합시다.make server
서버가 시작되었습니다.클라이언트를 실행합니다.make client
클라이언트에서 TLS를 사용하지 않았기 때문에 실패했습니다.그럼 우리 이렇게 하자!
우리가 서버에서 한 것과 유사하게, 나는 파일에서 TLS 자격 증명을 불러오는 함수를 추가했다.하지만 이번에는 서버 인증서에 서명한 CA의 인증서만 불러옵니다.
클라이언트가 서버에서 얻은 인증서의 진실성을 검증하여 대화하고자 하는 정확한 서버임을 확보해야 하기 때문이다.
func loadTLSCredentials() (credentials.TransportCredentials, error) {
// Load certificate of the CA who signed server's certificate
pemServerCA, err := ioutil.ReadFile("cert/ca-cert.pem")
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(pemServerCA) {
return nil, fmt.Errorf("failed to add server CA's certificate")
}
// Create the credentials and return it
config := &tls.Config{
RootCAs: certPool,
}
return credentials.NewTLS(config), nil
}
따라서, 우리는 여기에 ca-cert.pem
파일을 불러온 다음, 새로운 x509 인증서 탱크를 만들고, CA의pem를 이 탱크에 추가합니다.마지막으로, 우리는 증빙서류를 만들고 되돌려줍니다.신뢰할 수 있는 CA 인증서가 포함된 RootCAs 필드만 설정하면 됩니다.현재
main()
함수에서 두 개의 연결이 여전히 안전하지 않습니다.보안 TLS로 교체해야 합니다.자격 증명 대상을 얻기 위해
loadTLSCredentials()
를 호출합시다.그리고 grpc.WithInsecure()
호출을 grpc.WithTransportCredentials()
로 변경하고 TLS 자격 증명 객체로 전송합니다.func main() {
...
tlsCredentials, err := loadTLSCredentials()
if err != nil {
log.Fatal("cannot load TLS credentials: ", err)
}
cc1, err := grpc.Dial(*serverAddress, grpc.WithTransportCredentials(tlsCredentials))
if err != nil {
log.Fatal("cannot dial server: ", err)
}
...
cc2, err := grpc.Dial(
*serverAddress,
grpc.WithTransportCredentials(tlsCredentials),
grpc.WithUnaryInterceptor(interceptor.Unary()),
grpc.WithStreamInterceptor(interceptor.Stream()),
)
if err != nil {
log.Fatal("cannot dial server: ", err)
}
...
}
우리는 완성했다.한번 해보자!이번 요청이 서버에 성공적으로 전송되었습니다.완벽했어
주제 재지정 이름(SAN)
나는 여기서 너희들에게 한 가지 일을 보여주고 싶다.로컬 호스트에서 개발할 때 중요한 것은
IP:0.0.0.0
을 테마 대체 이름(SAN) 확장으로 인증서에 추가하는 것입니다.subjectAltName=DNS:*.pcbook.com,DNS:*.pcbook.org,IP:0.0.0.0
server-ext.cnf
프로필에서 삭제하면 어떻게 되는지 봅시다.subjectAltName=DNS:*.pcbook.com,DNS:*.pcbook.org
그리고 인증서를 다시 생성하고 서버를 다시 시작하고 클라이언트를 다시 실행합니다.make cert
make server
make client
보시다시피 SAN은 이 IP 주소를 포함하지 않기 때문에 TLS가 인증서를 검증할 수 없기 때문에 악수에 실패했습니다
0.0.0.0
.생산적으로, 이것은 가능하다. 왜냐하면 우리는 도메인 이름을 대체하기 때문이다.
네, 이제 gRPC 연결을 위해 서버 측 TLS를 사용하는 방법을 알게 되었습니다.상호 TLS를 사용하는 방법을 배워봅시다!
상호 TLS 구현
현재 서버는 클라이언트와 인증서를 공유했습니다.상호 TLS의 경우 클라이언트가 인증서를 서버와 공유해야 합니다.이제 이
cert/gen.sh
스크립트를 업데이트하여 클라이언트를 위해 인증서를 만들고 서명합시다....
# 4. Generate client's private key and certificate signing request (CSR)
openssl req -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=FR/ST=Alsace/L=Strasbourg/O=PC Client/OU=Computer/CN=*.pcclient.com/[email protected]"
# 5. Use CA's private key to sign client's CSR and get back the signed certificate
openssl x509 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.cnf
echo "Client's signed certificate"
openssl x509 -in client-cert.pem -noout -text
이 강좌에서 동일한 CA를 사용하여 서버와 클라이언트의 인증서에 서명한다고 가정합니다.현실 세계에서 우리는 여러 개의 클라이언트가 있을 수 있는데, 그것들은 서로 다른 CA가 서명한 서로 다른 인증서를 가지고 있다.이제 인증서를 다시 생성합시다.
make cert
네, 클라이언트의 인증서와 개인 키가 준비되었습니다.상호 TLS를 사용하려면 서버 측 cmd/server/main.go
에서 ClientAuth
으로 필드를 변경해야 합니다.func loadTLSCredentials() (credentials.TransportCredentials, error) {
...
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
}
return credentials.NewTLS(config), nil
}
또한 고객 인증서에 서명한 신뢰할 수 있는 CA의 인증서 목록을 제공해야 합니다.우리의 예에서 서버와 클라이언트의 인증서에 서명하는 CA는 하나밖에 없기 때문에 우리는 클라이언트에서 작성한 코드를 간단하게 복사해서 CA의 인증서를 불러오고 새로운 인증서 탱크를 만들 수 있다.
그리고 클라이언트 인증서에 서명한 CA가 되어야 한다는 사실을 반영하기 위해 변수 이름과 오류 메시지를 조금만 업데이트하면 됩니다.
func loadTLSCredentials() (credentials.TransportCredentials, error) {
// Load certificate of the CA who signed client's certificate
pemClientCA, err := ioutil.ReadFile("cert/ca-cert.pem")
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(pemClientCA) {
return nil, fmt.Errorf("failed to add client CA's certificate")
}
// Load server's certificate and private key
serverCert, err := tls.LoadX509KeyPair("cert/server-cert.pem", "cert/server-key.pem")
if err != nil {
return nil, err
}
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
}
return credentials.NewTLS(config), nil
}
서버가 완료되었습니다.종점에서 운행합시다.make server
현재 클라이언트를 이 새 서버에 연결하면 서버에서 인증서를 보내야 하기 때문에 실패합니다.이 문제를 해결하기 위해 클라이언트 코드
tls.NoClientCert
로 넘어갑시다.서버에 인증서를 불러오기 위해 코드만 복사하고 파일 이름을 tls.RequireAndVerifyClientCert
및 cmd/client/main.go
로 변경합니다.그리고 서버 쪽에서 한 것처럼 설정
client-cert.pem
필드를 통해 클라이언트 인증서를 TLS 설정에 추가해야 합니다.func loadTLSCredentials() (credentials.TransportCredentials, error) {
// Load certificate of the CA who signed server's certificate
pemServerCA, err := ioutil.ReadFile("cert/ca-cert.pem")
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(pemServerCA) {
return nil, fmt.Errorf("failed to add server CA's certificate")
}
// Load client's certificate and private key
clientCert, err := tls.LoadX509KeyPair("cert/client-cert.pem", "cert/client-key.pem")
if err != nil {
return nil, err
}
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: certPool,
}
return credentials.NewTLS(config), nil
}
알겠습니다. 지금 우리가 클라이언트를 다시 실행하면 모든 요청이 성공할 것입니다.경탄했어
개인 키 암호화
우리가 끝나기 전에 마지막 일이 있어.알다시피, 우리가 사용하고 있는 현재 클라이언트와 서버의 개인 키는 암호화되지 않았습니다.이것은 우리가 그것들을 생성할 때
client-key.pem
옵션을 사용했기 때문이다.openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=FR/ST=Ile de France/L=Paris/O=PC Book/OU=Computer/CN=*.pcbook.com/[email protected]"
이 Certificates
옵션을 삭제하고 실행-nodes
하면 서버의 개인 키를 암호화하기 위한 암호를 요청받을 것입니다.생성된 서버 개인 키가 암호화되었습니다.이 키를 사용하여 서버를 시작하려고 하면 오류가 발생합니다. TLS 자격 증명을 로드할 수 없습니다.이것은 키가 암호화되어 있기 때문이다.
우리는 암호로 키를 복호화하는 데 더 많은 코드를 추가할 수 있지만, 나는 최종적으로, 암호를 안전한 곳에 두어야 한다고 생각한다.따라서 암호화되지 않은 키를 이 위치에 저장할 수도 있습니다.
예를 들어,amazon 웹 서비스를 사용하면awssecret 관리자를 사용하여 암호화 형식으로 개인 키나 다른 기밀을 저장할 수 있습니다.또는 HashiCorp의 Vault를 사용하여 동일한 목적을 달성할 수도 있습니다.
이것이 바로 제가 본문에서 여러분과 나누고 싶은 모든 것입니다.나는 네가 그것이 유용하다고 생각하길 바란다.읽어주셔서 감사합니다. 다음 편에서 뵙겠습니다.
만약 당신이 이 글을 좋아한다면, subscribe to our Youtube channel 이후의 더 많은 강좌를 보십시오.
만약 당신이 내가 현재 복도교에 있는 신기한 팀에 가입하고 싶다면 our job openings here 를 보십시오.파리/암스테르담/런던/베를린/바르셀로나에서 원격 또는 현장 비자 보증을 합니다.
Reference
이 문제에 관하여(Go에서 SSL/TLS를 사용하여 gRPC 연결을 보호하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/techschoolguru/how-to-secure-grpc-connection-with-ssl-tls-in-go-4ph텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)