diff --git a/integration/helpers/instance.go b/integration/helpers/instance.go index 7e7deb03567a8..7e21a3f2dd42b 100644 --- a/integration/helpers/instance.go +++ b/integration/helpers/instance.go @@ -66,6 +66,7 @@ import ( "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" @@ -373,14 +374,16 @@ func NewInstance(t *testing.T, cfg InstanceConfig) *TeleInstance { fatalIf(err) keygen := keygen.New(context.TODO()) - cert, err := keygen.GenerateHostCert(services.HostCertParams{ + cert, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: sshSigner, PublicHostKey: cfg.Pub, HostID: cfg.HostID, NodeName: cfg.NodeName, - ClusterName: cfg.ClusterName, - Role: types.RoleAdmin, TTL: 24 * time.Hour, + Identity: sshca.Identity{ + ClusterName: cfg.ClusterName, + SystemRole: types.RoleAdmin, + }, }) fatalIf(err) tlsCA, err := tlsca.FromKeys(tlsCACert, cfg.Priv) diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 1e6f954b75b31..045c8883ee6db 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2130,20 +2130,22 @@ func (a *Server) GenerateHostCert(ctx context.Context, hostPublicKey []byte, hos } // create and sign! - return a.generateHostCert(ctx, services.HostCertParams{ + return a.generateHostCert(ctx, sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: hostPublicKey, HostID: hostID, NodeName: nodeName, - Principals: principals, - ClusterName: clusterName, - Role: role, TTL: ttl, + Identity: sshca.Identity{ + Principals: principals, + ClusterName: clusterName, + SystemRole: role, + }, }) } func (a *Server) generateHostCert( - ctx context.Context, p services.HostCertParams, + ctx context.Context, req sshca.HostCertificateRequest, ) ([]byte, error) { readOnlyAuthPref, err := a.GetReadOnlyAuthPreference(ctx) if err != nil { @@ -2151,7 +2153,7 @@ func (a *Server) generateHostCert( } var locks []types.LockTarget - switch p.Role { + switch req.Identity.SystemRole { case types.RoleNode: // Node role is a special case because it was previously suported as a // lock target that only locked the `ssh_service`. If the same Teleport server @@ -2164,9 +2166,9 @@ func (a *Server) generateHostCert( // and `Node` fields if the role is `Node` so that the previous behavior // is preserved. // This is a legacy behavior that we need to support for backwards compatibility. - locks = []types.LockTarget{{ServerID: p.HostID, Node: p.HostID}, {ServerID: HostFQDN(p.HostID, p.ClusterName), Node: HostFQDN(p.HostID, p.ClusterName)}} + locks = []types.LockTarget{{ServerID: req.HostID, Node: req.HostID}, {ServerID: HostFQDN(req.HostID, req.Identity.ClusterName), Node: HostFQDN(req.HostID, req.Identity.ClusterName)}} default: - locks = []types.LockTarget{{ServerID: p.HostID}, {ServerID: HostFQDN(p.HostID, p.ClusterName)}} + locks = []types.LockTarget{{ServerID: req.HostID}, {ServerID: HostFQDN(req.HostID, req.Identity.ClusterName)}} } if lockErr := a.checkLockInForce(readOnlyAuthPref.GetLockingMode(), locks, @@ -2174,7 +2176,7 @@ func (a *Server) generateHostCert( return nil, trace.Wrap(lockErr) } - return a.Authority.GenerateHostCert(p) + return a.Authority.GenerateHostCert(req) } // GetKeyStore returns the KeyStore used by the auth server @@ -2226,7 +2228,7 @@ type certRequest struct { traits wrappers.Traits // activeRequests tracks privilege escalation requests applied // during the construction of the certificate. - activeRequests services.RequestIDs + activeRequests []string // appSessionID is the session ID of the application session. appSessionID string // appPublicAddr is the public address of the application. @@ -3081,7 +3083,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. defaultMode: readOnlyAuthPref.GetLockingMode(), username: req.user.GetName(), mfaVerified: req.mfaVerified, - activeAccessRequests: req.activeRequests.AccessRequests, + activeAccessRequests: req.activeRequests, deviceID: req.deviceExtensions.DeviceID, }); err != nil { return nil, trace.Wrap(err) @@ -3210,11 +3212,6 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. // All users have access to this and join RBAC rules are checked after the connection is established. allowedLogins = append(allowedLogins, teleport.SSHSessionJoinPrincipal) - requestedResourcesStr, err := types.ResourceIDsToString(req.checker.GetAllowedResourceIDs()) - if err != nil { - return nil, trace.Wrap(err) - } - pinnedIP := "" if caType == types.UserCA && (req.checker.PinSourceIP() || req.pinIP) { if req.loginIP == "" { @@ -3254,7 +3251,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. Identity: sshca.Identity{ Username: req.user.GetName(), Impersonator: req.impersonator, - AllowedLogins: allowedLogins, + Principals: allowedLogins, Roles: req.checker.RoleNames(), PermitPortForwarding: req.checker.CanPortForward(), PermitAgentForwarding: req.checker.CanForwardAgents(), @@ -3272,7 +3269,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. BotName: req.botName, BotInstanceID: req.botInstanceID, CertificateExtensions: req.checker.CertificateExtensions(), - AllowedResourceIDs: requestedResourcesStr, + AllowedResourceIDs: req.checker.GetAllowedResourceIDs(), ConnectionDiagnosticID: req.connectionDiagnosticID, PrivateKeyPolicy: attestedKeyPolicy, DeviceID: req.deviceExtensions.DeviceID, @@ -3367,7 +3364,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. AWSRoleARNs: roleARNs, AzureIdentities: azureIdentities, GCPServiceAccounts: gcpAccounts, - ActiveRequests: req.activeRequests.AccessRequests, + ActiveRequests: req.activeRequests, DisallowReissue: req.disallowReissue, Renewable: req.renewable, Generation: req.generation, @@ -4734,14 +4731,16 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ return nil, trace.Wrap(err) } // generate host SSH certificate - hostSSHCert, err := a.generateHostCert(ctx, services.HostCertParams{ + hostSSHCert, err := a.generateHostCert(ctx, sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: req.PublicSSHKey, HostID: req.HostID, NodeName: req.NodeName, - ClusterName: clusterName.GetClusterName(), - Role: req.Role, - Principals: req.AdditionalPrincipals, + Identity: sshca.Identity{ + ClusterName: clusterName.GetClusterName(), + SystemRole: req.Role, + Principals: req.AdditionalPrincipals, + }, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/auth_test.go b/lib/auth/auth_test.go index b42e5dde88040..88e17a326e1f6 100644 --- a/lib/auth/auth_test.go +++ b/lib/auth/auth_test.go @@ -2642,7 +2642,7 @@ func TestGenerateUserCertWithLocks(t *testing.T) { mfaVerified: mfaID, sshPublicKey: sshPubKey, tlsPublicKey: tlsPubKey, - activeRequests: services.RequestIDs{AccessRequests: []string{requestID}}, + activeRequests: []string{requestID}, deviceExtensions: DeviceExtensions{ DeviceID: deviceID, AssetTag: "assettag1", diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 98fa97190a779..f140796d2100a 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3440,11 +3440,9 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC checker: checker, // Copy IP from current identity to the generated certificate, if present, // to avoid generateUserCerts() being used to drop IP pinning in the new certificates. - loginIP: a.context.Identity.GetIdentity().LoginIP, - traits: accessInfo.Traits, - activeRequests: services.RequestIDs{ - AccessRequests: req.AccessRequests, - }, + loginIP: a.context.Identity.GetIdentity().LoginIP, + traits: accessInfo.Traits, + activeRequests: req.AccessRequests, connectionDiagnosticID: req.ConnectionDiagnosticID, botName: getBotName(user), diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index 291458bd196e8..f4218479f302b 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -62,6 +62,7 @@ import ( "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services/suite" "github.com/gravitational/teleport/lib/srv/db/common/databaseobjectimportrule" + "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/proxy" @@ -77,14 +78,16 @@ func TestReadIdentity(t *testing.T) { caSigner, err := ssh.ParsePrivateKey(priv) require.NoError(t, err) - cert, err := a.GenerateHostCert(services.HostCertParams{ + cert, err := a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "id1", NodeName: "node-name", - ClusterName: "example.com", - Role: types.RoleNode, TTL: 0, + Identity: sshca.Identity{ + ClusterName: "example.com", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) @@ -98,14 +101,16 @@ func TestReadIdentity(t *testing.T) { // test TTL by converting the generated cert to text -> back and making sure ExpireAfter is valid ttl := 10 * time.Second expiryDate := clock.Now().Add(ttl) - bytes, err := a.GenerateHostCert(services.HostCertParams{ + bytes, err := a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "id1", NodeName: "node-name", - ClusterName: "example.com", - Role: types.RoleNode, TTL: ttl, + Identity: sshca.Identity{ + ClusterName: "example.com", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) copy, err := apisshutils.ParseCertificate(bytes) @@ -125,14 +130,16 @@ func TestBadIdentity(t *testing.T) { require.IsType(t, trace.BadParameter(""), err) // missing authority domain - cert, err := a.GenerateHostCert(services.HostCertParams{ + cert, err := a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "id2", NodeName: "", - ClusterName: "", - Role: types.RoleNode, TTL: 0, + Identity: sshca.Identity{ + ClusterName: "", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) @@ -140,14 +147,16 @@ func TestBadIdentity(t *testing.T) { require.IsType(t, trace.BadParameter(""), err) // missing host uuid - cert, err = a.GenerateHostCert(services.HostCertParams{ + cert, err = a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "example.com", NodeName: "", - ClusterName: "", - Role: types.RoleNode, TTL: 0, + Identity: sshca.Identity{ + ClusterName: "", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) @@ -155,14 +164,16 @@ func TestBadIdentity(t *testing.T) { require.IsType(t, trace.BadParameter(""), err) // unrecognized role - cert, err = a.GenerateHostCert(services.HostCertParams{ + cert, err = a.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "example.com", NodeName: "", - ClusterName: "id1", - Role: "bad role", TTL: 0, + Identity: sshca.Identity{ + ClusterName: "id1", + SystemRole: "bad role", + }, }) require.NoError(t, err) diff --git a/lib/auth/keygen/keygen.go b/lib/auth/keygen/keygen.go index 5f47b3a90ac16..7fdf2dc41fac0 100644 --- a/lib/auth/keygen/keygen.go +++ b/lib/auth/keygen/keygen.go @@ -33,9 +33,7 @@ import ( "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/lib/modules" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" - "github.com/gravitational/teleport/lib/utils" ) // Keygen is a key generator that precomputes keys to provide quick access to @@ -69,58 +67,73 @@ func New(_ context.Context, opts ...Option) *Keygen { // GenerateHostCert generates a host certificate with the passed in parameters. // The private key of the CA to sign the certificate must be provided. -func (k *Keygen) GenerateHostCert(c services.HostCertParams) ([]byte, error) { - if err := c.Check(); err != nil { +func (k *Keygen) GenerateHostCert(req sshca.HostCertificateRequest) ([]byte, error) { + if err := req.Check(); err != nil { return nil, trace.Wrap(err) } - return k.GenerateHostCertWithoutValidation(c) + return k.GenerateHostCertWithoutValidation(req) } // GenerateHostCertWithoutValidation generates a host certificate with the // passed in parameters without validating them. For use in tests only. -func (k *Keygen) GenerateHostCertWithoutValidation(c services.HostCertParams) ([]byte, error) { - pubKey, _, _, _, err := ssh.ParseAuthorizedKey(c.PublicHostKey) +func (k *Keygen) GenerateHostCertWithoutValidation(req sshca.HostCertificateRequest) ([]byte, error) { + pubKey, _, _, _, err := ssh.ParseAuthorizedKey(req.PublicHostKey) if err != nil { return nil, trace.Wrap(err) } + // create shallow copy of identity since we want to make some local changes + ident := req.Identity + + ident.CertType = ssh.HostCert + + // since this method ignores the supplied values for ValidBefore/ValidAfter, avoid confusing by + // rejecting identities where they are set. + if ident.ValidBefore != 0 { + return nil, trace.BadParameter("ValidBefore should not be set in calls to GenerateUserCert") + } + if ident.ValidAfter != 0 { + return nil, trace.BadParameter("ValidAfter should not be set in calls to GenerateUserCert") + } + // Build a valid list of principals from the HostID and NodeName and then // add in any additional principals passed in. - principals := BuildPrincipals(c.HostID, c.NodeName, c.ClusterName, types.SystemRoles{c.Role}) - principals = append(principals, c.Principals...) + principals := BuildPrincipals(req.HostID, req.NodeName, ident.ClusterName, types.SystemRoles{ident.SystemRole}) + principals = append(principals, ident.Principals...) if len(principals) == 0 { - return nil, trace.BadParameter("no principals provided: %v, %v, %v", - c.HostID, c.NodeName, c.Principals) + return nil, trace.BadParameter("cannot generate host certificate without principals") } principals = apiutils.Deduplicate(principals) + ident.Principals = principals - // create certificate - validBefore := uint64(ssh.CertTimeInfinity) - if c.TTL != 0 { - b := k.clock.Now().UTC().Add(c.TTL) - validBefore = uint64(b.Unix()) + // calculate ValidBefore based on the outer request TTL + ident.ValidBefore = uint64(ssh.CertTimeInfinity) + if req.TTL != 0 { + b := k.clock.Now().UTC().Add(req.TTL) + ident.ValidBefore = uint64(b.Unix()) } - cert := &ssh.Certificate{ - ValidPrincipals: principals, - Key: pubKey, - ValidAfter: uint64(k.clock.Now().UTC().Add(-1 * time.Minute).Unix()), - ValidBefore: validBefore, - CertType: ssh.HostCert, + + ident.ValidAfter = uint64(k.clock.Now().UTC().Add(-1 * time.Minute).Unix()) + + // encode the identity into a certificate + cert, err := ident.Encode("") + if err != nil { + return nil, trace.Wrap(err) } - cert.Permissions.Extensions = make(map[string]string) - cert.Permissions.Extensions[utils.CertExtensionRole] = c.Role.String() - cert.Permissions.Extensions[utils.CertExtensionAuthority] = c.ClusterName + + // set the public key of the certificate + cert.Key = pubKey // sign host certificate with private signing key of certificate authority - if err := cert.SignCert(rand.Reader, c.CASigner); err != nil { + if err := cert.SignCert(rand.Reader, req.CASigner); err != nil { return nil, trace.Wrap(err) } slog.DebugContext( context.TODO(), "Generated SSH host certificate.", - "role", c.Role, "principals", principals, + "role", ident.SystemRole, "principals", ident.Principals, ) return ssh.MarshalAuthorizedKey(cert), nil } @@ -145,6 +158,8 @@ func (k *Keygen) GenerateUserCertWithoutValidation(req sshca.UserCertificateRequ // create shallow copy of identity since we want to make some local changes ident := req.Identity + ident.CertType = ssh.UserCert + // since this method ignores the supplied values for ValidBefore/ValidAfter, avoid confusing by // rejecting identities where they are set. if ident.ValidBefore != 0 { @@ -162,7 +177,7 @@ func (k *Keygen) GenerateUserCertWithoutValidation(req sshca.UserCertificateRequ slog.DebugContext( context.TODO(), "Generated user key with expiry.", - "allowed_logins", ident.AllowedLogins, + "allowed_logins", ident.Principals, "valid_before_unix_ts", ident.ValidBefore, "valid_before", b, ) diff --git a/lib/auth/keygen/keygen_test.go b/lib/auth/keygen/keygen_test.go index d6c243b3ee986..e82933b944885 100644 --- a/lib/auth/keygen/keygen_test.go +++ b/lib/auth/keygen/keygen_test.go @@ -37,7 +37,6 @@ import ( "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth/test" "github.com/gravitational/teleport/lib/cryptosuites" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" ) @@ -176,16 +175,17 @@ func TestBuildPrincipals(t *testing.T) { // run tests for _, tc := range tests { t.Logf("Running test case: %q", tc.desc) - hostCertificateBytes, err := tt.suite.A.GenerateHostCert( - services.HostCertParams{ - CASigner: caSigner, - PublicHostKey: hostPublicKey, - HostID: tc.inHostID, - NodeName: tc.inNodeName, - ClusterName: tc.inClusterName, - Role: tc.inRole, - TTL: time.Hour, - }) + hostCertificateBytes, err := tt.suite.A.GenerateHostCert(sshca.HostCertificateRequest{ + CASigner: caSigner, + PublicHostKey: hostPublicKey, + HostID: tc.inHostID, + NodeName: tc.inNodeName, + TTL: time.Hour, + Identity: sshca.Identity{ + ClusterName: tc.inClusterName, + SystemRole: tc.inRole, + }, + }) require.NoError(t, err) hostCertificate, err := sshutils.ParseCertificate(hostCertificateBytes) @@ -233,9 +233,9 @@ func TestUserCertCompatibility(t *testing.T) { TTL: time.Hour, CertificateFormat: tc.inCompatibility, Identity: sshca.Identity{ - Username: "user", - AllowedLogins: []string{"centos", "root"}, - Roles: []string{"foo"}, + Username: "user", + Principals: []string{"centos", "root"}, + Roles: []string{"foo"}, CertificateExtensions: []*types.CertExtension{{ Type: types.CertExtensionType_SSH, Mode: types.CertExtensionMode_EXTENSION, diff --git a/lib/auth/sessions.go b/lib/auth/sessions.go index 7f202bd9110b3..caf98e262b0f8 100644 --- a/lib/auth/sessions.go +++ b/lib/auth/sessions.go @@ -290,7 +290,7 @@ func (a *Server) newWebSession( tlsPublicKey: tlsPublicKeyPEM, checker: checker, traits: req.Traits, - activeRequests: services.RequestIDs{AccessRequests: req.AccessRequests}, + activeRequests: req.AccessRequests, } var hasDeviceExtensions bool if opts != nil && opts.deviceExtensions != nil { @@ -557,7 +557,7 @@ func (a *Server) CreateAppSessionFromReq(ctx context.Context, req NewAppSessionR checker: checker, ttl: req.SessionTTL, traits: req.Traits, - activeRequests: services.RequestIDs{AccessRequests: req.AccessRequests}, + activeRequests: req.AccessRequests, // Set the app session ID in the certificate - used in auditing from the App Service. appSessionID: sessionID, // Only allow this certificate to be used for applications. diff --git a/lib/auth/test/suite.go b/lib/auth/test/suite.go index 14d22f8265647..ac1a9ee4cd2d1 100644 --- a/lib/auth/test/suite.go +++ b/lib/auth/test/suite.go @@ -64,16 +64,17 @@ func (s *AuthSuite) GenerateHostCert(t *testing.T) { caSigner, err := ssh.ParsePrivateKey(priv) require.NoError(t, err) - cert, err := s.A.GenerateHostCert( - services.HostCertParams{ - CASigner: caSigner, - PublicHostKey: pub, - HostID: "00000000-0000-0000-0000-000000000000", - NodeName: "auth.example.com", - ClusterName: "example.com", - Role: types.RoleAdmin, - TTL: time.Hour, - }) + cert, err := s.A.GenerateHostCert(sshca.HostCertificateRequest{ + CASigner: caSigner, + PublicHostKey: pub, + HostID: "00000000-0000-0000-0000-000000000000", + NodeName: "auth.example.com", + TTL: time.Hour, + Identity: sshca.Identity{ + ClusterName: "example.com", + SystemRole: types.RoleAdmin, + }, + }) require.NoError(t, err) certificate, err := sshutils.ParseCertificate(cert) @@ -102,7 +103,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"centos", "root"}, + Principals: []string{"centos", "root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -121,7 +122,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -137,7 +138,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -153,7 +154,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, }, @@ -170,7 +171,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { Identity: sshca.Identity{ Username: "user", Impersonator: impersonator, - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, PermitAgentForwarding: true, PermitPortForwarding: true, Roles: inRoles, @@ -195,7 +196,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ Username: "user", - AllowedLogins: []string{"root"}, + Principals: []string{"root"}, MFAVerified: "mfa-device-id", PreviousIdentityExpires: clock.Now().Add(time.Hour), }, @@ -219,7 +220,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { PublicUserKey: pub, // Required. Identity: sshca.Identity{ Username: "llama", // Required. - AllowedLogins: []string{"llama"}, // Required. + Principals: []string{"llama"}, // Required. DeviceID: devID, DeviceAssetTag: devTag, DeviceCredentialID: devCred, @@ -242,7 +243,7 @@ func (s *AuthSuite) GenerateUserCert(t *testing.T) { PublicUserKey: pub, // Required. Identity: sshca.Identity{ Username: "llama", // Required. - AllowedLogins: []string{"llama"}, // Required. + Principals: []string{"llama"}, // Required. GitHubUserID: githubUserID, GitHubUsername: githubUsername, }, diff --git a/lib/auth/testauthority/testauthority.go b/lib/auth/testauthority/testauthority.go index b58f9ac27493d..dbb14c56c20cb 100644 --- a/lib/auth/testauthority/testauthority.go +++ b/lib/auth/testauthority/testauthority.go @@ -28,7 +28,6 @@ import ( "github.com/gravitational/teleport/lib/auth/keygen" "github.com/gravitational/teleport/lib/cryptosuites" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" ) @@ -57,8 +56,8 @@ func (n *Keygen) GenerateKeyPair() (priv []byte, pub []byte, err error) { return privateKey.PrivateKeyPEM(), privateKey.MarshalSSHPublicKey(), nil } -func (n *Keygen) GenerateHostCert(c services.HostCertParams) ([]byte, error) { - return n.GenerateHostCertWithoutValidation(c) +func (n *Keygen) GenerateHostCert(req sshca.HostCertificateRequest) ([]byte, error) { + return n.GenerateHostCertWithoutValidation(req) } func (n *Keygen) GenerateUserCert(c sshca.UserCertificateRequest) ([]byte, error) { diff --git a/lib/client/client_store_test.go b/lib/client/client_store_test.go index 71239884aaaba..f62aaefeacf00 100644 --- a/lib/client/client_store_test.go +++ b/lib/client/client_store_test.go @@ -44,7 +44,6 @@ import ( "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/cryptosuites" "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" @@ -111,7 +110,7 @@ func (s *testAuthority) makeSignedKeyRing(t *testing.T, idx KeyRingIndex, makeEx TTL: ttl, Identity: sshca.Identity{ Username: idx.Username, - AllowedLogins: allowedLogins, + Principals: allowedLogins, PermitAgentForwarding: false, PermitPortForwarding: true, GitHubUserID: "1234567", @@ -311,13 +310,15 @@ func TestProxySSHConfig(t *testing.T) { caSigner, err := ssh.ParsePrivateKey(CAPriv) require.NoError(t, err) - hostCert, err := auth.keygen.GenerateHostCert(services.HostCertParams{ + hostCert, err := auth.keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: hostPub, HostID: "127.0.0.1", NodeName: "127.0.0.1", - ClusterName: "host-cluster-name", - Role: types.RoleNode, + Identity: sshca.Identity{ + ClusterName: "host-cluster-name", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) diff --git a/lib/client/identityfile/identity_test.go b/lib/client/identityfile/identity_test.go index 9d8eeb62a894d..fe1d9df9a9857 100644 --- a/lib/client/identityfile/identity_test.go +++ b/lib/client/identityfile/identity_test.go @@ -112,8 +112,8 @@ func newClientKeyRing(t *testing.T, modifiers ...func(*tlsca.Identity)) *client. CASigner: caSigner, PublicUserKey: ssh.MarshalAuthorizedKey(privateKey.SSHPublicKey()), Identity: sshca.Identity{ - Username: "testuser", - AllowedLogins: []string{"testuser"}, + Username: "testuser", + Principals: []string{"testuser"}, }, }) require.NoError(t, err) diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index a8dfdae28da95..b937812f49ddb 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -49,7 +49,6 @@ import ( "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/cryptosuites" "github.com/gravitational/teleport/lib/fixtures" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" @@ -366,17 +365,19 @@ func TestHostCertVerification(t *testing.T) { // Generate a host certificate for node with role "node". _, rootHostPub, err := keygen.GenerateKeyPair() require.NoError(t, err) - rootHostCertBytes, err := keygen.GenerateHostCert(services.HostCertParams{ + rootHostCertBytes, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: root.signer, PublicHostKey: rootHostPub, HostID: "5ff40d80-9007-4f28-8f49-7d4fda2f574d", NodeName: "server01", - Principals: []string{ - "127.0.0.1", + TTL: 1 * time.Hour, + Identity: sshca.Identity{ + Principals: []string{ + "127.0.0.1", + }, + ClusterName: "example.com", + SystemRole: types.RoleNode, }, - ClusterName: "example.com", - Role: types.RoleNode, - TTL: 1 * time.Hour, }) require.NoError(t, err) rootHostPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(rootHostCertBytes) @@ -384,14 +385,16 @@ func TestHostCertVerification(t *testing.T) { _, leafHostPub, err := keygen.GenerateKeyPair() require.NoError(t, err) - leafHostCertBytes, err := keygen.GenerateHostCert(services.HostCertParams{ + leafHostCertBytes, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: leaf.signer, PublicHostKey: leafHostPub, HostID: "620bb71c-c9eb-4f6d-9823-f7d9125ebb1d", NodeName: "server02", - ClusterName: "leaf.example.com", - Role: types.RoleNode, TTL: 1 * time.Hour, + Identity: sshca.Identity{ + ClusterName: "leaf.example.com", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) leafHostPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(leafHostCertBytes) @@ -620,14 +623,16 @@ func TestHostCertVerificationLoadAllCasProxyAddrEqClusterName(t *testing.T) { func mustGenerateHostPublicCert(t *testing.T, keygen *testauthority.Keygen, signer ssh.Signer, nodeName, clusterName string) ssh.PublicKey { _, leafHostPub, err := keygen.GenerateKeyPair() require.NoError(t, err) - leafHostCertBytes, err := keygen.GenerateHostCert(services.HostCertParams{ + leafHostCertBytes, err := keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: signer, PublicHostKey: leafHostPub, HostID: uuid.NewString(), NodeName: nodeName, - ClusterName: clusterName, - Role: types.RoleNode, TTL: 1 * time.Hour, + Identity: sshca.Identity{ + ClusterName: clusterName, + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) leafCerts, err := sshutils.ParseAuthorizedKeys([][]byte{leafHostCertBytes}) @@ -759,7 +764,7 @@ func (s *KeyAgentTestSuite) makeKeyRing(t *testing.T, username, proxyHost string TTL: ttl, Identity: sshca.Identity{ Username: username, - AllowedLogins: []string{username}, + Principals: []string{username}, PermitAgentForwarding: true, PermitPortForwarding: true, RouteToCluster: s.clusterName, diff --git a/lib/client/known_hosts_migrate_test.go b/lib/client/known_hosts_migrate_test.go index 612e7d3082f06..cba71bda212d6 100644 --- a/lib/client/known_hosts_migrate_test.go +++ b/lib/client/known_hosts_migrate_test.go @@ -28,7 +28,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/lib/auth/testauthority" - "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/sshca" ) type knownHostsMigrateTest struct { @@ -48,12 +48,14 @@ func generateHostCert(t *testing.T, s *knownHostsMigrateTest, clusterName string caSigner, err := ssh.ParsePrivateKey(CAPriv) require.NoError(t, err) - cert, err := s.keygen.GenerateHostCert(services.HostCertParams{ + cert, err := s.keygen.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, HostID: "127.0.0.1", NodeName: "127.0.0.1", - ClusterName: clusterName, PublicHostKey: hostPub, + Identity: sshca.Identity{ + ClusterName: clusterName, + }, }) require.NoError(t, err) diff --git a/lib/reversetunnel/srv_test.go b/lib/reversetunnel/srv_test.go index 8794a8323f0f1..678cb46a7aa72 100644 --- a/lib/reversetunnel/srv_test.go +++ b/lib/reversetunnel/srv_test.go @@ -38,7 +38,6 @@ import ( "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/testauthority" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/sshca" "github.com/gravitational/teleport/lib/utils" ) @@ -80,13 +79,15 @@ func TestServerKeyAuth(t *testing.T) { { desc: "host cert", key: func() ssh.PublicKey { - rawCert, err := ta.GenerateHostCert(services.HostCertParams{ + rawCert, err := ta.GenerateHostCert(sshca.HostCertificateRequest{ CASigner: caSigner, PublicHostKey: pub, HostID: "host-id", NodeName: con.User(), - ClusterName: "host-cluster-name", - Role: types.RoleNode, + Identity: sshca.Identity{ + ClusterName: "host-cluster-name", + SystemRole: types.RoleNode, + }, }) require.NoError(t, err) key, _, _, _, err := ssh.ParseAuthorizedKey(rawCert) @@ -111,7 +112,7 @@ func TestServerKeyAuth(t *testing.T) { TTL: time.Minute, Identity: sshca.Identity{ Username: con.User(), - AllowedLogins: []string{con.User()}, + Principals: []string{con.User()}, Roles: []string{"dev", "admin"}, RouteToCluster: "user-cluster-name", }, diff --git a/lib/services/authority.go b/lib/services/authority.go index 2345342b1195b..bd04c8c7c284a 100644 --- a/lib/services/authority.go +++ b/lib/services/authority.go @@ -23,14 +23,12 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" - "time" "github.com/gogo/protobuf/proto" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" @@ -279,46 +277,6 @@ func GetSSHCheckingKeys(ca types.CertAuthority) [][]byte { return out } -// HostCertParams defines all parameters needed to generate a host certificate -type HostCertParams struct { - // CASigner is the signer that will sign the public key of the host with the CA private key. - CASigner ssh.Signer - // PublicHostKey is the public key of the host - PublicHostKey []byte - // HostID is used by Teleport to uniquely identify a node within a cluster - HostID string - // Principals is a list of additional principals to add to the certificate. - Principals []string - // NodeName is the DNS name of the node - NodeName string - // ClusterName is the name of the cluster within which a node lives - ClusterName string - // Role identifies the role of a Teleport instance - Role types.SystemRole - // TTL defines how long a certificate is valid for - TTL time.Duration -} - -// Check checks parameters for errors -func (c HostCertParams) Check() error { - if c.CASigner == nil { - return trace.BadParameter("CASigner is required") - } - if c.HostID == "" && len(c.Principals) == 0 { - return trace.BadParameter("HostID [%q] or Principals [%q] are required", - c.HostID, c.Principals) - } - if c.ClusterName == "" { - return trace.BadParameter("ClusterName [%q] is required", c.ClusterName) - } - - if err := c.Role.Check(); err != nil { - return trace.Wrap(err) - } - - return nil -} - // CertPoolFromCertAuthorities returns a certificate pool from the TLS certificates // set up in the certificate authorities list, as well as the number of certificates // that were added to the pool. diff --git a/lib/srv/authhandlers_test.go b/lib/srv/authhandlers_test.go index 8e009819e2108..9c5ce5b43b1d7 100644 --- a/lib/srv/authhandlers_test.go +++ b/lib/srv/authhandlers_test.go @@ -220,8 +220,8 @@ func TestRBAC(t *testing.T) { CASigner: caSigner, PublicUserKey: ssh.MarshalAuthorizedKey(privateKey.SSHPublicKey()), Identity: sshca.Identity{ - Username: "testuser", - AllowedLogins: []string{"testuser"}, + Username: "testuser", + Principals: []string{"testuser"}, }, }) require.NoError(t, err) @@ -395,8 +395,8 @@ func TestRBACJoinMFA(t *testing.T) { PublicUserKey: privateKey.MarshalSSHPublicKey(), CertificateFormat: constants.CertificateFormatStandard, Identity: sshca.Identity{ - Username: username, - AllowedLogins: []string{username}, + Username: username, + Principals: []string{username}, Traits: wrappers.Traits{ teleport.TraitInternalPrefix: []string{""}, }, diff --git a/lib/sshca/identity.go b/lib/sshca/identity.go index 19f40bfdf336d..32e1ad5f8ffb8 100644 --- a/lib/sshca/identity.go +++ b/lib/sshca/identity.go @@ -35,22 +35,39 @@ import ( "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/utils" ) // Identity is a user identity. All identity fields map directly to an ssh certificate field. type Identity struct { + + // --- common identity fields --- + // ValidAfter is the unix timestamp that marks the start time for when the certificate should // be considered valid. ValidAfter uint64 // ValidBefore is the unix timestamp that marks the end time for when the certificate should // be considered valid. ValidBefore uint64 + // CertType indicates what type of cert this is (user or host). + CertType uint32 + // Principals is the list of SSH principals associated with the certificate (this means the + // list of allowed unix logins in the case of user certs). + Principals []string + + // --- host identity fields --- + + // ClusterName is the name of the cluster within which a node lives + ClusterName string + // SystemRole identifies the system role of a Teleport instance + SystemRole types.SystemRole + + // -- user identity fields --- + // Username is teleport username Username string // Impersonator is set when a user requests certificate for another user Impersonator string - // AllowedLogins is a list of SSH principals - AllowedLogins []string // PermitX11Forwarding permits X11 forwarding for this cert PermitX11Forwarding bool // PermitAgentForwarding permits agent forwarding for this cert @@ -67,7 +84,7 @@ type Identity struct { Traits wrappers.Traits // ActiveRequests tracks privilege escalation requests applied during // certificate construction. - ActiveRequests services.RequestIDs + ActiveRequests []string // MFAVerified is the UUID of an MFA device when this Identity was // confirmed immediately after an MFA check. MFAVerified string @@ -100,7 +117,7 @@ type Identity struct { // Machine ID bot. It is empty for human users. BotInstanceID string // AllowedResourceIDs lists the resources the user should be able to access. - AllowedResourceIDs string + AllowedResourceIDs []types.ResourceID // ConnectionDiagnosticID references the ConnectionDiagnostic that we should use to append traces when testing a Connection. ConnectionDiagnosticID string // PrivateKeyPolicy is the private key policy supported by this certificate. @@ -120,15 +137,6 @@ type Identity struct { GitHubUsername string } -// Check performs validation of certain fields in the identity. -func (i *Identity) Check() error { - if len(i.AllowedLogins) == 0 { - return trace.BadParameter("ssh user identity missing allowed logins") - } - - return nil -} - // Encode encodes the identity into an ssh certificate. Note that the returned certificate is incomplete // and must be have its public key set before signing. func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { @@ -140,18 +148,38 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { if validAfter == 0 { validAfter = uint64(time.Now().UTC().Add(-1 * time.Minute).Unix()) } + + if i.CertType == 0 { + return nil, trace.BadParameter("cannot encode ssh identity missing required field CertType") + } + cert := &ssh.Certificate{ // we have to use key id to identify teleport user KeyId: i.Username, - ValidPrincipals: i.AllowedLogins, + ValidPrincipals: i.Principals, ValidAfter: validAfter, ValidBefore: validBefore, - CertType: ssh.UserCert, + CertType: i.CertType, + } + + cert.Permissions.Extensions = make(map[string]string) + + if i.CertType == ssh.UserCert { + cert.Permissions.Extensions[teleport.CertExtensionPermitPTY] = "" + } + + // --- host extensions --- + + if sr := i.SystemRole.String(); sr != "" { + cert.Permissions.Extensions[utils.CertExtensionRole] = sr } - cert.Permissions.Extensions = map[string]string{ - teleport.CertExtensionPermitPTY: "", + + if i.ClusterName != "" { + cert.Permissions.Extensions[utils.CertExtensionAuthority] = i.ClusterName } + // --- user extensions --- + if i.PermitX11Forwarding { cert.Permissions.Extensions[teleport.CertExtensionPermitX11Forwarding] = "" } @@ -188,8 +216,12 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { if i.BotInstanceID != "" { cert.Permissions.Extensions[teleport.CertExtensionBotInstanceID] = i.BotInstanceID } - if i.AllowedResourceIDs != "" { - cert.Permissions.Extensions[teleport.CertExtensionAllowedResources] = i.AllowedResourceIDs + if len(i.AllowedResourceIDs) != 0 { + requestedResourcesStr, err := types.ResourceIDsToString(i.AllowedResourceIDs) + if err != nil { + return nil, trace.Wrap(err) + } + cert.Permissions.Extensions[teleport.CertExtensionAllowedResources] = requestedResourcesStr } if i.ConnectionDiagnosticID != "" { cert.Permissions.Extensions[teleport.CertExtensionConnectionDiagnosticID] = i.ConnectionDiagnosticID @@ -257,8 +289,11 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { if i.RouteToCluster != "" { cert.Permissions.Extensions[teleport.CertExtensionTeleportRouteToCluster] = i.RouteToCluster } - if !i.ActiveRequests.IsEmpty() { - requests, err := i.ActiveRequests.Marshal() + if len(i.ActiveRequests) != 0 { + reqs := services.RequestIDs{ + AccessRequests: i.ActiveRequests, + } + requests, err := reqs.Marshal() if err != nil { return nil, trace.Wrap(err) } @@ -271,14 +306,12 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) { // DecodeIdentity decodes an ssh certificate into an identity. func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { - if cert.CertType != ssh.UserCert { - return nil, trace.BadParameter("DecodeIdentity intended for use with user certs, got %v", cert.CertType) - } ident := &Identity{ - Username: cert.KeyId, - AllowedLogins: cert.ValidPrincipals, - ValidAfter: cert.ValidAfter, - ValidBefore: cert.ValidBefore, + Username: cert.KeyId, + Principals: cert.ValidPrincipals, + ValidAfter: cert.ValidAfter, + ValidBefore: cert.ValidBefore, + CertType: cert.CertType, } // clone the extension map and remove entries from the clone as they are processed so @@ -304,9 +337,19 @@ func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { return ok } - // ignore the permit pty extension, it's always set + // ignore the permit pty extension, teleport considers this permission implied for all users _, _ = takeExtension(teleport.CertExtensionPermitPTY) + // --- host extensions --- + + if v, ok := takeExtension(utils.CertExtensionRole); ok { + ident.SystemRole = types.SystemRole(v) + } + + ident.ClusterName = takeValue(utils.CertExtensionAuthority) + + // --- user extensions --- + ident.PermitX11Forwarding = takeBool(teleport.CertExtensionPermitX11Forwarding) ident.PermitAgentForwarding = takeBool(teleport.CertExtensionPermitAgentForwarding) ident.PermitPortForwarding = takeBool(teleport.CertExtensionPermitPortForwarding) @@ -335,7 +378,15 @@ func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { ident.BotName = takeValue(teleport.CertExtensionBotName) ident.BotInstanceID = takeValue(teleport.CertExtensionBotInstanceID) - ident.AllowedResourceIDs = takeValue(teleport.CertExtensionAllowedResources) + + if v, ok := takeExtension(teleport.CertExtensionAllowedResources); ok { + resourceIDs, err := types.ResourceIDsFromString(v) + if err != nil { + return nil, trace.BadParameter("failed to parse value %q for extension %q as resource IDs: %v", v, teleport.CertExtensionAllowedResources, err) + } + ident.AllowedResourceIDs = resourceIDs + } + ident.ConnectionDiagnosticID = takeValue(teleport.CertExtensionConnectionDiagnosticID) ident.PrivateKeyPolicy = keys.PrivateKeyPolicy(takeValue(teleport.CertExtensionPrivateKeyPolicy)) ident.DeviceID = takeValue(teleport.CertExtensionDeviceID) @@ -371,11 +422,11 @@ func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) { ident.RouteToCluster = takeValue(teleport.CertExtensionTeleportRouteToCluster) if v, ok := takeExtension(teleport.CertExtensionTeleportActiveRequests); ok { - var requests services.RequestIDs - if err := requests.Unmarshal([]byte(v)); err != nil { + var reqs services.RequestIDs + if err := reqs.Unmarshal([]byte(v)); err != nil { return nil, trace.BadParameter("failed to unmarshal value %q for extension %q as active requests: %v", v, teleport.CertExtensionTeleportActiveRequests, err) } - ident.ActiveRequests = requests + ident.ActiveRequests = reqs.AccessRequests } // aggregate all remaining extensions into the CertificateExtensions field diff --git a/lib/sshca/identity_test.go b/lib/sshca/identity_test.go index 5c7c6db75b3e8..ef5b721f993a4 100644 --- a/lib/sshca/identity_test.go +++ b/lib/sshca/identity_test.go @@ -26,31 +26,32 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/api/utils/keys" - "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils/testutils" ) func TestIdentityConversion(t *testing.T) { ident := &Identity{ - ValidAfter: 1, - ValidBefore: 2, - Username: "user", - Impersonator: "impersonator", - AllowedLogins: []string{"login1", "login2"}, - PermitX11Forwarding: true, - PermitAgentForwarding: true, - PermitPortForwarding: true, - Roles: []string{"role1", "role2"}, - RouteToCluster: "cluster", - Traits: wrappers.Traits{"trait1": []string{"value1"}, "trait2": []string{"value2"}}, - ActiveRequests: services.RequestIDs{ - AccessRequests: []string{uuid.NewString()}, - }, + ValidAfter: 1, + ValidBefore: 2, + CertType: ssh.UserCert, + ClusterName: "some-cluster", + SystemRole: types.RoleNode, + Username: "user", + Impersonator: "impersonator", + Principals: []string{"login1", "login2"}, + PermitX11Forwarding: true, + PermitAgentForwarding: true, + PermitPortForwarding: true, + Roles: []string{"role1", "role2"}, + RouteToCluster: "cluster", + Traits: wrappers.Traits{"trait1": []string{"value1"}, "trait2": []string{"value2"}}, + ActiveRequests: []string{uuid.NewString()}, MFAVerified: "mfa", PreviousIdentityExpires: time.Unix(12345, 0), LoginIP: "127.0.0.1", @@ -62,11 +63,16 @@ func TestIdentityConversion(t *testing.T) { Type: types.CertExtensionType_SSH, Mode: types.CertExtensionMode_EXTENSION, }}, - Renewable: true, - Generation: 3, - BotName: "bot", - BotInstanceID: "instance", - AllowedResourceIDs: "resource", + Renewable: true, + Generation: 3, + BotName: "bot", + BotInstanceID: "instance", + AllowedResourceIDs: []types.ResourceID{{ + ClusterName: "cluster", + Kind: types.KindKubePod, // must use a kube resource kind for parsing of sub-resource to work correctly + Name: "name", + SubResourceName: "sub/sub", + }}, ConnectionDiagnosticID: "diag", PrivateKeyPolicy: keys.PrivateKeyPolicy("policy"), DeviceID: "device", @@ -83,6 +89,9 @@ func TestIdentityConversion(t *testing.T) { "CertExtension.XXX_NoUnkeyedLiteral", "CertExtension.XXX_unrecognized", "CertExtension.XXX_sizecache", + "ResourceID.XXX_NoUnkeyedLiteral", + "ResourceID.XXX_unrecognized", + "ResourceID.XXX_sizecache", } require.True(t, testutils.ExhaustiveNonEmpty(ident, ignores...), "empty=%+v", testutils.FindAllEmpty(ident, ignores...)) diff --git a/lib/sshca/sshca.go b/lib/sshca/sshca.go index 15f5dcf6c1aeb..ac1642ce4dd33 100644 --- a/lib/sshca/sshca.go +++ b/lib/sshca/sshca.go @@ -26,7 +26,6 @@ import ( "golang.org/x/crypto/ssh" apidefaults "github.com/gravitational/teleport/api/defaults" - "github.com/gravitational/teleport/lib/services" ) // Authority implements minimal key-management facility for generating OpenSSH @@ -35,13 +34,48 @@ type Authority interface { // GenerateHostCert takes the private key of the CA, public key of the new host, // along with metadata (host ID, node name, cluster name, roles, and ttl) and generates // a host certificate. - GenerateHostCert(certParams services.HostCertParams) ([]byte, error) + GenerateHostCert(HostCertificateRequest) ([]byte, error) // GenerateUserCert generates user ssh certificate, it takes pkey as a signing // private key (user certificate authority) GenerateUserCert(UserCertificateRequest) ([]byte, error) } +// HostCertificateRequest is a request to generate a new ssh host certificate. +type HostCertificateRequest struct { + // CASigner is the signer that will sign the public key of the host with the CA private key + CASigner ssh.Signer + // PublicHostKey is the public key of the host + PublicHostKey []byte + // HostID is used by Teleport to uniquely identify a node within a cluster (this is used to help infill + // Identity.Princiapals and is not a standalone cert field). + HostID string + // NodeName is the DNS name of the node (this is used to help infill Identity.Princiapals and is not a + // standalone cert field). + NodeName string + // TTL defines how long a certificate is valid for + TTL time.Duration + // Identity is the host identity to be encoded in the certificate. + Identity Identity +} + +func (r *HostCertificateRequest) Check() error { + if r.CASigner == nil { + return trace.BadParameter("ssh host certificate request missing ca signer") + } + if r.HostID == "" && len(r.Identity.Principals) == 0 { + return trace.BadParameter("ssh host certificate request missing host ID and principals") + } + if r.Identity.ClusterName == "" { + return trace.BadParameter("ssh host certificate request missing cluster name") + } + if err := r.Identity.SystemRole.Check(); err != nil { + return trace.Wrap(err) + } + + return nil +} + // UserCertificateRequest is a request to generate a new ssh user certificate. type UserCertificateRequest struct { // CASigner is the signer that will sign the public key of the user with the CA private key @@ -64,8 +98,8 @@ func (r *UserCertificateRequest) CheckAndSetDefaults() error { if r.TTL < apidefaults.MinCertDuration { r.TTL = apidefaults.MinCertDuration } - if err := r.Identity.Check(); err != nil { - return trace.Wrap(err) + if len(r.Identity.Principals) == 0 { + return trace.BadParameter("ssh user identity missing allowed logins") } return nil