diff --git a/lib/client/api.go b/lib/client/api.go index 9c47a16303ffd..12d0855982e74 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -226,6 +226,10 @@ type Config struct { // against Teleport client and obtaining credentials from elsewhere. SkipLocalAuth bool + // UseKeyPrincipals forces the use of the username from the key principals rather than using + // the current user username. + UseKeyPrincipals bool + // Agent is used when SkipLocalAuth is true Agent agent.Agent @@ -2166,7 +2170,7 @@ func (tc *TeleportClient) getProxySSHPrincipal() string { proxyPrincipal = tc.JumpHosts[0].Username } // see if we already have a signed key in the cache, we'll use that instead - if !tc.Config.SkipLocalAuth && tc.localAgent != nil { + if (!tc.Config.SkipLocalAuth || tc.UseKeyPrincipals) && tc.localAgent != nil { signers, err := tc.localAgent.Signers() if err != nil || len(signers) == 0 { return proxyPrincipal diff --git a/lib/client/api_test.go b/lib/client/api_test.go index a8e524e54ae81..44c17681603a3 100644 --- a/lib/client/api_test.go +++ b/lib/client/api_test.go @@ -17,12 +17,16 @@ limitations under the License. package client import ( + "io" "os" "testing" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/trace" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" "github.com/stretchr/testify/require" "gopkg.in/check.v1" @@ -499,6 +503,53 @@ func TestApplyProxySettings(t *testing.T) { } } +type mockAgent struct { + // Agent is embedded to avoid redeclaring all interface methods. + // Only the Signers method is implemented by testAgent. + agent.Agent + ValidPrincipals []string +} + +type mockSigner struct { + ValidPrincipals []string +} + +func (s *mockSigner) PublicKey() ssh.PublicKey { + return &ssh.Certificate{ + ValidPrincipals: s.ValidPrincipals, + } +} + +func (s *mockSigner) Sign(rand io.Reader, b []byte) (*ssh.Signature, error) { + return nil, trace.Errorf("mockSigner does not implement Sign") +} + +// Signers implements agent.Agent.Signers. +func (m *mockAgent) Signers() ([]ssh.Signer, error) { + return []ssh.Signer{&mockSigner{ValidPrincipals: m.ValidPrincipals}}, nil +} + +func TestNewClient_UseKeyPrincipals(t *testing.T) { + cfg := &Config{ + Username: "xyz", + HostLogin: "xyz", + WebProxyAddr: "localhost", + SkipLocalAuth: true, + UseKeyPrincipals: true, // causes VALID to be returned, as key was used + Agent: &mockAgent{ValidPrincipals: []string{"VALID"}}, + AuthMethods: []ssh.AuthMethod{ssh.Password("xyz") /* placeholder authmethod */}, + } + client, err := NewClient(cfg) + require.NoError(t, err) + require.Equal(t, "VALID", client.getProxySSHPrincipal(), "ProxySSHPrincipal mismatch") + + cfg.UseKeyPrincipals = false // causes xyz to be returned as key was not used + + client, err = NewClient(cfg) + require.NoError(t, err) + require.Equal(t, "xyz", client.getProxySSHPrincipal(), "ProxySSHPrincipal mismatch") +} + func TestParseSearchKeywords(t *testing.T) { t.Parallel() diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index 166aa41522de6..365822251d9bf 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -1910,6 +1910,10 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (*client.TeleportClient, erro if cf.IdentityFileIn != "" { // Ignore local authentication methods when identity file is provided c.SkipLocalAuth = true + // Force the use of the certificate principals so Unix + // username does not get used when logging in + c.UseKeyPrincipals = hostLogin == "" + var ( key *client.Key identityAuth ssh.AuthMethod