grpc-go icon indicating copy to clipboard operation
grpc-go copied to clipboard

Updating credentials of a running server

Open TalLerner opened this issue 1 year ago • 1 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

TalLerner avatar May 07 '24 18:05 TalLerner

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 https://github.com/arjan-bal/routeguide/commit/b7b06088a321b7d46ee0defde2895e0cef6b3a1f which has a server that switches it's TLS certs every 5 seconds.

Let me know if this works for you.

arjan-bal avatar May 08 '24 17:05 arjan-bal

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.

arjan-bal avatar May 14 '24 07:05 arjan-bal

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.

github-actions[bot] avatar May 20 '24 12:05 github-actions[bot]

Thank you, Arjan, for your reply! I am in the process of applying and testing your proposal. I will update you on the results soon.

Kind Regards, Tal Lerner

From: Arjan Singh Bal @.> Date: Wednesday, 8 May 2024 at 20:41 To: grpc/grpc-go @.> Cc: Tal Lerner @.>, Mention @.> Subject: Re: [grpc/grpc-go] Updating credentials of a running server (Issue #7209)

Hi @TalLernerhttps://github.com/TalLerner, a possible solution is to implement your own TransportCredentialshttps://pkg.go.dev/google.golang.org/grpc/credentials#TransportCredentials that delegates to TLS credentials created using one of the available constructorshttps://pkg.go.dev/google.golang.org/grpc/credentials#TransportCredentials. 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 @.***https://github.com/arjan-bal/routeguide/commit/b7b06088a321b7d46ee0defde2895e0cef6b3a1f which has a server that switches it's TLS certs every 5 seconds.

Let me know if this works for you.

— Reply to this email directly, view it on GitHubhttps://github.com/grpc/grpc-go/issues/7209#issuecomment-2101085166, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AUS3O5TQMHPKNQKQCDXU363ZBJPS5AVCNFSM6AAAAABHLRBKOCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMBRGA4DKMJWGY. You are receiving this because you were mentioned.Message ID: @.***>

TalLerner avatar May 21 '24 14:05 TalLerner

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.

github-actions[bot] avatar May 27 '24 14:05 github-actions[bot]

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.

github-actions[bot] avatar Jun 03 '24 10:06 github-actions[bot]

I just wanted to update you that the solution you suggested was implemented and tested and seems to work great.

Thank you!

From: github-actions[bot] @.> Date: Monday, 3 June 2024 at 13:46 To: grpc/grpc-go @.> Cc: Tal Lerner @.>, Assign @.> Subject: Re: [grpc/grpc-go] Updating credentials of a running server (Issue #7209)

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.

— Reply to this email directly, view it on GitHubhttps://github.com/grpc/grpc-go/issues/7209#issuecomment-2144877084, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AUS3O5Q3JZXFPQW2YIACVTLZFRCO7AVCNFSM6AAAAABHLRBKOCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNBUHA3TOMBYGQ. You are receiving this because you were assigned.Message ID: @.***>

TalLerner avatar Jun 03 '24 11:06 TalLerner