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:
parent
1d6b0491a7
commit
d2f4ed93df
4 changed files with 71 additions and 4 deletions
|
@ -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 == "" {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
22
miniflux.1
22
miniflux.1
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue