Add support for secret keys exposed as a file

Secret keys are often exposed as a file in containerized environments.
This commit is contained in:
Frédéric Guillot 2020-06-29 20:49:05 -07:00
parent 1d6b0491a7
commit d2f4ed93df
4 changed files with 71 additions and 4 deletions

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"miniflux.app/config"
"miniflux.app/logger" "miniflux.app/logger"
"miniflux.app/model" "miniflux.app/model"
"miniflux.app/storage" "miniflux.app/storage"
@ -15,8 +16,8 @@ import (
func createAdmin(store *storage.Storage) { func createAdmin(store *storage.Storage) {
user := model.NewUser() user := model.NewUser()
user.Username = os.Getenv("ADMIN_USERNAME") user.Username = config.Opts.AdminUsername()
user.Password = os.Getenv("ADMIN_PASSWORD") user.Password = config.Opts.AdminPassword()
user.IsAdmin = true user.IsAdmin = true
if user.Username == "" || user.Password == "" { if user.Username == "" || user.Password == "" {

View file

@ -39,6 +39,8 @@ const (
defaultCleanupRemoveSessionsDays = 30 defaultCleanupRemoveSessionsDays = 30
defaultProxyImages = "http-only" defaultProxyImages = "http-only"
defaultCreateAdmin = false defaultCreateAdmin = false
defaultAdminUsername = ""
defaultAdminPassword = ""
defaultOAuth2UserCreation = false defaultOAuth2UserCreation = false
defaultOAuth2ClientID = "" defaultOAuth2ClientID = ""
defaultOAuth2ClientSecret = "" defaultOAuth2ClientSecret = ""
@ -82,6 +84,8 @@ type Options struct {
schedulerEntryFrequencyMaxInterval int schedulerEntryFrequencyMaxInterval int
workerPoolSize int workerPoolSize int
createAdmin bool createAdmin bool
adminUsername string
adminPassword string
proxyImages string proxyImages string
oauth2UserCreationAllowed bool oauth2UserCreationAllowed bool
oauth2ClientID string oauth2ClientID string
@ -302,6 +306,16 @@ func (o *Options) CreateAdmin() bool {
return o.createAdmin 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. // ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (o *Options) ProxyImages() string { func (o *Options) ProxyImages() string {
return o.proxyImages 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("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL: %v\n", o.schedulerEntryFrequencyMinInterval))
builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages)) builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages))
builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin)) 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("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey))
builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed)) builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed))
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID)) builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID))

View file

@ -6,9 +6,11 @@ package config // import "miniflux.app/config"
import ( import (
"bufio" "bufio"
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
url_parser "net/url" url_parser "net/url"
"os" "os"
"strconv" "strconv"
@ -88,6 +90,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.listenAddr = parseString(value, defaultListenAddr) p.opts.listenAddr = parseString(value, defaultListenAddr)
case "DATABASE_URL": case "DATABASE_URL":
p.opts.databaseURL = parseString(value, defaultDatabaseURL) p.opts.databaseURL = parseString(value, defaultDatabaseURL)
case "DATABASE_URL_FILE":
p.opts.databaseURL = readSecretFile(value, defaultDatabaseURL)
case "DATABASE_MAX_CONNS": case "DATABASE_MAX_CONNS":
p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns) p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
case "DATABASE_MIN_CONNS": case "DATABASE_MIN_CONNS":
@ -148,14 +152,28 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.proxyImages = parseString(value, defaultProxyImages) p.opts.proxyImages = parseString(value, defaultProxyImages)
case "CREATE_ADMIN": case "CREATE_ADMIN":
p.opts.createAdmin = parseBool(value, defaultCreateAdmin) 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": case "POCKET_CONSUMER_KEY":
p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey) p.opts.pocketConsumerKey = parseString(value, defaultPocketConsumerKey)
case "POCKET_CONSUMER_KEY_FILE":
p.opts.pocketConsumerKey = readSecretFile(value, defaultPocketConsumerKey)
case "OAUTH2_USER_CREATION": case "OAUTH2_USER_CREATION":
p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation) p.opts.oauth2UserCreationAllowed = parseBool(value, defaultOAuth2UserCreation)
case "OAUTH2_CLIENT_ID": case "OAUTH2_CLIENT_ID":
p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID) p.opts.oauth2ClientID = parseString(value, defaultOAuth2ClientID)
case "OAUTH2_CLIENT_ID_FILE":
p.opts.oauth2ClientID = readSecretFile(value, defaultOAuth2ClientID)
case "OAUTH2_CLIENT_SECRET": case "OAUTH2_CLIENT_SECRET":
p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret) p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
case "OAUTH2_CLIENT_SECRET_FILE":
p.opts.oauth2ClientSecret = readSecretFile(value, defaultOAuth2ClientSecret)
case "OAUTH2_REDIRECT_URL": case "OAUTH2_REDIRECT_URL":
p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL) p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
case "OAUTH2_OIDC_DISCOVERY_ENDPOINT": case "OAUTH2_OIDC_DISCOVERY_ENDPOINT":
@ -235,3 +253,17 @@ func parseString(value string, fallback string) string {
} }
return value 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
}

View file

@ -124,6 +124,9 @@ Postgresql connection parameters\&.
.br .br
Default is "user=postgres password=postgres dbname=miniflux2 sslmode=disable"\&. Default is "user=postgres password=postgres dbname=miniflux2 sslmode=disable"\&.
.TP .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 .B DATABASE_MAX_CONNS
Maximum number of database connections (default is 20)\&. Maximum number of database connections (default is 20)\&.
.TP .TP
@ -188,9 +191,15 @@ OAuth2 provider to use\&. Only google is supported\&.
.B OAUTH2_CLIENT_ID .B OAUTH2_CLIENT_ID
OAuth2 client ID\&. OAuth2 client ID\&.
.TP .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 .B OAUTH2_CLIENT_SECRET
OAuth2 client secret\&. OAuth2 client secret\&.
.TP .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 .B OAUTH2_REDIRECT_URL
OAuth2 redirect URL\&. OAuth2 redirect URL\&.
.TP .TP
@ -207,14 +216,23 @@ Set to 1 to run database migrations\&.
Set to 1 to create an admin user from environment variables\&. Set to 1 to create an admin user from environment variables\&.
.TP .TP
.B ADMIN_USERNAME .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 .TP
.B ADMIN_PASSWORD .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 .TP
.B POCKET_CONSUMER_KEY .B POCKET_CONSUMER_KEY
Pocket consumer API key for all users\&. Pocket consumer API key for all users\&.
.TP .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 .B PROXY_IMAGES
Avoids mixed content warnings for external images: http-only, all, or none\&. Avoids mixed content warnings for external images: http-only, all, or none\&.
.br .br