Skip to content

Commit

Permalink
User auto-provisioning support for MariaDB
Browse files Browse the repository at this point in the history
  • Loading branch information
greedy52 committed Oct 5, 2023
1 parent c334f4e commit 11c606e
Show file tree
Hide file tree
Showing 17 changed files with 1,814 additions and 1,431 deletions.
6 changes: 6 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,12 @@ message DatabaseSpecV3 {
message DatabaseAdminUser {
// Name is the username of the privileged database user.
string Name = 1 [(gogoproto.jsontag) = "name"];
// DefaultDatabase is the database that the privileged database user logs
// into by default.
//
// Depending on the database type, this database may be used to store
// procedures or data for managing database users.
string DefaultDatabase = 2 [(gogoproto.jsontag) = "default_database"];
}

// OracleOptions contains information about privileged database user used
Expand Down
4 changes: 4 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,10 @@ const (
// discovered databases.
DatabaseAdminLabel = TeleportNamespace + "/db-admin"

// DatabaseAdminDefaultDatabaseLabel is used to identify the database that
// the admin user logs into by default.
DatabaseAdminDefaultDatabaseLabel = TeleportNamespace + "/db-admin-default-database"

// cloudKubeClusterNameOverrideLabel is a cloud agnostic label key for
// overriding kubernetes cluster name in discovered cloud kube clusters.
// It's used for AWS, GCP, and Azure, but not exported to decouple the
Expand Down
20 changes: 15 additions & 5 deletions api/types/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ type Database interface {
// Copy returns a copy of this database resource.
Copy() *DatabaseV3
// GetAdminUser returns database privileged user information.
GetAdminUser() string
GetAdminUser() DatabaseAdminUser
// SupportsAutoUsers returns true if this database supports automatic
// user provisioning.
SupportsAutoUsers() bool
Expand Down Expand Up @@ -291,13 +291,23 @@ func (d *DatabaseV3) SetURI(uri string) {
}

// GetAdminUser returns database privileged user information.
func (d *DatabaseV3) GetAdminUser() string {
func (d *DatabaseV3) GetAdminUser() (ret DatabaseAdminUser) {
// First check the spec.
if d.Spec.AdminUser != nil {
return d.Spec.AdminUser.Name
ret = *d.Spec.AdminUser
}

// If it's not in the spec, check labels (for auto-discovered databases).
return d.Metadata.Labels[DatabaseAdminLabel]
// TODO Azure will require different labels.
if d.Origin() == OriginCloud {
if ret.Name == "" {
ret.Name = d.Metadata.Labels[DatabaseAdminLabel]
}
if ret.DefaultDatabase == "" {
ret.DefaultDatabase = d.Metadata.Labels[DatabaseAdminDefaultDatabaseLabel]
}
}
return
}

// GetOracle returns the Oracle options from spec.
Expand Down Expand Up @@ -841,7 +851,7 @@ func (d *DatabaseV3) CheckAndSetDefaults() error {

// Admin user (for automatic user provisioning) is only supported for
// PostgreSQL currently.
if d.GetAdminUser() != "" && !d.SupportsAutoUsers() {
if d.GetAdminUser().Name != "" && !d.SupportsAutoUsers() {
return trace.BadParameter("cannot set admin user on database %q: %v/%v databases don't support automatic user provisioning yet",
d.GetName(), d.GetProtocol(), d.GetType())
}
Expand Down
2,760 changes: 1,405 additions & 1,355 deletions api/types/types.pb.go

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,8 @@ func applyDatabasesConfig(fc *FileConfig, cfg *servicecfg.Config) error {
Mode: servicecfg.TLSMode(database.TLS.Mode),
},
AdminUser: servicecfg.DatabaseAdminUser{
Name: database.AdminUser.Name,
Name: database.AdminUser.Name,
DefaultDatabase: database.AdminUser.DefaultDatabase,
},
Oracle: convOracleOptions(database.Oracle),
AWS: servicecfg.DatabaseAWS{
Expand Down
6 changes: 6 additions & 0 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -1990,6 +1990,12 @@ type Database struct {
type DatabaseAdminUser struct {
// Name is the database admin username (e.g. "postgres").
Name string `yaml:"name"`
// DefaultDatabase is the database that the admin user logs into by
// default.
//
// Depending on the database type, this database may be used to store
// procedures or data for managing database users.
DefaultDatabase string `yaml:"default_database"`
}

// DatabaseAD contains database Active Directory configuration.
Expand Down
9 changes: 8 additions & 1 deletion lib/service/servicecfg/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ type Database struct {
type DatabaseAdminUser struct {
// Name is the database admin username (e.g. "postgres").
Name string
// DefaultDatabase is the database that the admin user logs into by
// default.
//
// Depending on the database type, this database may be used to store
// procedures or data for managing database users.
DefaultDatabase string
}

// OracleOptions are additional Oracle options.
Expand Down Expand Up @@ -158,7 +164,8 @@ func (d *Database) ToDatabase() (types.Database, error) {
ServerVersion: d.MySQL.ServerVersion,
},
AdminUser: &types.DatabaseAdminUser{
Name: d.AdminUser.Name,
Name: d.AdminUser.Name,
DefaultDatabase: d.AdminUser.DefaultDatabase,
},
Oracle: convOracleOptions(d.Oracle),
AWS: types.AWS{
Expand Down
82 changes: 70 additions & 12 deletions lib/srv/db/autousers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package db

import (
"context"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -77,16 +78,69 @@ func TestAutoUsersPostgres(t *testing.T) {
}

func TestAutoUsersMySQL(t *testing.T) {
tests := []mysqlAutoUserTest{
{
name: "MySQL with long name",
serverVersion: "8.0.28",
teleportUser: "a.very.long.name@teleport.example.com",
wantDatabaseUser: "tp-ZLhdP1FgxXsUvcVpG8ucVm/PCHg",
},
{
name: "MySQL not supported",
serverVersion: "5.7.42",
teleportUser: "a.very.long.name@teleport.example.com",
wantClientError: true,
},
{
name: "MariaDB",
serverVersion: "5.5.5-10.1.0-MariaDB",
teleportUser: "a.very.long.name@teleport.example.com",
wantDatabaseUser: "a.very.long.name@teleport.example.com",
},
{
name: "MariaDB with long name",
serverVersion: "5.5.5-10.1.0-MariaDB",
teleportUser: strings.Repeat("even-longer-name", 5) + "@teleport.example.com",
wantDatabaseUser: "tp-W+34lSjdNvyLfzOejQLRcbe0Rrs",
},
{
name: "MariaDB not supported",
serverVersion: "5.5.5-10.0.0-MariaDB",
teleportUser: "a.very.long.name@teleport.example.com",
wantClientError: true,
},
}

for _, test := range tests {
test := test
t.Run(test.name, test.Run)
}
}

type mysqlAutoUserTest struct {
name string
serverVersion string
teleportUser string
wantDatabaseUser string
wantClientError bool
}

func (m *mysqlAutoUserTest) Run(t *testing.T) {
t.Parallel()

ctx := context.Background()
testCtx := setupTestContext(ctx, t, withSelfHostedMySQL("mysql", withMySQLAdminUser("admin")))
testCtx := setupTestContext(
ctx,
t,
withSelfHostedMySQL("mysql",
withMySQLAdminUser("admin"),
withMySQLServerVersion(m.serverVersion),
),
)
go testCtx.startHandlingConnections()

// Use a long name to test hashed name is used in database.
teleportUser := "a.very.long.name@teleport.example.com"
wantDatabaseUser := "tp-ZLhdP1FgxXsUvcVpG8ucVm/PCHg"

// Create user with role that allows user provisioning.
_, role, err := auth.CreateUserAndRole(testCtx.tlsServer.Auth(), teleportUser, []string{"auto"}, nil)
_, role, err := auth.CreateUserAndRole(testCtx.tlsServer.Auth(), m.teleportUser, []string{"auto"}, nil)
require.NoError(t, err)
options := role.GetOptions()
options.CreateDatabaseUser = types.NewBoolOption(true)
Expand All @@ -97,17 +151,21 @@ func TestAutoUsersMySQL(t *testing.T) {
require.NoError(t, err)

// DatabaseUser must match identity.
_, err = testCtx.mysqlClient(teleportUser, "mysql", "user1")
_, err = testCtx.mysqlClient(m.teleportUser, "mysql", "user1")
require.Error(t, err)

// Try to connect to the database as this user.
mysqlConn, err := testCtx.mysqlClient(teleportUser, "mysql", teleportUser)
mysqlConn, err := testCtx.mysqlClient(m.teleportUser, "mysql", m.teleportUser)
if m.wantClientError {
require.Error(t, err)
return
}
require.NoError(t, err)

select {
case e := <-testCtx.mysql["mysql"].db.UserEventsCh():
require.Equal(t, teleportUser, e.TeleportUser)
require.Equal(t, wantDatabaseUser, e.DatabaseUser)
require.Equal(t, m.teleportUser, e.TeleportUser)
require.Equal(t, m.wantDatabaseUser, e.DatabaseUser)
require.Equal(t, []string{"reader", "writer"}, e.Roles)
require.True(t, e.Active)
case <-time.After(5 * time.Second):
Expand All @@ -121,8 +179,8 @@ func TestAutoUsersMySQL(t *testing.T) {
// Verify user was deactivated.
select {
case e := <-testCtx.mysql["mysql"].db.UserEventsCh():
require.Equal(t, teleportUser, e.TeleportUser)
require.Equal(t, wantDatabaseUser, e.DatabaseUser)
require.Equal(t, m.teleportUser, e.TeleportUser)
require.Equal(t, m.wantDatabaseUser, e.DatabaseUser)
require.False(t, e.Active)
case <-time.After(5 * time.Second):
t.Fatal("user not deactivated after 5s")
Expand Down
2 changes: 1 addition & 1 deletion lib/srv/db/common/autousers.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (a *UserProvisioner) Activate(ctx context.Context, sessionCtx *Session) (fu
"administrator")
}

if sessionCtx.Database.GetAdminUser() == "" {
if sessionCtx.Database.GetAdminUser().Name == "" {
return nil, trace.BadParameter(
"your Teleport role requires automatic database user provisioning " +
"but this database doesn't have admin user configured, contact " +
Expand Down

0 comments on commit 11c606e

Please sign in to comment.