Skip to content
Merged
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
347 changes: 314 additions & 33 deletions gen/proto/go/teleport/lib/vnet/v1/client_application_service.pb.go

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 52 additions & 15 deletions lib/client/cluster_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,28 +282,66 @@ func (c *ClusterClient) SessionSSHConfig(ctx context.Context, user string, targe
return sshConfig, nil
}

keyRing, err := c.tc.localAgent.GetKeyRing(target.Cluster, WithAllCerts...)
newKeyRing, completedMFA, err := c.SessionSSHKeyRing(ctx, user, target)
if err != nil {
return nil, trace.Wrap(MFARequiredUnknown(err))
return nil, trace.Wrap(err)
}
if !completedMFA {
// The caller relies on this function returning an error if
// target.MFACheck is nil and session MFA was not actually required.
return nil, trace.Wrap(services.ErrSessionMFANotRequired)
}

am, err := newKeyRing.AsAuthMethod()
if err != nil {
return nil, trace.Wrap(ceremonyFailedErr{err})
}

sshConfig.Auth = []ssh.AuthMethod{am}
return sshConfig, nil
}

// SessionSSHKeyRing returns a KeyRing valid for an SSH session to the target.
// If per session MFA is required to establish the connection, then the MFA
// ceremony will be performed. If per session MFA is not required, the user's
// base KeyRing for the cluster will be returned.
func (c *ClusterClient) SessionSSHKeyRing(ctx context.Context, user string, target NodeDetails) (keyRing *KeyRing, completedMFA bool, err error) {
ctx, span := c.Tracer.Start(
ctx,
"clusterClient/SessionSSHKeyRing",
oteltrace.WithSpanKind(oteltrace.SpanKindClient),
oteltrace.WithAttributes(
attribute.String("cluster", c.tc.SiteName),
),
)
defer span.End()

baseKeyRing, err := c.tc.localAgent.GetKeyRing(target.Cluster, WithSSHCerts{})
if err != nil {
return nil, false, trace.Wrap(MFARequiredUnknown(err))
}

if target.MFACheck != nil && !target.MFACheck.Required {
return baseKeyRing, false, nil
}

// Always connect to root for getting new credentials, but attempt to reuse
// the existing client if possible.
rootClusterName, err := keyRing.RootClusterName()
rootClusterName, err := baseKeyRing.RootClusterName()
if err != nil {
return nil, trace.Wrap(MFARequiredUnknown(err))
return nil, false, trace.Wrap(MFARequiredUnknown(err))
}

mfaClt := c
if target.Cluster != rootClusterName {
cfg, err := c.ProxyClient.ClientConfig(ctx, rootClusterName)
if err != nil {
return nil, trace.Wrap(err)
return nil, false, trace.Wrap(err)
}

authClient, err := authclient.NewClient(cfg)
if err != nil {
return nil, trace.Wrap(MFARequiredUnknown(err))
return nil, false, trace.Wrap(MFARequiredUnknown(err))
}

mfaClt = &ClusterClient{
Expand All @@ -326,20 +364,19 @@ func (c *ClusterClient) SessionSSHConfig(ctx context.Context, user string, targe
RouteToCluster: target.Cluster,
MFACheck: target.MFACheck,
},
keyRing,
baseKeyRing.Copy(),
)
if err != nil {
return nil, trace.Wrap(err)
if errors.Is(err, services.ErrSessionMFANotRequired) {
log.DebugContext(ctx, "Session MFA was not required, returning original KeyRing")
return baseKeyRing, false, nil
}
log.DebugContext(ctx, "Error performing session MFA ceremony", "error", err)
return nil, false, trace.Wrap(err)
}

log.DebugContext(ctx, "Issued single-use user certificate after an MFA check")
am, err := result.KeyRing.AsAuthMethod()
if err != nil {
return nil, trace.Wrap(ceremonyFailedErr{err})
}

sshConfig.Auth = []ssh.AuthMethod{am}
return sshConfig, nil
return result.KeyRing, true, nil
}

// prepareUserCertsRequest creates a [proto.UserCertsRequest] with the fields
Expand Down
Loading
Loading