Connect: Accommodate for making gRPC client creds from tshd key pair#16782
Conversation
There was a problem hiding this comment.
This function used to do two things. I extracted them into separate functions.
- Create a key pair for the tshd process and save the public key on disk.
- This is now
generateAndSaveCert.
- This is now
- Create gRPC server credentials.
- This is now
createServerCredentials.
- This is now
For tshd-initiated communication, the tshd process will need to create a client that will connect to a gRPC server operated by the renderer process of the Electron app. On Windows, we use gRPC over TCP with mTLS. Each process creates its own keypair and saves the public key to a predetermined location. The previous code assumes that tshd is only going to need server credentials. This commit makes it possible to create client credentials from the same key pair.
53ad1fe to
867f227
Compare
|
@nklaassen @xacrimon ping |
|
|
||
| certificate, err := keys.X509KeyPair(cert.Cert, cert.PrivateKey) | ||
| if err != nil { | ||
| return tls.Certificate{}, trace.Wrap(err) |
There was a problem hiding this comment.
I know this tls.Certificate{} is thrown away anyway if an error is returned but is there a reason to create the new struct on error? Or is this just to satisfy return types?
There was a problem hiding this comment.
I know this
tls.Certificate{}is thrown away anyway if an error is returned but is there a reason to create the new struct on error? Or is this just to satisfy return types?
Yes, that is to satisfy return types.
| // File is first saved using under tempPath and then renamed to fullPath. | ||
| // This prevents other processes from reading a half written file. | ||
| fullPath := filepath.Join(path) | ||
| tempPath := fullPath + ".tmp" |
There was a problem hiding this comment.
Could there possibly be concurrent calls across different processes that could race here?
There was a problem hiding this comment.
Multiple calls to generateAndSaveCert? No, this is called when we run tsh daemon start and we create only one such process.
There was a problem hiding this comment.
still, os.CreateTemp might be appropriate and would avoid that problem altogether
There was a problem hiding this comment.
e019eb2 makes it so that os.CreateTemp is used instead of manually creating a "temp" file.
| grpcServer := grpc.NewServer(tshdCreds, grpc.ChainUnaryInterceptor( | ||
| withErrorHandling(cfg.Log), | ||
| )) |
There was a problem hiding this comment.
| grpcServer := grpc.NewServer(tshdCreds, grpc.ChainUnaryInterceptor( | |
| withErrorHandling(cfg.Log), | |
| )) | |
| grpcServer := grpc.NewServer(serverOptions...) |
| // createServerCredentials creates mTLS credentials for a gRPC server. The client cert file is read | ||
| // only on an incoming connection, not upfront. |
There was a problem hiding this comment.
I can see that the client cert file is read on every connection, the comment should probably explain why
There was a problem hiding this comment.
I can see that the client cert file is read on every connection, the comment should probably explain why
Comment added:
teleport/lib/teleterm/apiserver/grpccredentials.go
Lines 40 to 43 in fddbb94
| func generateAndSaveCert(path string) (tls.Certificate, error) { | ||
| // File is first saved using under tempPath and then renamed to fullPath. | ||
| // This prevents other processes from reading a half written file. | ||
| fullPath := filepath.Join(path) |
There was a problem hiding this comment.
is there a reason to call filepath.Join with a single arg?
There was a problem hiding this comment.
Ah, good catch, thanks. That was a leftover, it used to look like this, then I refactored the function args but forgot to remove the call to Join.
teleport/lib/teleterm/apiserver/apiserver.go
Line 145 in 78cfbfd
| // File is first saved using under tempPath and then renamed to fullPath. | ||
| // This prevents other processes from reading a half written file. | ||
| fullPath := filepath.Join(path) | ||
| tempPath := fullPath + ".tmp" |
There was a problem hiding this comment.
still, os.CreateTemp might be appropriate and would avoid that problem altogether
| // createServerCredentials creates mTLS credentials for a gRPC server. The client cert file is read | ||
| // only on an incoming connection, not upfront. |
There was a problem hiding this comment.
I can see that the client cert file is read on every connection, the comment should probably explain why
Comment added:
teleport/lib/teleterm/apiserver/grpccredentials.go
Lines 40 to 43 in fddbb94
| func generateAndSaveCert(path string) (tls.Certificate, error) { | ||
| // File is first saved using under tempPath and then renamed to fullPath. | ||
| // This prevents other processes from reading a half written file. | ||
| fullPath := filepath.Join(path) |
There was a problem hiding this comment.
Ah, good catch, thanks. That was a leftover, it used to look like this, then I refactored the function args but forgot to remove the call to Join.
teleport/lib/teleterm/apiserver/apiserver.go
Line 145 in 78cfbfd
| // File is first saved using under tempPath and then renamed to fullPath. | ||
| // This prevents other processes from reading a half written file. | ||
| fullPath := filepath.Join(path) | ||
| tempPath := fullPath + ".tmp" |
There was a problem hiding this comment.
e019eb2 makes it so that os.CreateTemp is used instead of manually creating a "temp" file.
|
|
||
| err = os.WriteFile(tempPath, cert.Cert, 0600) | ||
| if err != nil { | ||
| if err = tempFile.Chmod(0600); err != nil { |
There was a problem hiding this comment.
I'm not sure if I need that extra Chmod here. AFAIK os.CreateTemp already creates a file with 0600 and I suppose we can expect a temp file to have the strictest permissions possible. But the docs for os.CreateTemp don't state any guarantees about it so I'd rather be explicit.
|
I’m going to backport this once v11.0.0 is released. |
|
@ravicious See the table below for backport results.
|
Equivalent webapps PR: gravitational/webapps#1220.
On Windows, we use gRPC over TCP with mTLS since Node.js doesn't support UDS on Windows. Each process creates its own keypair and saves the public key to a predetermined location.
Up until now, these were the client-server pairs we had to support:
For tshd-initiated communication, the tshd process will need to create a client that will connect to a gRPC server operated by the renderer process of the Electron app.
The previous code assumes that tshd is only going to need server credentials. This commit makes it possible to create client credentials from the same key pair.