Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v15] Adds support to Firestore backends for custom databases #47584

Merged
merged 1 commit into from
Oct 16, 2024
Merged
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
6 changes: 5 additions & 1 deletion docs/pages/reference/backends.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,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 @@ -1301,7 +1305,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 @@ -320,26 +324,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 @@ -383,7 +397,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 @@ -1095,7 +1109,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
Loading