grpc / grpc-go

The Go language implementation of gRPC. HTTP/2 based RPC

Home Page:https://grpc.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Updating credentials of a running server

TalLerner opened this issue · comments

Please see the FAQ in our main README.md before submitting your issue.

Use case(s) - what problem will this feature solve?

When using server certificates, while the server is running new certificates are created. The new certificate must be updated in the server's options. Currently this is not possible without restarting the server and disconnecting the clients.

Proposed Solution

If the options can be updated while the server is running this problem can be resolved.

Alternatives Considered

I didn't find any alternatives. Do you have any suggestions?

Additional Context

Hi @TalLerner, a possible solution is to implement your own TransportCredentials that delegates to TLS credentials created using one of the available constructors. Your custom TransportCredentials can provide the option to switch the delegate during runtime. An example of such a TransportCredentials implementation is as follows:

type DynamicCreds struct {
	delegate credentials.TransportCredentials
	rwMutex  sync.RWMutex
}

func (d *DynamicCreds) ClientHandshake(ctx context.Context, host string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	d.rwMutex.RLock()
	defer d.rwMutex.RUnlock()
	return d.delegate.ClientHandshake(ctx, host, conn)
}

func (d *DynamicCreds) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	d.rwMutex.RLock()
	defer d.rwMutex.RUnlock()
	return d.delegate.ServerHandshake(conn)
}

func (d *DynamicCreds) Info() credentials.ProtocolInfo {
	d.rwMutex.RLock()
	defer d.rwMutex.RUnlock()
	return d.delegate.Info()
}

func (d *DynamicCreds) Clone() credentials.TransportCredentials {
	d.rwMutex.RLock()
	defer d.rwMutex.RUnlock()
	return NewDynamicCreds(d.delegate.Clone())
}

func (d *DynamicCreds) OverrideServerName(name string) error {
	d.rwMutex.RLock()
	defer d.rwMutex.RUnlock()
	return d.delegate.OverrideServerName(name)
}

func (d *DynamicCreds) UpdateDelegate(newCreds credentials.TransportCredentials) {
	d.rwMutex.Lock()
	defer d.rwMutex.Unlock()
	if newCreds == d {
		fmt.Printf("Can't point to self!")
		return
	}
	d.delegate = newCreds
}

func NewDynamicCreds(delegate credentials.TransportCredentials) *DynamicCreds {
	return &DynamicCreds{
		delegate: delegate,
		rwMutex:  sync.RWMutex{},
	}
}

You can then create DynamicCreds and use them while starting your server as follows:

serverCertFile := data.Path("x509/server_cert.pem")
serverKeyFile := data.Path("x509/server_key.pem")
serverCreds, err := credentials.NewServerTLSFromFile(serverCertFile, serverKeyFile)
if err != nil {
	log.Fatalf("Failed to generate credentials: %v", err)
}
dynCreds := NewDynamicCreds(serverCreds)
opts = []grpc.ServerOption{grpc.Creds(dynCreds)}
grpcServer := grpc.NewServer(opts...)

When you want to change the delegate, you can call dynCreds.UpdateDelegate() while passing in the new credentials. This way you gain the ability to change only the transport credentials without updating the server options.

I tried this out in arjan-bal/routeguide@b7b0608 which has a server that switches it's TLS certs every 5 seconds.

Let me know if this works for you.

Another option suggested by @atollena is to create a tls.Config with empty Certificates, write a closure that gets the latest certificates and assign it to the GetCertificate field of the tls.Config. The tls library will call your closure during every handshake to fetch the certificates.

Use this tls.Config to create gRPC transport credentials by calling the constructor.

This issue is labeled as requiring an update from the reporter, and no update has been received after 6 days. If no update is provided in the next 7 days, this issue will be automatically closed.