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
5 changes: 3 additions & 2 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1597,8 +1597,9 @@ func (c *Client) SignDatabaseCSR(ctx context.Context, req *proto.DatabaseCSRRequ
return resp, nil
}

// GenerateDatabaseCert generates client certificate used by a database
// service to authenticate with the database instance.
// GenerateDatabaseCert generates a client certificate used by a database
// service to authenticate with the database instance, or a server certificate
// for configuring a self-hosted database, depending on the requester_name.
func (c *Client) GenerateDatabaseCert(ctx context.Context, req *proto.DatabaseCertRequest) (*proto.DatabaseCertResponse, error) {
resp, err := c.grpc.GenerateDatabaseCert(ctx, req)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ const (
// KindConnectionDiagnostic is a resource that tracks the result of testing a connection
KindConnectionDiagnostic = "connection_diagnostic"

// KindDatabaseCertificate is a resource to control Database Certificates generation
// KindDatabaseCertificate is a resource to control db CA cert
// generation.
KindDatabaseCertificate = "database_certificate"

// KindInstaller is a resource that holds a node installer script
Expand Down
19 changes: 16 additions & 3 deletions api/types/trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ const (
HostCA CertAuthType = "host"
// UserCA identifies the key as a user certificate authority
UserCA CertAuthType = "user"
// DatabaseCA is a certificate authority used in database access.
// DatabaseCA is a certificate authority used as a server CA in database
// access.
DatabaseCA CertAuthType = "db"
// DatabaseClientCA is a certificate authority used as a client CA in
// database access.
DatabaseClientCA CertAuthType = "db_client"
// OpenSSHCA is a certificate authority used when connecting to agentless nodes.
OpenSSHCA CertAuthType = "openssh"
// JWTSigner identifies type of certificate authority as JWT signer. In this
Expand All @@ -54,7 +58,16 @@ const (
)

// CertAuthTypes lists all certificate authority types.
var CertAuthTypes = []CertAuthType{HostCA, UserCA, DatabaseCA, OpenSSHCA, JWTSigner, SAMLIDPCA, OIDCIdPCA}
var CertAuthTypes = []CertAuthType{HostCA, UserCA, DatabaseCA, DatabaseClientCA, OpenSSHCA, JWTSigner, SAMLIDPCA, OIDCIdPCA}

// IsUnsupportedAuthorityErr returns whether an error is due to an unsupported
// CertAuthType.
func IsUnsupportedAuthorityErr(err error) bool {
return err != nil && trace.IsBadParameter(err) &&
strings.Contains(err.Error(), authTypeNotSupported)
}

const authTypeNotSupported string = "authority type is not supported"

// Check checks if certificate authority type value is correct
func (c CertAuthType) Check() error {
Expand All @@ -64,7 +77,7 @@ func (c CertAuthType) Check() error {
}
}

return trace.BadParameter("%q authority type is not supported", c)
return trace.BadParameter("%q %s", c, authTypeNotSupported)
}

// CertAuthID - id of certificate authority (it's type and domain name)
Expand Down
17 changes: 16 additions & 1 deletion integration/helpers/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) {
return nil, trace.Wrap(err)
}

dbClientCA, err := types.NewCertAuthority(types.CertAuthoritySpecV2{
Type: types.DatabaseClientCA,
ClusterName: s.SiteName,
ActiveKeys: types.CAKeySet{
TLS: []*types.TLSKeyPair{{
Key: s.PrivKey,
KeyType: types.PrivateKeyType_RAW,
Cert: s.TLSCACert,
}},
},
})
if err != nil {
return nil, trace.Wrap(err)
}

osshCA, err := types.NewCertAuthority(types.CertAuthoritySpecV2{
Type: types.OpenSSHCA,
ClusterName: s.SiteName,
Expand All @@ -203,7 +218,7 @@ func (s *InstanceSecrets) GetCAs() ([]types.CertAuthority, error) {
return nil, trace.Wrap(err)
}

return []types.CertAuthority{hostCA, userCA, dbCA, osshCA}, nil
return []types.CertAuthority{hostCA, userCA, dbCA, dbClientCA, osshCA}, nil
}

func (s *InstanceSecrets) AllowedLogins() []string {
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4876,7 +4876,7 @@ func newKeySet(ctx context.Context, keyStore *keystore.Manager, caID types.CertA
}
keySet.SSH = append(keySet.SSH, sshKeyPair)
keySet.TLS = append(keySet.TLS, tlsKeyPair)
case types.DatabaseCA:
case types.DatabaseCA, types.DatabaseClientCA:
// Database CA only contains TLS cert.
tlsKeyPair, err := keyStore.NewTLSKeyPair(ctx, caID.DomainName)
if err != nil {
Expand Down
39 changes: 27 additions & 12 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -4318,26 +4318,40 @@ func (a *ServerWithRoles) SignDatabaseCSR(ctx context.Context, req *proto.Databa
return a.authServer.SignDatabaseCSR(ctx, req)
}

// GenerateDatabaseCert generates a certificate used by a database service
// to authenticate with the database instance.
// GenerateDatabaseCert generates a client certificate used by a database
// service to authenticate with the database instance, or a server certificate
// for configuring a self-hosted database, depending on the requester_name.
//
// This certificate can be requested by:
//
// - Cluster administrator using "tctl auth sign --format=db" command locally
// on the auth server to produce a certificate for configuring a self-hosted
// database.
// - Remote user using "tctl auth sign --format=db" command with a remote
// proxy (e.g. Teleport Cloud), as long as they can impersonate system
// role Db.
// - Remote user using "tctl auth sign --format=db" command or
// /webapi/sites/:site/sign/db with a remote proxy (e.g. Teleport Cloud),
// as long as they can impersonate system role Db.
// - Database service when initiating connection to a database instance to
// produce a client certificate.
// - Proxy service when generating mTLS files to a database
func (a *ServerWithRoles) GenerateDatabaseCert(ctx context.Context, req *proto.DatabaseCertRequest) (*proto.DatabaseCertResponse, error) {
// Check if the User can `create` DatabaseCertificates
err := a.action(apidefaults.Namespace, types.KindDatabaseCertificate, types.VerbCreate)
err := a.checkAccessToGenerateDatabaseCert(types.KindDatabaseCertificate)
if err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GenerateDatabaseCert(ctx, req)
}

// checkAccessToGenerateDatabaseCert is a helper for checking db cert gen authz.
// Requester must have at least one of:
// - create: database_certificate or database_client_certificate.
// - built-in Admin or DB role.
// - allowed to impersonate the built-in DB role.
func (a *ServerWithRoles) checkAccessToGenerateDatabaseCert(resourceKind string) error {
const verb = types.VerbCreate
// Check if the User can `create` Database Certificates
err := a.action(apidefaults.Namespace, resourceKind, verb)
if err != nil {
if !trace.IsAccessDenied(err) {
return nil, trace.Wrap(err)
return trace.Wrap(err)
}

// Err is access denied, trying the old way
Expand All @@ -4347,12 +4361,13 @@ func (a *ServerWithRoles) GenerateDatabaseCert(ctx context.Context, req *proto.D
if !a.hasBuiltinRole(types.RoleDatabase, types.RoleAdmin) {
if err := a.canImpersonateBuiltinRole(types.RoleDatabase); err != nil {
log.WithError(err).Warnf("User %v tried to generate database certificate but does not have '%s' permission for '%s' kind, nor is allowed to impersonate %q system role",
a.context.User.GetName(), types.VerbCreate, types.KindDatabaseCertificate, types.RoleDatabase)
return nil, trace.AccessDenied(fmt.Sprintf("access denied. User must have '%s' permission for '%s' kind to generate the certificate ", types.VerbCreate, types.KindDatabaseCertificate))
a.context.User.GetName(), verb, resourceKind, types.RoleDatabase)
return trace.AccessDenied("access denied. User must have '%s' permission for '%s' kind to generate the certificate ",
verb, resourceKind)
}
}
}
return a.authServer.GenerateDatabaseCert(ctx, req)
return nil
}

// GenerateSnowflakeJWT generates JWT in the Snowflake required format.
Expand Down
28 changes: 16 additions & 12 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ func TestRoleRequestDenyReimpersonation(t *testing.T) {
}

// TestGenerateDatabaseCert makes sure users and services with appropriate
// permissions can generate certificates for self-hosted databases.
// permissions can generate database certificates.
func TestGenerateDatabaseCert(t *testing.T) {
t.Parallel()
ctx := context.Background()
Expand All @@ -957,22 +957,26 @@ func TestGenerateDatabaseCert(t *testing.T) {
require.NoError(t, srv.Auth().UpsertRole(ctx, roleDb))

tests := []struct {
desc string
identity TestIdentity
err string
desc string
identity TestIdentity
requester proto.DatabaseCertRequest_Requester
err string
}{
{
desc: "user can't sign database certs",
identity: TestUser(userWithoutAccess.GetName()),
err: "access denied",
desc: "user can't sign database certs",
identity: TestUser(userWithoutAccess.GetName()),
requester: proto.DatabaseCertRequest_TCTL,
err: "access denied",
},
{
desc: "user can impersonate Db and sign database certs",
identity: TestUser(userImpersonateDb.GetName()),
desc: "user can impersonate Db and sign database certs",
identity: TestUser(userImpersonateDb.GetName()),
requester: proto.DatabaseCertRequest_TCTL,
},
{
desc: "built-in admin can sign database certs",
identity: TestAdmin(),
desc: "built-in admin can sign database certs",
identity: TestAdmin(),
requester: proto.DatabaseCertRequest_TCTL,
},
{
desc: "database service can sign database certs",
Expand All @@ -992,7 +996,7 @@ func TestGenerateDatabaseCert(t *testing.T) {
client, err := srv.NewClient(test.identity)
require.NoError(t, err)

_, err = client.GenerateDatabaseCert(ctx, &proto.DatabaseCertRequest{CSR: csr})
_, err = client.GenerateDatabaseCert(ctx, &proto.DatabaseCertRequest{CSR: csr, RequesterName: test.requester})
if test.err != "" {
require.ErrorContains(t, err, test.err)
} else {
Expand Down
5 changes: 3 additions & 2 deletions lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,9 @@ type ClientI interface {
// sessions created by the SAML identity provider.
CreateSAMLIdPSession(context.Context, types.CreateSAMLIdPSessionRequest) (types.WebSession, error)

// GenerateDatabaseCert generates client certificate used by a database
// service to authenticate with the database instance.
// GenerateDatabaseCert generates a client certificate used by a database
// service to authenticate with the database instance, or a server certificate
// for configuring a self-hosted database, depending on the requester_name.
GenerateDatabaseCert(context.Context, *proto.DatabaseCertRequest) (*proto.DatabaseCertResponse, error)

// GetWebSession queries the existing web session described with req.
Expand Down
Loading