Allow users impersonating database service generate database certs (#7024)

This commit is contained in:
Roman Tkachenko 2021-05-25 14:11:35 -07:00 committed by GitHub
parent 593e0e6145
commit fc4c18f297
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 8 deletions

View file

@ -2628,17 +2628,49 @@ func (a *ServerWithRoles) SignDatabaseCSR(ctx context.Context, req *proto.Databa
}
// GenerateDatabaseCert generates a certificate used by a database service
// to authenticate with the database instance
// to authenticate with the database instance.
//
// 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.
// - Database service when initiating connection to a database instance to
// produce a client certificate.
func (a *ServerWithRoles) GenerateDatabaseCert(ctx context.Context, req *proto.DatabaseCertRequest) (*proto.DatabaseCertResponse, error) {
// This certificate can be requested only by a database service when
// initiating connection to a database instance, or by an admin when
// generating certificates for a database instance.
if !a.hasBuiltinRole(string(teleport.RoleDatabase)) && !a.hasBuiltinRole(string(teleport.RoleAdmin)) {
return nil, trace.AccessDenied("this request can only be executed by a database service or an admin")
// Check if this is a local cluster admin, or a datababase service, or a
// user that is allowed to impersonate database service.
if !a.hasBuiltinRole(string(types.RoleDatabase)) && !a.hasBuiltinRole(string(types.RoleAdmin)) {
if err := a.canImpersonateBuiltinRole(types.RoleDatabase); err != nil {
log.WithError(err).Warnf("User %v tried to generate database certificate but is not allowed to impersonate %q system role.",
a.context.User.GetName(), types.RoleDatabase)
return nil, trace.AccessDenied("access denied")
}
}
return a.authServer.GenerateDatabaseCert(ctx, req)
}
// canImpersonateBuiltinRole checks if the current user can impersonate the
// provided system role.
func (a *ServerWithRoles) canImpersonateBuiltinRole(role types.SystemRole) error {
roleCtx, err := NewBuiltinRoleContext(role)
if err != nil {
return trace.Wrap(err)
}
roleSet, ok := roleCtx.Checker.(BuiltinRoleSet)
if !ok {
return trace.BadParameter("expected BuiltinRoleSet, got %T", roleCtx.Checker)
}
err = a.context.Checker.CheckImpersonate(a.context.User, roleCtx.User, roleSet.RoleSet.WithoutImplicit())
if err != nil {
return trace.Wrap(err)
}
return nil
}
// GetAppServers gets all application servers.
func (a *ServerWithRoles) GetAppServers(ctx context.Context, namespace string, opts ...services.MarshalOption) ([]services.Server, error) {
if err := a.action(namespace, services.KindAppServer, services.VerbList); err != nil {

View file

@ -18,9 +18,11 @@ package auth
import (
"context"
"crypto/x509/pkix"
"testing"
"time"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
@ -60,6 +62,71 @@ func TestSSOUserCanReissueCert(t *testing.T) {
require.NoError(t, err)
}
// TestGenerateDatabaseCert makes sure users and services with appropriate
// permissions can generate certificates for self-hosted databases.
func TestGenerateDatabaseCert(t *testing.T) {
t.Parallel()
ctx := context.Background()
srv := newTestTLSServer(t)
// This user can't impersonate anyone and can't generate database certs.
userWithoutAccess, _, err := CreateUserAndRole(srv.Auth(), "user", []string{"role1"})
require.NoError(t, err)
// This user can impersonate system role Db.
userImpersonateDb, roleDb, err := CreateUserAndRole(srv.Auth(), "user-impersonate-db", []string{"role2"})
require.NoError(t, err)
roleDb.SetImpersonateConditions(types.Allow, types.ImpersonateConditions{
Users: []string{string(types.RoleDatabase)},
Roles: []string{string(types.RoleDatabase)},
})
require.NoError(t, srv.Auth().UpsertRole(ctx, roleDb))
tests := []struct {
desc string
identity TestIdentity
err string
}{
{
desc: "user can't sign database certs",
identity: TestUser(userWithoutAccess.GetName()),
err: "access denied",
},
{
desc: "user can impersonate Db and sign database certs",
identity: TestUser(userImpersonateDb.GetName()),
},
{
desc: "built-in admin can sign database certs",
identity: TestAdmin(),
},
{
desc: "database service can sign database certs",
identity: TestBuiltin(teleport.RoleDatabase),
},
}
// Generate CSR once for speed sake.
priv, _, err := srv.Auth().GenerateKeyPair("")
require.NoError(t, err)
csr, err := tlsca.GenerateCertificateRequestPEM(pkix.Name{CommonName: "test"}, priv)
require.NoError(t, err)
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
client, err := srv.NewClient(test.identity)
require.NoError(t, err)
_, err = client.GenerateDatabaseCert(ctx, &proto.DatabaseCertRequest{CSR: csr})
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
// TestSetAuthPreference tests the dynamic configuration rules described
// in rfd/0016-dynamic-configuration.md § Implementation.
func TestSetAuthPreference(t *testing.T) {

View file

@ -35,7 +35,12 @@ import (
// NewAdminContext returns new admin auth context
func NewAdminContext() (*Context, error) {
authContext, err := contextForBuiltinRole(BuiltinRole{Role: teleport.RoleAdmin, Username: fmt.Sprintf("%v", teleport.RoleAdmin)}, nil)
return NewBuiltinRoleContext(types.RoleAdmin)
}
// NewBuiltinRoleContext create auth context for the provided builtin role.
func NewBuiltinRoleContext(role types.SystemRole) (*Context, error) {
authContext, err := contextForBuiltinRole(BuiltinRole{Role: role, Username: fmt.Sprintf("%v", role)}, nil)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -18,8 +18,8 @@ package services
import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/defaults"
"github.com/pborman/uuid"
)

View file

@ -1124,6 +1124,17 @@ func (set RoleSet) HasRole(role string) bool {
return false
}
// WithoutImplicit returns this role set with default implicit role filtered out.
func (set RoleSet) WithoutImplicit() (out RoleSet) {
for _, r := range set {
if r.GetName() == teleport.DefaultImplicitRole {
continue
}
out = append(out, r)
}
return out
}
// AdjustSessionTTL will reduce the requested ttl to lowest max allowed TTL
// for this role set, otherwise it returns ttl unchanged
func (set RoleSet) AdjustSessionTTL(ttl time.Duration) time.Duration {