From d2f4ed93df5e1866c5389aa2a687a6bc3c944b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= <fred@miniflux.net> Date: Mon, 29 Jun 2020 20:49:05 -0700 Subject: [PATCH] Add support for secret keys exposed as a file Secret keys are often exposed as a file in containerized environments. --- cli/create_admin.go | 5 +++-- config/options.go | 16 ++++++++++++++++ config/parser.go | 32 ++++++++++++++++++++++++++++++++ miniflux.1 | 22 ++++++++++++++++++++-- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/cli/create_admin.go b/cli/create_admin.go index 35f0c4a5..8308c520 100644 --- a/cli/create_admin.go +++ b/cli/create_admin.go @@ -8,6 +8,7 @@ import ( "fmt" "os" + "miniflux.app/config" "miniflux.app/logger" "miniflux.app/model" "miniflux.app/storage" @@ -15,8 +16,8 @@ import ( func createAdmin(store *storage.Storage) { user := model.NewUser() - user.Username = os.Getenv("ADMIN_USERNAME") - user.Password = os.Getenv("ADMIN_PASSWORD") + user.Username = config.Opts.AdminUsername() + user.Password = config.Opts.AdminPassword() user.IsAdmin = true if user.Username == "" || user.Password == "" { diff --git a/config/options.go b/config/options.go index 513396a1..5fc6c36c 100644 --- a/config/options.go +++ b/config/options.go @@ -39,6 +39,8 @@ const ( defaultCleanupRemoveSessionsDays = 30 defaultProxyImages = "http-only" defaultCreateAdmin = false + defaultAdminUsername = "" + defaultAdminPassword = "" defaultOAuth2UserCreation = false defaultOAuth2ClientID = "" defaultOAuth2ClientSecret = "" @@ -82,6 +84,8 @@ type Options struct { schedulerEntryFrequencyMaxInterval int workerPoolSize int createAdmin bool + adminUsername string + adminPassword string proxyImages string oauth2UserCreationAllowed bool oauth2ClientID string @@ -302,6 +306,16 @@ func (o *Options) CreateAdmin() bool { return o.createAdmin } +// AdminUsername returns the admin username if defined. +func (o *Options) AdminUsername() string { + return o.adminUsername +} + +// AdminPassword returns the admin password if defined. +func (o *Options) AdminPassword() string { + return o.adminPassword +} + // ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy. func (o *Options) ProxyImages() string { return o.proxyImages @@ -378,6 +392,8 @@ func (o *Options) String() string { builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL: %v\n", o.schedulerEntryFrequencyMinInterval)) builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages)) builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin)) + builder.WriteString(fmt.Sprintf("ADMIN_USERNAME: %v\n", o.adminUsername)) + builder.WriteString(fmt.Sprintf("ADMIN_PASSWORD: %v\n", o.adminPassword)) builder.WriteString(fmt.Sprintf("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey)) builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed)) builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID)) diff --git a/config/parser.go b/config/parser.go index 810ce56b..77b74357 100644 --- a/config/parser.go +++ b/config/parser.go @@ -6,9 +6,11 @@ package config // import "miniflux.app/config" import ( "bufio" + "bytes" "errors" "fmt" "io" + "io/ioutil" url_parser "net/url" "os" "strconv" @@ -88,6 +90,8 @@ func (p *Parser) parseLines(lines []string) (err error) { p.opts.listenAddr = parseString(value, defaultListenAddr) case "DATABASE_URL": p.opts.databaseURL = parseString(value, defaultDatabaseURL) + case "DATABASE_URL_FILE": + p.opts.databaseURL = readSecretFile(value, defaultDatabaseURL) case "DATABASE_MAX_CONNS": p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns) case "DATABASE_MIN_CONNS": @@ -148,14 +152,28 @@ func (p *Parser) parseLines(lines []string) (err error) { p.opts.proxyImages = parseString(value, defaultProxyImages) case "CREATE_ADMIN": p.opts.createAdmin = parseBool(value, defaultCreateAdmin) + case "ADMIN_USERNAME": + p.opts.adminUsername = parseString(value, defaultAdminUsername) + case "ADMIN_USERNAME_FILE": + p.opts.adminUsername = readSecretFile(value, defaultAdminUsername) + case "ADMIN_PASSWORD": + p.opts.adminPassword = parseString(value, defaultAdminPassword) + case "ADMIN_PASSWORD_FILE": + p.opts.adminPassword = readSecretFile(value, defaultAdminPassword) case "POCKET_CONSUMER_KEY": p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey) + case "POCKET_CONSUMER_KEY_FILE": + p.opts.pocketConsumerKey = readSecretFile(value, defaultPocketConsumerKey) case "OAUTH2_USER_CREATION": p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation) case "OAUTH2_CLIENT_ID": p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID) + case "OAUTH2_CLIENT_ID_FILE": + p.opts.oauth2ClientID = readSecretFile(value, defaultOAuth2ClientID) case "OAUTH2_CLIENT_SECRET": p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret) + case "OAUTH2_CLIENT_SECRET_FILE": + p.opts.oauth2ClientSecret = readSecretFile(value, defaultOAuth2ClientSecret) case "OAUTH2_REDIRECT_URL": p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL) case "OAUTH2_OIDC_DISCOVERY_ENDPOINT": @@ -235,3 +253,17 @@ func parseString(value string, fallback string) string { } return value } + +func readSecretFile(filename, fallback string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return fallback + } + + value := string(bytes.TrimSpace(data)) + if value == "" { + return fallback + } + + return value +} diff --git a/miniflux.1 b/miniflux.1 index fc1faa54..2f57f098 100644 --- a/miniflux.1 +++ b/miniflux.1 @@ -124,6 +124,9 @@ Postgresql connection parameters\&. .br Default is "user=postgres password=postgres dbname=miniflux2 sslmode=disable"\&. .TP +.B DATABASE_URL_FILE +Path to a secret key exposed as a file, it should contain $DATABASE_URL value\&. +.TP .B DATABASE_MAX_CONNS Maximum number of database connections (default is 20)\&. .TP @@ -188,9 +191,15 @@ OAuth2 provider to use\&. Only google is supported\&. .B OAUTH2_CLIENT_ID OAuth2 client ID\&. .TP +.B OAUTH2_CLIENT_ID_FILE +Path to a secret key exposed as a file, it should contain $OAUTH2_CLIENT_ID value\&. +.TP .B OAUTH2_CLIENT_SECRET OAuth2 client secret\&. .TP +.B OAUTH2_CLIENT_SECRET_FILE +Path to a secret key exposed as a file, it should contain $OAUTH2_CLIENT_SECRET value\&. +.TP .B OAUTH2_REDIRECT_URL OAuth2 redirect URL\&. .TP @@ -207,14 +216,23 @@ Set to 1 to run database migrations\&. Set to 1 to create an admin user from environment variables\&. .TP .B ADMIN_USERNAME -Admin user login, used only if \fBCREATE_ADMIN\fR is enabled\&. +Admin user login, used only if $CREATE_ADMIN is enabled\&. +.TP +.B ADMIN_USERNAME_FILE +Path to a secret key exposed as a file, it should contain $ADMIN_USERNAME value\&. .TP .B ADMIN_PASSWORD -Admin user password, used only if \fBCREATE_ADMIN\fR is enabled\&. +Admin user password, used only if $CREATE_ADMIN is enabled\&. +.TP +.B ADMIN_PASSWORD_FILE +Path to a secret key exposed as a file, it should contain $ADMIN_PASSWORD value\&. .TP .B POCKET_CONSUMER_KEY Pocket consumer API key for all users\&. .TP +.B POCKET_CONSUMER_KEY_FILE +Path to a secret key exposed as a file, it should contain $POCKET_CONSUMER_KEY value\&. +.TP .B PROXY_IMAGES Avoids mixed content warnings for external images: http-only, all, or none\&. .br -- GitLab