Herkese merhaba!

Uzun yıllardır bol miktarda kişisel zaman ve enerji harcayarak bilgimizi hepinizle paylaşıyoruz. Ancak şu andan itibaren bu blogu çalışır durumda tutabilmek için yardımınıza ihtiyacımız var. Yapmanız gereken tek şey, sitedeki reklamlardan birine tıklamak olacaktır, aksi takdirde hosting vb. masraflar nedeniyle maalesef yayından kaldırılacaktır. Teşekkürler.

Bu, burada mevcut olan yazının güncellenmiş sürümü. Golang v1.15 sertifikaların işlenme şeklini bozduğu için bu sürümü yayınlamak zorunda kaldım. Aşağıdaki hatayı alacaksınız.


transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0

Bu hatayı gidermek için sertifikaları oluşturma şeklimizi değiştirmemiz gerekiyor. Bunu yaparken sıfırdan tamamen yeni bir örnek de kullanacağım. Kısaca, istemci ve sunucu uygulamalarımız birbirleriyle iletişim kurmak için TLS kullanacaktır.


Yapı


├── Makefile
├── cert
│   ├── cert.conf
│   ├── server.crt
│   └── server.key
├── client
│   └── main.go
├── go.mod
├── hello.pb.go
├── hello.proto
├── message
│   ├── client.go
│   └── server.go
└── server
└── main.go

Sertifikalar


cert.conf dosyasının içeriği aşağıdaki gibidir.


[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = UK
ST = London
L = London
O = Inanzzz Ltd.
OU = Information Technologies
emailAddress = email@email.com
CN = localhost

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = localhost
DNS.2 = inanzzz.com
DNS.3 = www.inanzzz.com

Özel anahtarı ve sertifikayı oluşturalım.


$ openssl genrsa -out cert/server.key 2048

$ openssl req -nodes -new -x509 -sha256 -days 1825 -config cert/cert.conf -extensions 'req_ext' -key cert/server.key -out cert/server.crt

Burada önemli olan nokta subjectAltName özelliğidir. Golang v1.15, bu özellikle doğrudan ilgili olan SNA'ya ihtiyaç duyar. Ayrıntıları teyit edelim.


$ openssl x509 -in cert/server.crt -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
59:b3:15:04:02:f9:04:0f:77:b5:0c:c6:dd:d9:65:0f:06:2d:e5:cd
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = UK, ST = London, L = London, O = Inanzzz Ltd., OU = Information Technologies, emailAddress = email@email.com, CN = localhost
Validity
Not Before: Sep 13 19:13:52 2020 GMT
Not After : Sep 12 19:13:52 2025 GMT
Subject: C = UK, ST = London, L = London, O = Inanzzz Ltd., OU = Information Technologies, emailAddress = email@email.com, CN = localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (4096 bit)
Modulus:
00:a7:90:4e:47:5b:3a:87:20:55:c8:36:8d:a9:00:
............................................:
e9:56:3f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:localhost, DNS:inanzzz.com, DNS:www.inanzzz.com
Signature Algorithm: sha256WithRSAEncryption
9a:40:93:5a:20:6e:20:9f:14:70:c5:5b:96:ee:da:ac:22:98:
.....................................................:
44:fe:3d:31:03:53:b7:e1
-----BEGIN CERTIFICATE-----
MIIGADCCA+igAwIBAgIUWbMVBAL5BA93tQzG3dllDwYt5c0wDQYJKoZIhvcNAQEL
................................................................
mai+5Eck8n8uEJzQnIB4oDDb4eucoL26GLDa4YUGONZdeijOcpluTgXt/uNE/j0x
A1O34Q==
-----END CERTIFICATE-----

subjectAltName özelliğini kullanmasaydınız, yukarıdaki çıktınız X509v3 Subject Alternative Name uzantısını içermeyecekti.


Dosyalar


hello.pb.go dosyasını yaratmak için make compile komutunu çalıştırın.


Makefile


.PHONY: compile
compile:
protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative hello.proto

.PHONY: client
client:
go run --race client/main.go

.PHONY: server
server:
go run --race server/main.go

hello.proto


syntax = "proto3";

package message;

option go_package = "github.com/inanzzz/hello";

message MessageRequest {
string text = 1;
}

message MessageResponse {
bool ok = 1;
}

service MessageService {
rpc SendMessage (MessageRequest) returns (MessageResponse) {}
}

go.mod


module github.com/inanzzz/hello

go 1.15

require (
github.com/golang/protobuf v1.4.2
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
google.golang.org/grpc v1.31.1
google.golang.org/protobuf v1.25.0
)

server/main.go


package main

import (
"log"
"net"

"github.com/inanzzz/hello"
"github.com/inanzzz/hello/message"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

func main() {
log.Println("server")

listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}

creds, err := credentials.NewServerTLSFromFile("cert/server.crt", "cert/server.key")
if err != nil {
log.Fatalln(err)
}

grpcServer := grpc.NewServer(grpc.Creds(creds))

messageServer := message.NewServer()

client.RegisterMessageServiceServer(grpcServer, messageServer)

log.Fatalln(grpcServer.Serve(listener))
}

message/server.go


package message

import (
"context"
"log"

"github.com/inanzzz/hello"
)

type Server struct {
client.UnimplementedMessageServiceServer
}

func NewServer() Server {
return Server{}
}

func (s Server) SendMessage(ctx context.Context, req *client.MessageRequest) (*client.MessageResponse, error) {
log.Println(req)

return &client.MessageResponse{Ok:true}, nil
}

client/main.go


Bu versiyon 1.


package main

import (
"context"
"log"
"time"

"github.com/inanzzz/hello/message"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

func main() {
log.Println("client")

creds, err := credentials.NewClientTLSFromFile("cert/server.crt", "localhost")
if err != nil {
log.Fatalln(err)
}

opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
}

conn, err := grpc.Dial(":50051", opts...)
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

messageClient := message.NewClient(conn, time.Second)
res, err := messageClient.SendMessage(context.Background(), "Hello")

log.Println("RES:", res)
log.Println("ERR:", err)
}

Bu versiyon 2.


package main

import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"time"

"github.com/inanzzz/hello/message"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

func main() {
log.Println("client")

caCert, err := ioutil.ReadFile("cert/server.crt")
if err != nil {
log.Fatalln(err)
}

rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(caCert)

tlsConf := &tls.Config{
RootCAs: rootCAs,
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
ServerName: "localhost",
}

opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(tlsConf)),
}

conn, err := grpc.Dial(":50051", opts...)
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

messageClient := message.NewClient(conn, time.Second)
res, err := messageClient.SendMessage(context.Background(), "Hello")

log.Println("RES:", res)
log.Println("ERR:", err)
}

message/client.go


package message

import (
"context"
"time"

"github.com/inanzzz/hello"
"google.golang.org/grpc"
)

type Client struct {
messageClient client.MessageServiceClient
timeout time.Duration
}

func NewClient(conn grpc.ClientConnInterface, timeout time.Duration) Client {
return Client{
messageClient: client.NewMessageServiceClient(conn),
timeout: timeout,
}
}

func (c Client) SendMessage(ctx context.Context, message string) (*client.MessageResponse, error) {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(c.timeout))
defer cancel()

return c.messageClient.SendMessage(ctx, &client.MessageRequest{Text: message})
}

Test


$ make server
go run --race server/main.go
2020/09/13 20:39:17 server

# This shall output as soon as you run client
2020/09/13 20:39:22 text:"Hello"

$ make client
go run --race client/main.go
2020/09/13 20:39:22 client
2020/09/13 20:39:22 RES: ok:true
2020/09/13 20:39:22 ERR: nil