Skip to content

Instantly share code, notes, and snippets.

@tsuna
Created March 27, 2016 04:17
Show Gist options
  • Save tsuna/f14565a7e61b32edd66f to your computer and use it in GitHub Desktop.
Save tsuna/f14565a7e61b32edd66f to your computer and use it in GitHub Desktop.
SSL/TLS certificate rotation with gRPC (hack/demo/POC)

gRPC SSL/TLS cert rotation

Generate a couple key pairs:

openssl req -x509 -newkey rsa:2048 -keyout key1.pem -out cert1.pem -days 42 -nodes
openssl req -x509 -newkey rsa:2048 -keyout key2.pem -out cert2.pem -days 42 -nodes
ln -s key1.pem key.pem
ln -s cert1.pem cert.pem

The easiest thing to do is to give each key pair a unique problem (e.g. key1/cert1 isn't signed by a recognized CA [which is the case out of the box when using a self-signed cert like above] while key2/cert2 is for the wrong common name)

Start the server:

go run greeter_server/main.go -cafile cert.pem -keyfile key.pem &

Run the client a first time (go run greeter_client/main.go) then swap the symlinks and kill -HUP the server, run the client again and observe the different error messages.

$ go run greeter_client/main.go
2016/03/26 21:00:18 grpc: Server.Serve failed to complete security handshake from "127.0.0.1:56595": remote error: bad certificate
2016/03/26 21:00:19 grpc: Conn.resetTransport failed to create client transport: connection error: desc = "transport: x509: certificate signed by unknown authority"; Reconnecting to "localhost:50051"
$ rm cert.pem key.pem
$ ln -s key2.pem key.pem
$ ln -s cert2.pem cert.pem
$ kill -HUP %5
2016/03/26 21:00:41 reloading cert/key pair                                                                                                                                                         
2016/03/26 21:00:41 cert/key pair reloaded
$ go run greeter_client/main.go
2016/03/26 21:00:54 grpc: Server.Serve failed to complete security handshake from "127.0.0.1:56617": remote error: bad certificate
2016/03/26 21:00:55 grpc: Conn.resetTransport failed to create client transport: connection error: desc = "transport: x509: certificate is valid for yolo, not localhost"; Reconnecting to "localhost:50051"
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package main
import (
"crypto/tls"
"fmt"
"log"
"os"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
config := &tls.Config{}
//config.InsecureSkipVerify = true
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(credentials.NewTLS(config)))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := fmt.Sprintf("pid-%d", os.Getpid())
stream, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could start stream: %v", err)
}
for {
r, err := stream.Recv()
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
}
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package main
import (
"crypto/tls"
"flag"
"log"
"net"
"os"
"os/signal"
"reflect"
"syscall"
"time"
"unsafe"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
port = "127.0.0.1:50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct{}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(req *pb.HelloRequest, stream pb.Greeter_SayHelloServer) error {
for {
err := stream.Send(&pb.HelloReply{Message: "Hello " + req.Name})
if err != nil {
return err
}
<-time.After(1 * time.Second)
}
}
var keyFile = flag.String("keyfile", "",
"Path to client TLS private key file")
var caFile = flag.String("cafile", "",
"Path to server TLS certificate file")
func main() {
flag.Parse()
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
config := &tls.Config{}
cert, err := tls.LoadX509KeyPair(*caFile, *keyFile)
if err != nil {
log.Fatal(err)
}
config.Certificates = []tls.Certificate{cert}
s := grpc.NewServer(grpc.Creds(credentials.NewTLS(config)))
pb.RegisterGreeterServer(s, &server{})
log.Printf("Starting server")
go func() {
err = s.Serve(lis)
if err != nil {
log.Fatal("Failed to start server", err)
}
}()
sighup := make(chan os.Signal, 1)
signal.Notify(sighup, syscall.SIGHUP)
for {
<-sighup
log.Printf("reloading cert/key pair")
config := &tls.Config{}
cert, err := tls.LoadX509KeyPair(*caFile, *keyFile)
if err != nil {
log.Fatal(err)
}
config.Certificates = []tls.Certificate{cert}
creds := credentials.NewTLS(config)
r := reflect.ValueOf(s).Elem().FieldByName("opts").FieldByName("creds")
forceExport(r).Set(reflect.ValueOf(creds))
log.Printf("cert/key pair reloaded")
}
}
// The `reflect' package intentionally makes it impossible to access the value
// of an unexported attribute. The implementation of reflect.DeepEqual() cheats
// as it bypasses this check. Unfortunately, we can't use the same cheat, which
// prevents us from re-implementing DeepEqual properly. So this is our cheat on
// top of theirs. It makes the given reflect.Value appear as if it was exported.
func forceExport(v reflect.Value) reflect.Value {
const flagRO uintptr = 1 << 5 // from reflect/value.go
ptr := unsafe.Pointer(&v)
rv := (*struct {
typ unsafe.Pointer // a *reflect.rtype (reflect.Type)
ptr unsafe.Pointer // The value wrapped by this reflect.Value
flag uintptr
})(ptr)
rv.flag &= ^flagRO // Unset the flag so this value appears to be exported.
return v
}
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
@bwplotka
Copy link

Hmm.. is it really thread safe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment