diff --git a/cmd/server/server.go b/cmd/server/server.go index f63c6fdb5e..6e510893df 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -390,8 +390,14 @@ func (s *server) createDatastore(ctx context.Context, cfg *config.ControlPlaneSp options := []mongodb.Option{ mongodb.WithLogger(logger), } - return mongodb.NewMongoDB(ctx, mdConfig.URL, mdConfig.Database, options...) - + if mdConfig.UsernameFile != "" || mdConfig.PasswordFile != "" { + options = append(options, mongodb.WithAuthenticationFile(mdConfig.UsernameFile, mdConfig.PasswordFile)) + } + return mongodb.NewMongoDB( + ctx, + mdConfig.URL, + mdConfig.Database, + options...) default: return nil, fmt.Errorf("unknown datastore type %q", cfg.Datastore.Type) } diff --git a/pkg/app/ops/cmd/server/server.go b/pkg/app/ops/cmd/server/server.go index 2623b74033..71b0ae46cc 100644 --- a/pkg/app/ops/cmd/server/server.go +++ b/pkg/app/ops/cmd/server/server.go @@ -156,8 +156,15 @@ func (s *server) createDatastore(ctx context.Context, cfg *config.ControlPlaneSp options := []mongodb.Option{ mongodb.WithLogger(logger), } - return mongodb.NewMongoDB(ctx, mdConfig.URL, mdConfig.Database, options...) - + if mdConfig.UsernameFile != "" || mdConfig.PasswordFile != "" { + options = append(options, mongodb.WithAuthenticationFile(mdConfig.UsernameFile, mdConfig.PasswordFile)) + } + return mongodb.NewMongoDB( + ctx, + mdConfig.URL, + mdConfig.Database, + options..., + ) default: return nil, fmt.Errorf("unknown datastore type %q", cfg.Datastore.Type) } diff --git a/pkg/config/control_plane.go b/pkg/config/control_plane.go index f0721b345b..f02ab09559 100644 --- a/pkg/config/control_plane.go +++ b/pkg/config/control_plane.go @@ -211,6 +211,8 @@ type DataStoreMongoDBConfig struct { // The url of MongoDB. All of credentials can be specified via this field. URL string `json:"url"` // The name of the database. + // Also set Database as 'AuthSource' (default as 'admin' or '$external') when UsernameFle || PasswordFile specify + // Ref: https://github.com/mongodb/mongo-go-driver/blob/9e2aca8afd8821e6b068cc2f25192bc640d90a0d/mongo/client.go#L390 Database string `json:"database"` // The path to the username file. // For those who don't want to include the username in the URL. diff --git a/pkg/datastore/mongodb/mongodb.go b/pkg/datastore/mongodb/mongodb.go index 83ca290d5c..99cb06c4ac 100644 --- a/pkg/datastore/mongodb/mongodb.go +++ b/pkg/datastore/mongodb/mongodb.go @@ -17,6 +17,8 @@ package mongodb import ( "context" "errors" + "fmt" + "io/ioutil" "strings" "go.mongodb.org/mongo-driver/bson" @@ -27,17 +29,23 @@ import ( "github.com/pipe-cd/pipe/pkg/datastore" ) -type MongoDB struct { - ctx context.Context - client *mongo.Client - database string +const ( + // Scram type ref https://github.com/mongodb/mongo-go-driver/blob/9e2aca8afd8821e6b068cc2f25192bc640d90a0d/mongo/client_examples_test.go#L119 + // Default + scram string = "SCRAM" +) - logger *zap.Logger +type MongoDB struct { + ctx context.Context + client *mongo.Client + database string + logger *zap.Logger + authMechanism string + usernameFile string + passwordFile string } func NewMongoDB(ctx context.Context, url, database string, opts ...Option) (*MongoDB, error) { - // TODO: Enable to specify username and password via file. - // Need to check if it overrides AuthMechanism etc. m := &MongoDB{ ctx: ctx, database: database, @@ -49,6 +57,15 @@ func NewMongoDB(ctx context.Context, url, database string, opts ...Option) (*Mon m.logger = m.logger.Named("mongodb") clientOpts := options.Client().ApplyURI(url) + + if m.authMechanism != "" { + credential, err := m.determineCredential() + if err != nil { + return nil, err + } + clientOpts.SetAuth(*credential) + } + client, err := mongo.Connect(ctx, clientOpts) if err != nil { return nil, err @@ -59,6 +76,14 @@ func NewMongoDB(ctx context.Context, url, database string, opts ...Option) (*Mon type Option func(*MongoDB) +func WithAuthenticationFile(usernameFile, passwordFile string) Option { + return func(m *MongoDB) { + m.usernameFile = usernameFile + m.passwordFile = passwordFile + m.authMechanism = scram + } +} + func WithLogger(logger *zap.Logger) Option { return func(s *MongoDB) { s.logger = logger @@ -227,3 +252,25 @@ func (m *MongoDB) Close() error { func makePrimaryKeyFilter(id string) bson.D { return bson.D{{"_id", id}} } + +func (m *MongoDB) determineCredential() (*options.Credential, error) { + switch m.authMechanism { + case scram: + username, err := ioutil.ReadFile(m.usernameFile) + if err != nil { + return nil, fmt.Errorf("failed to read username file: %w", err) + } + password, err := ioutil.ReadFile(m.passwordFile) + if err != nil { + return nil, fmt.Errorf("failed to read password file: %w", err) + } + + return &options.Credential{ + Username: strings.TrimRight(string(username), "\n"), + Password: strings.TrimRight(string(password), "\n"), + AuthSource: m.database, + }, nil + default: + return nil, fmt.Errorf("unsupported %q authMechanism credential", m.authMechanism) + } +}