Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/observability/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ func WithPropagationContext(ctx context.Context, pc PropagationContext, opts ...
func DefaultProvider() oteltrace.TracerProvider {
return otel.GetTracerProvider()
}

// NewTracer creates a new [oteltrace.Tracer] from the global default
// [oteltrace.TracerProvider] with the provided name
func NewTracer(name string) oteltrace.Tracer {
return DefaultProvider().Tracer(name)
}
2 changes: 1 addition & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2276,7 +2276,7 @@ func twoClustersTunnel(t *testing.T, suite *integrationTestSuite, now time.Time,
require.Equal(t, "hello world\n", outputA.String())

// Update trusted CAs.
err = tc.UpdateTrustedCA(ctx, a.Secrets.SiteName)
err = tc.UpdateTrustedCA(ctx, a.GetSiteAPI(a.Secrets.SiteName))
require.NoError(t, err)

// The known_hosts file should have two certificates, the way bytes.Split
Expand Down
199 changes: 121 additions & 78 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,12 +565,29 @@ func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error)
}
return trace.Wrap(err)
}
if err := tc.ActivateKey(ctx, key); err != nil {

if err := tc.ActivateKeyWithoutTrustedCerts(ctx, key); err != nil {
return trace.Wrap(err)
}

clusterClient, err := tc.ConnectToCluster(ctx)
if err != nil {
return trace.Wrap(err)
}
defer clusterClient.Close()

rootAuth, err := clusterClient.RootClient(ctx)
if err != nil {
return trace.Wrap(err)
}
defer rootAuth.Close()

if err := tc.UpdateTrustedCA(ctx, rootAuth); err != nil {
return trace.Wrap(err)
}

// Attempt device login. This activates a fresh key if successful.
if err := tc.AttemptDeviceLogin(ctx, key); err != nil {
if err := tc.AttemptDeviceLogin(ctx, key, rootAuth); err != nil {
return trace.Wrap(err)
}

Expand Down Expand Up @@ -3332,7 +3349,14 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) {
// successful, as skipping the ceremony is valid for various reasons (Teleport
// cluster doesn't support device authn, device wasn't enrolled, etc).
// Use [TeleportClient.DeviceLogin] if you want more control over process.
func (tc *TeleportClient) AttemptDeviceLogin(ctx context.Context, key *Key) error {
func (tc *TeleportClient) AttemptDeviceLogin(ctx context.Context, key *Key, rootAuth auth.ClientI) error {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/AttemptDeviceLogin",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

pingResp, err := tc.Ping(ctx)
if err != nil {
return trace.Wrap(err)
Expand All @@ -3343,11 +3367,15 @@ func (tc *TeleportClient) AttemptDeviceLogin(ctx context.Context, key *Key) erro
return nil
}

newCerts, err := tc.DeviceLogin(ctx, &devicepb.UserCertificates{
// Augment the SSH certificate.
// The TLS certificate is already part of the connection.
SshAuthorizedKey: key.Cert,
})
newCerts, err := tc.DeviceLogin(
ctx,
&devicepb.UserCertificates{
// Augment the SSH certificate.
// The TLS certificate is already part of the connection.
SshAuthorizedKey: key.Cert,
},
rootAuth,
)
switch {
case errors.Is(err, devicetrust.ErrDeviceKeyNotFound):
log.Debug("Device Trust: Skipping device authentication, device key not found")
Expand All @@ -3370,7 +3398,7 @@ func (tc *TeleportClient) AttemptDeviceLogin(ctx context.Context, key *Key) erro
Type: "CERTIFICATE",
Bytes: newCerts.X509Der,
})
return trace.Wrap(tc.ActivateKey(ctx, &cp))
return trace.Wrap(tc.ActivateKey(ctx, &cp, rootAuth))
}

// DeviceLogin attempts to authenticate the current device with Teleport.
Expand All @@ -3386,18 +3414,13 @@ func (tc *TeleportClient) AttemptDeviceLogin(ctx context.Context, key *Key) erro
// `tsh login`).
//
// Device Trust is a Teleport Enterprise feature.
func (tc *TeleportClient) DeviceLogin(ctx context.Context, certs *devicepb.UserCertificates) (*devicepb.UserCertificates, error) {
proxyClient, err := tc.ConnectToProxy(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
defer proxyClient.Close()

authClient, err := proxyClient.ConnectToRootCluster(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
defer authClient.Close()
func (tc *TeleportClient) DeviceLogin(ctx context.Context, certs *devicepb.UserCertificates, rootAuth auth.ClientI) (*devicepb.UserCertificates, error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/DeviceLogin",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

// Allow tests to override the default authn function.
runCeremony := tc.dtAuthnRunCeremony
Expand All @@ -3406,7 +3429,7 @@ func (tc *TeleportClient) DeviceLogin(ctx context.Context, certs *devicepb.UserC
}

// Login without a previous auto-enroll attempt.
devicesClient := authClient.DevicesClient()
devicesClient := rootAuth.DevicesClient()
newCerts, loginErr := runCeremony(ctx, devicesClient, certs)
// Success or auto-enroll impossible.
if loginErr == nil || errors.Is(loginErr, devicetrust.ErrPlatformNotSupported) || trace.IsNotImplemented(loginErr) {
Expand Down Expand Up @@ -3530,6 +3553,13 @@ type SSHLoginFunc func(context.Context, *keys.PrivateKey) (*auth.SSHLoginRespons
// SSHLogin uses the given login function to login the client. This function handles
// private key logic and parsing the resulting auth response.
func (tc *TeleportClient) SSHLogin(ctx context.Context, sshLoginFunc SSHLoginFunc) (*Key, error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/SSHLogin",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

priv, err := tc.GetNewLoginKey(ctx)
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -3591,6 +3621,13 @@ func (tc *TeleportClient) SSHLogin(ctx context.Context, sshLoginFunc SSHLoginFun

// GetNewLoginKey gets a new private key for login.
func (tc *TeleportClient) GetNewLoginKey(ctx context.Context) (priv *keys.PrivateKey, err error) {
_, span := tc.Tracer.Start(
ctx,
"teleportClient/GetNewLoginKey",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

switch tc.PrivateKeyPolicy {
case keys.PrivateKeyPolicyHardwareKey:
log.Debugf("Attempting to login with YubiKey private key.")
Expand All @@ -3603,10 +3640,7 @@ func (tc *TeleportClient) GetNewLoginKey(ctx context.Context) (priv *keys.Privat
priv, err = native.GeneratePrivateKey()
}

if err != nil {
return nil, trace.Wrap(err)
}
return priv, nil
return priv, trace.Wrap(err)
}

// new SSHLogin generates a new SSHLogin using the given login key.
Expand All @@ -3631,6 +3665,13 @@ func (tc *TeleportClient) newSSHLogin(priv *keys.PrivateKey) (SSHLogin, error) {
}

func (tc *TeleportClient) pwdlessLogin(ctx context.Context, priv *keys.PrivateKey) (*auth.SSHLoginResponse, error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/pwdlessLogin",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

// Only pass on the user if explicitly set, otherwise let the credential
// picker kick in.
user := ""
Expand Down Expand Up @@ -3682,6 +3723,13 @@ func (tc *TeleportClient) localLogin(ctx context.Context, priv *keys.PrivateKey,

// directLogin asks for a password + OTP token, makes a request to CA via proxy
func (tc *TeleportClient) directLogin(ctx context.Context, secondFactorType constants.SecondFactorType, priv *keys.PrivateKey) (*auth.SSHLoginResponse, error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/directLogin",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

password, err := tc.AskPassword(ctx)
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -3714,6 +3762,13 @@ func (tc *TeleportClient) directLogin(ctx context.Context, secondFactorType cons

// mfaLocalLogin asks for a password and performs the challenge-response authentication
func (tc *TeleportClient) mfaLocalLogin(ctx context.Context, priv *keys.PrivateKey) (*auth.SSHLoginResponse, error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/mfaLocalLogin",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

password, err := tc.AskPassword(ctx)
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -3799,7 +3854,7 @@ func (tc *TeleportClient) ssoLogin(ctx context.Context, priv *keys.PrivateKey, c

// ActivateKey saves the target session cert into the local
// keystore (and into the ssh-agent) for future use.
func (tc *TeleportClient) ActivateKey(ctx context.Context, key *Key) error {
func (tc *TeleportClient) ActivateKey(ctx context.Context, key *Key, getter services.AuthorityGetter) error {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/ActivateKey",
Expand All @@ -3817,21 +3872,27 @@ func (tc *TeleportClient) ActivateKey(ctx context.Context, key *Key) error {
return trace.Wrap(err)
}

// Connect to the Auth Server of the root cluster and fetch the known hosts.
rootClusterName := key.TrustedCerts[0].ClusterName
if err := tc.UpdateTrustedCA(ctx, rootClusterName); err != nil {
if len(tc.JumpHosts) == 0 {
return trace.Wrap(err)
}
errViaJumphost := err
// If JumpHosts was pointing at the leaf cluster (e.g. during 'tsh ssh
// -J leaf.example.com'), this could've caused the above error. Try to
// fetch CAs without JumpHosts to force it to use the root cluster.
if err := tc.WithoutJumpHosts(func(tc *TeleportClient) error {
return tc.UpdateTrustedCA(ctx, rootClusterName)
}); err != nil {
return trace.NewAggregate(errViaJumphost, err)
}
Comment on lines -3826 to -3834
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fallback flow always creates a new TeleportClient and in this PR this logic was removed.
Not sure if this flow is 100% covered by UT or integration test but I'm afraid that by removing this fallback we will break the flow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's now the responsibility of the caller to ensure that the provided services.AuthorityGetter is connected to the root Auth service so in theory we shouldn't need to attempt the fallback any more. I'll add a test to cover this flow if one doesn't already exist.

return trace.Wrap(tc.UpdateTrustedCA(ctx, getter))
}

// ActivateKeyWithoutTrustedCerts saves the target session cert into the local
// keystore (and into the ssh-agent) for future use.
func (tc *TeleportClient) ActivateKeyWithoutTrustedCerts(ctx context.Context, key *Key) error {
_, span := tc.Tracer.Start(
ctx,
"teleportClient/ActivateKey",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

if tc.localAgent == nil {
// skip activation if no local agent is present
return nil
}

// save the cert to the local storage (~/.tsh usually):
if err := tc.localAgent.AddKey(key); err != nil {
return trace.Wrap(err)
}

return nil
Expand Down Expand Up @@ -3935,57 +3996,25 @@ func (tc *TeleportClient) ShowMOTD(ctx context.Context) error {
return nil
}

// GetTrustedCA returns a list of host certificate authorities
// trusted by the cluster client is authenticated with.
func (tc *TeleportClient) GetTrustedCA(ctx context.Context, clusterName string) ([]types.CertAuthority, error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/GetTrustedCA",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
oteltrace.WithAttributes(attribute.String("cluster", clusterName)),
)
defer span.End()

// Connect to the proxy.
if !tc.Config.ProxySpecified() {
return nil, trace.BadParameter("proxy server is not specified")
}
proxyClient, err := tc.ConnectToProxy(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
defer proxyClient.Close()

// Get a client to the Auth Server.
clt, err := proxyClient.ConnectToCluster(ctx, clusterName)
if err != nil {
return nil, trace.Wrap(err)
}
defer clt.Close()

// Get the list of host certificates that this cluster knows about.
return clt.GetCertAuthorities(ctx, types.HostCA, false)
}

// UpdateTrustedCA connects to the Auth Server and fetches all host certificates
// and updates ~/.tsh/keys/proxy/certs.pem and ~/.tsh/known_hosts.
func (tc *TeleportClient) UpdateTrustedCA(ctx context.Context, clusterName string) error {
func (tc *TeleportClient) UpdateTrustedCA(ctx context.Context, getter services.AuthorityGetter) error {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/UpdateTrustedCA",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
oteltrace.WithAttributes(attribute.String("cluster", clusterName)),
)
defer span.End()

if tc.localAgent == nil {
return trace.BadParameter("TeleportClient.UpdateTrustedCA called on a client without localAgent")
}
// Get the list of host certificates that this cluster knows about.
hostCerts, err := tc.GetTrustedCA(ctx, clusterName)

hostCerts, err := getter.GetCertAuthorities(ctx, types.HostCA, false)
if err != nil {
return trace.Wrap(err)
}

trustedCerts := auth.AuthoritiesToTrustedCerts(hostCerts)

// Update the CA pool and known hosts for all CAs the cluster knows about.
Expand Down Expand Up @@ -4286,6 +4315,13 @@ func Username() (string, error) {

// AskOTP prompts the user to enter the OTP token.
func (tc *TeleportClient) AskOTP(ctx context.Context) (token string, err error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/AskOTP",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

stdin := prompt.Stdin()
if !stdin.IsTerminal() {
return "", trace.Wrap(prompt.ErrNotTerminal, "cannot perform OTP login without a terminal")
Expand All @@ -4295,6 +4331,13 @@ func (tc *TeleportClient) AskOTP(ctx context.Context) (token string, err error)

// AskPassword prompts the user to enter the password
func (tc *TeleportClient) AskPassword(ctx context.Context) (pwd string, err error) {
ctx, span := tc.Tracer.Start(
ctx,
"teleportClient/AskPassword",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
)
defer span.End()

stdin := prompt.Stdin()
if !stdin.IsTerminal() {
return "", trace.Wrap(prompt.ErrNotTerminal, "cannot perform password login without a terminal")
Expand Down
Loading