Golang gRPC with mutual TLS
개요
본 문서는 Golang언어로 gRPC 통신에 사용하기 위한 mTLS를 구축하는 예시 코드를 기술한다. mTLS는 mutual TLS의 약어로, 서버-클라이언트 간의 양방향 인증을 수행하는 방식을 말한다.
준비사항
mTLS를 구현하기 위해서는 다음의 인증서를 준비해야 한다.
rootCA.crt
: 루트 인증서server.crt
,server.key
: 루트의 개인키로 서명된 서버용 인증서client.crt
,client.key
: 루트의 개인키로 서명된 클라이언트용 인증서
본 문서에서는 인증서를 생성하기 위한 방법에 대해서는 기술하지 않고, 만들어진 인증서를 사용하여 코드를 작성하는 부분을 기술한다.
Source Code
Server Source
func main(){
port := 40000
ip := "0.0.0.0"
rootCAFile := "./test/certs/rootCa.crt"
certFile := "./test/certs/server.crt"
keyFile := "./test/certs/server.key"
// read keypair
x509KeyPair, err := tls.LoadX509KeyPair(certFile, keyFile)
// read rootCA
rootCACertData, err := os.ReadFile(rootCAFile)
if err != nil {
log.Fatal(err)
}
// create cert pool
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(rootCACertData)
// create cert pool
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(rootCACertData)
// create tls config
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{x509KeyPair},
ClientCAs: certPool,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
},
ClientAuth: tls.RequireAndVerifyClientCert,
}
tlsCredentials := credentials.NewTLS(tlsConfig)
// create listener
serverAddress := fmt.Sprintf("%s:%d", ip, port)
lis, err := net.Listen("tcp", serverAddress)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// create server
grpcServer := grpc.NewServer(grpc.Creds(tlsCredentials))
// register service
// pma.NewEventListenerService()는 gRPC로 구현된 서비스 객체이다.
pm.RegisterPmAgentEventServiceServer(grpcServer, pma.NewEventListenerService())
fmt.Printf("PM Server Start listen...%s\n", serverAddress)
// start server
grpcServer.Serve(lis)
}
Client Source
func main(){
serverPort := 40000
serverIp := "127.0.0.1"
rootCAFile := "./test/certs/rootCa.crt"
certFile := "./test/certs/client.crt"
keyFile := "./test/certs/client.key"
// read keypair
x509KeyPair, err := tls.LoadX509KeyPair(certFile, keyFile)
// read rootCA
rootCACertData, err := os.ReadFile(rootCAFile)
if err != nil {
log.Fatal(err)
}
// create cert pool
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(rootCACertData)
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: certPool,
ServerName: "server", // server.crt의 cn 값 혹은 SAN에 해당하는 값을 넣는다.
}
tlsCredential := credentials.NewTLS(tlsConfig)
// keep alive time
keepaliveClientParams := keepalive.ClientParameters{
Time: 10 * time.Second, // send pings every 10 seconds if there is no activity
Timeout: 1 * time.Second, // wait 1 second for ping ack before considering the connection dead
PermitWithoutStream: true, // send pings even without active streams
}
// tls
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(tlsCredential))
opts = append(opts, grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"pick_first"}`))
opts = append(opts, grpc.WithKeepaliveParams(keepaliveClientParams))
conn, err := grpc.NewClient(fmt.Sprintf("%s:%d", serverIp, serverPort), opts...)
if err != nil {
log.Fatal(err)
}
// TODO: something with conn
....
}
암호가 있는 개인키의 경우
import (
"github.com/youmark/pkcs8"
)
// Load client clientKey
keyPEMBlock, err := os.ReadFile(keyFile)
if err != nil {
return err
}
block, _ := pem.Decode(keyPEMBlock)
if block == nil {
return errors.New(fmt.Sprintf("Pem Decode Error"))
}
const passPhrase = "this is passphrase"
privateKey, err := pkcs8.ParsePKCS8PrivateKeyRSA(block.Bytes, []byte(passPhrase))
if err != nil{
return err
}
decryptedPrivatePem := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: decryptedPrivatePem,
}
// encode private key to pem
var buf bytes.Buffer
err = pem.Encode(&buf, privateKeyPEM)
if err != nil {
return err
}
// Load client clientCert
certPEMBlock, err := os.Readfile(certFile)
if err != nil {
return err
}
// set TLS key pair
clientCertKeyPair, err := tls.X509KeyPair(certPEMBlock, buf.Bytes())
// use clientCertKeyPair
Last updated