Виммельбух, 3, перетяжка
Виммельбух, 3, перетяжка
Виммельбух, 3, перетяжка

Поднимаем TLS для gRPC в Go

Аватарка пользователя Kirill Parasotchenko
Отредактировано

В этой статье мы рассмотрим, как поднять gRPC сервер и клиент для него на Go, если у вас валидный сертификат или самоподписанный сертификат.

9К открытий43К показов
Поднимаем TLS для gRPC в Go

В этой статье мы рассмотрим, как поднять gRPC сервер и клиент для него на Go, если у вас самоподписанный сертификат. Кстати, если вы опытный тестировщик и хотите присоединиться к команде, не пропустите вакансию Fullstack QA в Сбер.

Для начала мы обсудим, как поднять gRPC сервер и клиент без шифрования. Затем посмотрим, как мог бы выглядеть код клиента, если сервер использует сертификат, подписанный доверенным CA.

В конце я расскажу, что можно сделать, если вы хотите поднять server и client с TLS, если у вас самоподписанный сертификат.

Insecure connection

Давайте начнём с опции, когда вам не нужно шифрование. Допустим, server и client находятся в одном закрытом кластере и кроме них там никого нет. На самом деле, даже в этом случае я рекомендую поднять TLS, но давайте рассмотрим такую опцию.

Курс по Go-разработке от Kata Academy
  • постоянный доступ
  • бесплатно
  • онлайн
tproger.ru

Начнём с сервера, и тут всё просто: если нам не нужен TLS, то мы просто нигде не должны его настраивать.

			listener, err := net.Listen("tcp", ":50051")
if err != nil {
   log.Fatalf("net.Listen error: %v", err)
}

s := grpc.NewServer()

pb.RegisterGreeterServer(s, &server{})

log.Fatal(s.Serve(listener))

		

С клиентом всё немного сложнее: если мы не хотим использовать TLS, то при создании gRPC соединения нам это явно нужно указать опцией grpc.WithTransportCredentials(insecure.NewCredentials()).

			var (
   ctx  = context.Background()
   addr = "localhost:50051"
)

conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
   log.Fatal(err)
}

client := pb.NewGreeterClient(conn)
		

Secure connection

Что, если опция незащищённого соединения нам не подходит?

Давайте для начала вспомним, как работает TLS. Одним из шагов TLS рукопожатия является отправка от сервера его сертификата. Получив сертификат, клиент должен проверить его подпись. То есть получив сертификат клиент смотрит на подпись сертификата и проверяет, что эта подпись создана одним из доверенных CA. Список доверенных CA есть в системе. 

Так происходит например при открытии любой https страницы в браузере.

Давайте теперь вернёмся к нашим gRPC серверу и клиенту. Представим, что наш сервер использует сертификат, подписанный доверенным CA, как научить нашего клиента распознавать это?

На первом шаге, когда мы создавали незащищённое соединение, мы указали опцию insecure для transport credentials. Без этой опции клиент не поднялся бы. Для защищённого соединения нам тоже нужно будет указать опцию transport credentials, но передать в неё системные CA. В Go это можно сделать следующим образом:

			import (
  "crypto/tls"
  "crypto/x509"

  "google.golang.org/grpc/credentials"
)

func generateTLSCreds() (credentials.TransportCredentials, error) {
   systemRoots, err := x509.SystemCertPool()
   if err != nil {
      return nil, err
   }


   return credentials.NewTLS(&tls.Config{
      RootCAs: systemRoots,
   }), nil
}

		
			tlsCreds, err := generateTLSCreds()
if err != nil {
   log.Fatal(err)
}

		

Теперь вместо grpc.WithTransportCredentials(insecure.NewCredentials()) нам нужно указать grpc.WithTransportCredentials(tlsCreds)

Так клиент научится верифицировать подпись серверного сертификата системными CA.

Secure connection + self-signed certificate

Давайте теперь подумаем, что нам делать, если мы хотим использовать самоподписанный сертификат на сервере. Если мы поднимем сервер с самоподписанным сертификатом и оставим код клиента, как мы сделали на предыдущем шаге, то ничего работать не будет, так как на этапе проверки подписи серверного сертификата на стороне клиента клиент посчитает сертификат невалидным, так как он подписанным неизвестным ему CA. В итоге TLS рукопожатие не пройдёт и соединение установлено не будет. 

Курс по Go-разработке от Kata Academy
  • постоянный доступ
  • бесплатно
  • онлайн
tproger.ru

Давайте попробуем это исправить, но для начала научимся генерировать самоподписанные сертификаты.

Воспользуемся openssl, чтобы сгенерировать CA сертификат и серверный сертификат.

			openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=Acme Root CA" -out ca.crt
openssl req -newkey rsa:2048 -nodes -keyout server.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=localhost" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:localhost") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
		

На выходе у нас должно получиться 6 файлов:

ca.crt, ca.key, ca.srl, server.crt, server.csr, server.key

Хорошо, теперь давайте поднимем сервер с этим сертификатом.

			func generateTLSCreds() (credentials.TransportCredentials, error) {
   // Здесь нужно указать полные пути к файлам
   certFile := "server.crt"
   keyFile := "server.key"


   return credentials.NewServerTLSFromFile(certFile, keyFile)
}

		
			listener, err := net.Listen("tcp", ":50051")
if err != nil {
   log.Fatalf("failed to listen: %v", err)
}


tlsCreds, err := generateTLSCreds()
if err != nil {
   log.Fatalf("failed to generate tls creds: %v", err)
}


s := grpc.NewServer(grpc.Creds(tlsCreds))
		

Следующим шагом нам нужно научить клиента верифицировать подпись самоподписанного сертификата, то есть добавить наш CA в список его доверенных CA. На стороне клиента нужно сделать следующее:

			func generateTLSCreds() (credentials.TransportCredentials, error) {
   // Здесь нужно указать полный путь к файлу
   certFile := "ca.crt"

   return credentials.NewClientTLSFromFile(certFile, "")
}

		
			var (
   ctx  = context.Background()
   addr = "localhost:50051"
)


tlsCreds, err := generateTLSCreds()
if err != nil {
   log.Fatal(err)
}


conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(tlsCreds))
if err != nil {
   log.Fatal(err)
}

		

Теперь клиент и сервер могут общаться по TLS соединению с самоподписанным сертификатом.

Заключение

В этой статье мы рассмотрели три опции настройки TLS между сервером и клиентом в Go: незащищённое соединение, соединение с сертификатом, подписанным доверенным CA и соединение с самоподписанным сертификатом.

Следите за новыми постами
Следите за новыми постами по любимым темам
9К открытий43К показов