diff --git a/platform/config/env/env.go b/platform/config/env/env.go index 557d3a73..aaae6eae 100644 --- a/platform/config/env/env.go +++ b/platform/config/env/env.go @@ -3,10 +3,13 @@ package env import ( "errors" "fmt" + "io/ioutil" "os" "strconv" "strings" "time" + + "github.com/xenolf/lego/log" ) // Get environment variables @@ -15,7 +18,7 @@ func Get(names ...string) (map[string]string, error) { var missingEnvVars []string for _, envVar := range names { - value := os.Getenv(envVar) + value := GetOrFile(envVar) if value == "" { missingEnvVars = append(missingEnvVars, envVar) } @@ -76,13 +79,13 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) { } func getOneWithFallback(main string, names ...string) (string, string) { - value := os.Getenv(main) + value := GetOrFile(main) if len(value) > 0 { return value, main } for _, name := range names { - value := os.Getenv(name) + value := GetOrFile(name) if len(value) > 0 { return value, main } @@ -94,7 +97,7 @@ func getOneWithFallback(main string, names ...string) (string, string) { // GetOrDefaultInt returns the given environment variable value as an integer. // Returns the default if the envvar cannot be coopered to an int, or is not found. func GetOrDefaultInt(envVar string, defaultValue int) int { - v, err := strconv.Atoi(os.Getenv(envVar)) + v, err := strconv.Atoi(GetOrFile(envVar)) if err != nil { return defaultValue } @@ -116,7 +119,7 @@ func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration // GetOrDefaultString returns the given environment variable value as a string. // Returns the default if the envvar cannot be find. func GetOrDefaultString(envVar string, defaultValue string) string { - v := os.Getenv(envVar) + v := GetOrFile(envVar) if len(v) == 0 { return defaultValue } @@ -127,10 +130,34 @@ func GetOrDefaultString(envVar string, defaultValue string) string { // GetOrDefaultBool returns the given environment variable value as a boolean. // Returns the default if the envvar cannot be coopered to a boolean, or is not found. func GetOrDefaultBool(envVar string, defaultValue bool) bool { - v, err := strconv.ParseBool(os.Getenv(envVar)) + v, err := strconv.ParseBool(GetOrFile(envVar)) if err != nil { return defaultValue } return v } + +// GetOrFile Attempts to resolve 'key' as an environment variable. +// Failing that, it will check to see if '_FILE' exists. +// If so, it will attempt to read from the referenced file to populate a value. +func GetOrFile(envVar string) string { + envVarValue := os.Getenv(envVar) + if envVarValue != "" { + return envVarValue + } + + fileVar := envVar + "_FILE" + fileVarValue := os.Getenv(fileVar) + if fileVarValue == "" { + return envVarValue + } + + fileContents, err := ioutil.ReadFile(fileVarValue) + if err != nil { + log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err) + return "" + } + + return string(fileContents) +} diff --git a/platform/config/env/env_test.go b/platform/config/env/env_test.go index d32d80fd..3bd5c403 100644 --- a/platform/config/env/env_test.go +++ b/platform/config/env/env_test.go @@ -1,6 +1,7 @@ package env import ( + "io/ioutil" "os" "testing" "time" @@ -273,3 +274,67 @@ func TestGetOrDefaultBool(t *testing.T) { }) } } + +func TestGetOrFile_ReadsEnvVars(t *testing.T) { + err := os.Setenv("TEST_LEGO_ENV_VAR", "lego_env") + require.NoError(t, err) + defer os.Unsetenv("TEST_LEGO_ENV_VAR") + + value := GetOrFile("TEST_LEGO_ENV_VAR") + + assert.Equal(t, "lego_env", value) +} + +func TestGetOrFile_ReadsFiles(t *testing.T) { + varEnvFileName := "TEST_LEGO_ENV_VAR_FILE" + varEnvName := "TEST_LEGO_ENV_VAR" + + err := os.Unsetenv(varEnvFileName) + require.NoError(t, err) + err = os.Unsetenv(varEnvName) + require.NoError(t, err) + + file, err := ioutil.TempFile("", "lego") + require.NoError(t, err) + defer os.Remove(file.Name()) + + err = ioutil.WriteFile(file.Name(), []byte("lego_file"), 0644) + require.NoError(t, err) + + err = os.Setenv(varEnvFileName, file.Name()) + require.NoError(t, err) + defer os.Unsetenv(varEnvFileName) + + value := GetOrFile(varEnvName) + + assert.Equal(t, "lego_file", value) +} + +func TestGetOrFile_PrefersEnvVars(t *testing.T) { + varEnvFileName := "TEST_LEGO_ENV_VAR_FILE" + varEnvName := "TEST_LEGO_ENV_VAR" + + err := os.Unsetenv(varEnvFileName) + require.NoError(t, err) + err = os.Unsetenv(varEnvName) + require.NoError(t, err) + + file, err := ioutil.TempFile("", "lego") + require.NoError(t, err) + defer os.Remove(file.Name()) + + err = ioutil.WriteFile(file.Name(), []byte("lego_file"), 0644) + require.NoError(t, err) + + err = os.Setenv(varEnvFileName, file.Name()) + require.NoError(t, err) + defer os.Unsetenv(varEnvFileName) + + err = os.Setenv(varEnvName, "lego_env") + require.NoError(t, err) + defer os.Unsetenv(varEnvName) + + value := GetOrFile(varEnvName) + + assert.Equal(t, "lego_env", value) +} diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index f46df26c..41aeb905 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -5,7 +5,6 @@ package alidns import ( "errors" "fmt" - "os" "strings" "time" @@ -57,7 +56,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = values["ALICLOUD_ACCESS_KEY"] config.SecretKey = values["ALICLOUD_SECRET_KEY"] - config.RegionID = os.Getenv("ALICLOUD_REGION_ID") + config.RegionID = env.GetOrFile("ALICLOUD_REGION_ID") return NewDNSProviderConfig(config) } diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index 5ddbc7af..d3cb76da 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -3,7 +3,6 @@ package auroradns import ( "errors" "fmt" - "os" "sync" "time" @@ -53,7 +52,7 @@ func NewDNSProvider() (*DNSProvider, error) { } config := NewDefaultConfig() - config.BaseURL = os.Getenv("AURORA_ENDPOINT") + config.BaseURL = env.GetOrFile("AURORA_ENDPOINT") config.UserID = values["AURORA_USER_ID"] config.Key = values["AURORA_KEY"] diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index 876117b4..3b40dc5e 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -5,7 +5,6 @@ package dnsimple import ( "errors" "fmt" - "os" "strconv" "strings" "time" @@ -45,8 +44,8 @@ type DNSProvider struct { // See: https://developer.dnsimple.com/v2/#authentication func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() - config.AccessToken = os.Getenv("DNSIMPLE_OAUTH_TOKEN") - config.BaseURL = os.Getenv("DNSIMPLE_BASE_URL") + config.AccessToken = env.GetOrFile("DNSIMPLE_OAUTH_TOKEN") + config.BaseURL = env.GetOrFile("DNSIMPLE_BASE_URL") return NewDNSProviderConfig(config) } diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go index 262a3020..c0f580d5 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "os" "strconv" "strings" "time" @@ -57,7 +56,7 @@ func NewDNSProvider() (*DNSProvider, error) { } var baseURL string - if sandbox, _ := strconv.ParseBool(os.Getenv("DNSMADEEASY_SANDBOX")); sandbox { + if sandbox, _ := strconv.ParseBool(env.GetOrFile("DNSMADEEASY_SANDBOX")); sandbox { baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0" } else { baseURL = "https://api.dnsmadeeasy.com/V2.0" diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 339c5ef6..4ffcc6e7 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "os" "time" "github.com/exoscale/egoscale" @@ -56,7 +55,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = values["EXOSCALE_API_KEY"] config.APISecret = values["EXOSCALE_API_SECRET"] - config.Endpoint = os.Getenv("EXOSCALE_ENDPOINT") + config.Endpoint = env.GetOrFile("EXOSCALE_ENDPOINT") return NewDNSProviderConfig(config) } diff --git a/providers/dns/lightsail/lightsail.go b/providers/dns/lightsail/lightsail.go index 7328c076..3b3fe678 100644 --- a/providers/dns/lightsail/lightsail.go +++ b/providers/dns/lightsail/lightsail.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "math/rand" - "os" "time" "github.com/aws/aws-sdk-go/aws" @@ -54,7 +53,7 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider func NewDefaultConfig() *Config { return &Config{ - DNSZone: os.Getenv("DNS_ZONE"), + DNSZone: env.GetOrFile("DNS_ZONE"), PropagationTimeout: env.GetOrDefaultSecond("LIGHTSAIL_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond("LIGHTSAIL_POLLING_INTERVAL", acme.DefaultPollingInterval), Region: env.GetOrDefaultString("LIGHTSAIL_REGION", "us-east-1"), diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 20c8b274..3e109886 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "os" "strings" "time" @@ -56,7 +55,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Username = values["NAMECOM_USERNAME"] config.APIToken = values["NAMECOM_API_TOKEN"] - config.Server = os.Getenv("NAMECOM_SERVER") + config.Server = env.GetOrFile("NAMECOM_SERVER") return NewDNSProviderConfig(config) } diff --git a/providers/dns/nifcloud/nifcloud.go b/providers/dns/nifcloud/nifcloud.go index 7e828aa9..6021cd51 100644 --- a/providers/dns/nifcloud/nifcloud.go +++ b/providers/dns/nifcloud/nifcloud.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "os" "time" "github.com/xenolf/lego/acme" @@ -52,7 +51,7 @@ func NewDNSProvider() (*DNSProvider, error) { } config := NewDefaultConfig() - config.BaseURL = os.Getenv("NIFCLOUD_DNS_ENDPOINT") + config.BaseURL = env.GetOrFile("NIFCLOUD_DNS_ENDPOINT") config.AccessKey = values["NIFCLOUD_ACCESS_KEY_ID"] config.SecretKey = values["NIFCLOUD_SECRET_ACCESS_KEY"] diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 792ee8b0..797b90a8 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net" - "os" "strings" "time" @@ -62,8 +61,8 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Nameserver = values["RFC2136_NAMESERVER"] - config.TSIGKey = os.Getenv("RFC2136_TSIG_KEY") - config.TSIGSecret = os.Getenv("RFC2136_TSIG_SECRET") + config.TSIGKey = env.GetOrFile("RFC2136_TSIG_KEY") + config.TSIGSecret = env.GetOrFile("RFC2136_TSIG_SECRET") return NewDNSProviderConfig(config) } diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index a1d4a8a3..9416c6a4 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "math/rand" - "os" "strings" "time" @@ -35,7 +34,7 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt("AWS_TTL", 10), PropagationTimeout: env.GetOrDefaultSecond("AWS_PROPAGATION_TIMEOUT", 2*time.Minute), PollingInterval: env.GetOrDefaultSecond("AWS_POLLING_INTERVAL", 4*time.Second), - HostedZoneID: os.Getenv("AWS_HOSTED_ZONE_ID"), + HostedZoneID: env.GetOrFile("AWS_HOSTED_ZONE_ID"), } } diff --git a/providers/dns/vegadns/vegadns.go b/providers/dns/vegadns/vegadns.go index 468c2a07..6b4cf7d1 100644 --- a/providers/dns/vegadns/vegadns.go +++ b/providers/dns/vegadns/vegadns.go @@ -5,7 +5,6 @@ package vegadns import ( "errors" "fmt" - "os" "strings" "time" @@ -50,8 +49,8 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.BaseURL = values["VEGADNS_URL"] - config.APIKey = os.Getenv("SECRET_VEGADNS_KEY") - config.APISecret = os.Getenv("SECRET_VEGADNS_SECRET") + config.APIKey = env.GetOrFile("SECRET_VEGADNS_KEY") + config.APISecret = env.GetOrFile("SECRET_VEGADNS_SECRET") return NewDNSProviderConfig(config) }