Skip to content

Commit

Permalink
Adds support to Firestore backends for custom databases (#47540) (#47583
Browse files Browse the repository at this point in the history
)

Firestore has supported multiple databases in a project for a while
(see https://cloud.google.com/blog/products/databases/manage-multiple-firestore-databases-in-a-project),
however, Teleport only allowed using the default database in the project.
The DatabaseID is now exposed in the file config, and if provided
both the state and events backends will use the appropriate database.

Closes #37227.
  • Loading branch information
rosstimothy authored Oct 17, 2024
1 parent e2be133 commit e50b57c
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 10 deletions.
6 changes: 5 additions & 1 deletion docs/pages/reference/backends.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,10 @@ teleport:
# Name of the Firestore table.
collection_name: Example_TELEPORT_FIRESTORE_TABLE_NAME
# An optional database id to use. If not provided the default
# database for the project is used.
database_id: Example_TELEPORT_FIRESTORE_DATABASE_ID
credentials_path: /var/lib/teleport/gcs_creds
# This setting configures Teleport to send the audit events to three places:
Expand All @@ -1315,7 +1319,7 @@ teleport:
# database table, so attempting to use the same table for both will result in errors.
# When using highly available storage like Firestore, you should make sure that the list always specifies
# the High Availability storage method first, as this is what the Teleport web UI uses as its source of events to display.
audit_events_uri: ['firestore://Example_TELEPORT_FIRESTORE_EVENTS_TABLE_NAME', 'file:///var/lib/teleport/audit/events', 'stdout://']
audit_events_uri: ['firestore://Example_TELEPORT_FIRESTORE_EVENTS_TABLE_NAME?projectID=$PROJECT_ID&credentialsPath=$CREDENTIALS_PATH&databaseID=$DATABASE_ID', 'file:///var/lib/teleport/audit/events', 'stdout://']
# This setting configures Teleport to save the recorded sessions in GCP storage:
audit_sessions_uri: gs://Example_TELEPORT_GCS_BUCKET/records
Expand Down
29 changes: 22 additions & 7 deletions lib/backend/firestore/firestorebk.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package firestore

import (
"bytes"
"cmp"
"context"
"encoding/base64"
"errors"
Expand Down Expand Up @@ -77,6 +78,9 @@ type Config struct {
DisableExpiredDocumentPurge bool `json:"disable_expired_document_purge,omitempty"`
// EndPoint is used to point the Firestore clients at emulated Firestore storage.
EndPoint string `json:"endpoint,omitempty"`
// DatabaseID is the identifier of a specific Firestore database to use. If not specified, the
// default database for the ProjectID is used.
DatabaseID string `json:"database_id,omitempty"`
}

type backendConfig struct {
Expand Down Expand Up @@ -310,26 +314,36 @@ func (t ownerCredentials) GetRequestMetadata(context.Context, ...string) (map[st
func (t ownerCredentials) RequireTransportSecurity() bool { return false }

// CreateFirestoreClients creates a firestore admin and normal client given the supplied parameters
func CreateFirestoreClients(ctx context.Context, projectID string, endPoint string, credentialsFile string) (*apiv1.FirestoreAdminClient, *firestore.Client, error) {
func CreateFirestoreClients(ctx context.Context, projectID, database string, endpoint string, credentialsFile string) (*apiv1.FirestoreAdminClient, *firestore.Client, error) {
var args []option.ClientOption

if endPoint != "" {
if endpoint != "" {
args = append(args,
option.WithTelemetryDisabled(),
option.WithoutAuthentication(),
option.WithEndpoint(endPoint),
option.WithEndpoint(endpoint),
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
option.WithGRPCDialOption(grpc.WithPerRPCCredentials(ownerCredentials{})),
)
} else if credentialsFile != "" {
args = append(args, option.WithCredentialsFile(credentialsFile))
}

firestoreClient, err := firestore.NewClient(ctx, projectID, args...)
firestoreAdminClient, err := apiv1.NewFirestoreAdminClient(ctx, args...)
if err != nil {
return nil, nil, ConvertGRPCError(err)
}
firestoreAdminClient, err := apiv1.NewFirestoreAdminClient(ctx, args...)

if database == "" {
firestoreClient, err := firestore.NewClient(ctx, projectID, args...)
if err != nil {
return nil, nil, ConvertGRPCError(err)
}

return firestoreAdminClient, firestoreClient, nil
}

firestoreClient, err := firestore.NewClientWithDatabase(ctx, projectID, database, args...)
if err != nil {
return nil, nil, ConvertGRPCError(err)
}
Expand Down Expand Up @@ -373,7 +387,7 @@ func New(ctx context.Context, params backend.Params, options Options) (*Backend,
}

closeCtx, cancel := context.WithCancel(ctx)
firestoreAdminClient, firestoreClient, err := CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.EndPoint, cfg.CredentialsPath)
firestoreAdminClient, firestoreClient, err := CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.DatabaseID, cfg.EndPoint, cfg.CredentialsPath)
if err != nil {
cancel()
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -1084,7 +1098,8 @@ func ConvertGRPCError(err error, args ...interface{}) error {
}

func (b *Backend) getIndexParent() string {
return "projects/" + b.ProjectID + "/databases/(default)/collectionGroups/" + b.CollectionName
database := cmp.Or(b.backendConfig.Config.DatabaseID, "(default)")
return "projects/" + b.ProjectID + "/databases/" + database + "/collectionGroups/" + b.CollectionName
}

func (b *Backend) ensureIndexes(adminSvc *apiv1.FirestoreAdminClient) error {
Expand Down
8 changes: 6 additions & 2 deletions lib/events/firestoreevents/firestoreevents.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package firestoreevents

import (
"cmp"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -202,6 +203,8 @@ func (cfg *EventsConfig) SetFromURL(url *url.URL) error {
}
cfg.ProjectID = projectIDParamString

cfg.DatabaseID = url.Query().Get("databaseID")

eventRetentionPeriodParamString := url.Query().Get(eventRetentionPeriodPropertyKey)
if eventRetentionPeriodParamString == "" {
cfg.RetentionPeriod = defaultEventRetentionPeriod
Expand Down Expand Up @@ -286,7 +289,7 @@ func New(cfg EventsConfig) (*Log, error) {
})
l.Info("Initializing event backend.")
closeCtx, cancel := context.WithCancel(context.Background())
firestoreAdminClient, firestoreClient, err := firestorebk.CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.EndPoint, cfg.CredentialsPath)
firestoreAdminClient, firestoreClient, err := firestorebk.CreateFirestoreClients(closeCtx, cfg.ProjectID, cfg.DatabaseID, cfg.EndPoint, cfg.CredentialsPath)
if err != nil {
cancel()
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -576,7 +579,8 @@ type searchEventsFilter struct {
}

func (l *Log) getIndexParent() string {
return "projects/" + l.ProjectID + "/databases/(default)/collectionGroups/" + l.CollectionName
database := cmp.Or(l.Config.DatabaseID, "(default)")
return "projects/" + l.ProjectID + "/databases/" + database + "/collectionGroups/" + l.CollectionName
}

func (l *Log) ensureIndexes(adminSvc *apiv1.FirestoreAdminClient) error {
Expand Down

0 comments on commit e50b57c

Please sign in to comment.