Repository: xenolf/lego
Pull request #814 (2019-03-03 19:58:54)

oraclecloud: better way to get private key.
- replace `OCI_PRIVKEY_BASE64` by `OCI_PRIVKEY_FILE` and `OCI_PRIVKEY` (base64 encoded)



Patch:
diff --git a/cmd/cmd_dnshelp.go b/cmd/cmd_dnshelp.go
index e54058aa..99873435 100644
--- a/cmd/cmd_dnshelp.go
+++ b/cmd/cmd_dnshelp.go
@@ -70,7 +70,7 @@ Here is an example bash command using the CloudFlare DNS provider:
 	fmt.Fprintln(w, "\tnetcup:\tNETCUP_CUSTOMER_NUMBER, NETCUP_API_KEY, NETCUP_API_PASSWORD")
 	fmt.Fprintln(w, "\tnifcloud:\tNIFCLOUD_ACCESS_KEY_ID, NIFCLOUD_SECRET_ACCESS_KEY")
 	fmt.Fprintln(w, "\tns1:\tNS1_API_KEY")
-	fmt.Fprintln(w, "\toraclecloud:\tOCI_PRIVKEY_BASE64, OCI_PRIVKEY_PASS, OCI_TENANCY_OCID, OCI_USER_OCID, OCI_PUBKEY_FINGERPRINT, OCI_REGION, OCI_COMPARTMENT_OCID")
+	fmt.Fprintln(w, "\toraclecloud:\tOCI_PRIVKEY_FILE, OCI_PRIVKEY_PASS, OCI_TENANCY_OCID, OCI_USER_OCID, OCI_PUBKEY_FINGERPRINT, OCI_REGION, OCI_COMPARTMENT_OCID")
 	fmt.Fprintln(w, "\totc:\tOTC_USER_NAME, OTC_PASSWORD, OTC_PROJECT_NAME, OTC_DOMAIN_NAME, OTC_IDENTITY_ENDPOINT")
 	fmt.Fprintln(w, "\tovh:\tOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY")
 	fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL")
diff --git a/providers/dns/oraclecloud/README.md b/providers/dns/oraclecloud/README.md
index 031685b5..476882e0 100644
--- a/providers/dns/oraclecloud/README.md
+++ b/providers/dns/oraclecloud/README.md
@@ -1,9 +1,9 @@
 # Export shell-env for OracleCloud
 
-in Bash
+## In Bash
 
 ```
-export OCI_PRIVKEY_BASE64=`base64 ~/.oci/oci_api_key.pem`
+export OCI_PRIVKEY_FILE="/.oci/oci_api_key.pem"
 export OCI_PRIVKEY_PASS="secret"
 export OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret"
 export OCI_USER_OCID="ocid1.user.oc1..secret"
@@ -12,11 +12,31 @@ export OCI_REGION="us-phoenix-1"
 export OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret"
 ```
 
-in Fish
+```
+export OCI_PRIVKEY=`base64 ~/.oci/oci_api_key.pem`
+export OCI_PRIVKEY_PASS="secret"
+export OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret"
+export OCI_USER_OCID="ocid1.user.oc1..secret"
+export OCI_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
+export OCI_REGION="us-phoenix-1"
+export OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret"
+```
+
+## In Fish
+
+```
+set -x OCI_PRIVKEY_FILE '~/.oci/oci_api_key.pem'
+set -x OCI_PRIVKEY_PASS 'secret'
+set -x OCI_TENANCY_OCID 'ocid1.tenancy.oc1..secret'
+set -x OCI_USER_OCID 'ocid1.user.oc1..secret'
+set -x OCI_PUBKEY_FINGERPRINT '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'
+set -x OCI_REGION 'us-phoenix-1'
+set -x OCI_COMPARTMENT_OCID 'ocid1.tenancy.oc1..secret'
+```
 
 ```
 set IFS
-set -x OCI_PRIVKEY_BASE64 (base64 ~/.oci/oci_api_key.pem)
+set -x OCI_PRIVKEY (base64 ~/.oci/oci_api_key.pem)
 set IFS \n" "\t
 
 set -x OCI_PRIVKEY_PASS 'secret'
diff --git a/providers/dns/oraclecloud/configprovider.go b/providers/dns/oraclecloud/configprovider.go
index bbc78279..57ea4206 100644
--- a/providers/dns/oraclecloud/configprovider.go
+++ b/providers/dns/oraclecloud/configprovider.go
@@ -4,13 +4,15 @@ import (
 	"crypto/rsa"
 	"encoding/base64"
 	"fmt"
+	"io/ioutil"
+	"os"
 
 	"github.com/oracle/oci-go-sdk/common"
 	"github.com/xenolf/lego/platform/config/env"
 )
 
 const (
-	ociPrivkeyBase64     = "OCI_PRIVKEY_BASE64"
+	ociPrivkey           = "OCI_PRIVKEY"
 	ociPrivkeyPass       = "OCI_PRIVKEY_PASS"
 	ociTenancyOCID       = "OCI_TENANCY_OCID"
 	ociUserOCID          = "OCI_USER_OCID"
@@ -31,12 +33,12 @@ func newConfigProvider(values map[string]string) *configProvider {
 }
 
 func (p *configProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
-	privateKeyDecoded, err := base64.StdEncoding.DecodeString(p.values[ociPrivkeyBase64])
+	privateKey, err := getPrivateKey(ociPrivkey)
 	if err != nil {
 		return nil, err
 	}
 
-	return common.PrivateKeyFromBytes(privateKeyDecoded, common.String(p.privateKeyPassphrase))
+	return common.PrivateKeyFromBytes(privateKey, common.String(p.privateKeyPassphrase))
 }
 
 func (p *configProvider) KeyID() (string, error) {
@@ -73,3 +75,27 @@ func (p *configProvider) KeyFingerprint() (string, error) {
 func (p *configProvider) Region() (string, error) {
 	return p.values[ociRegion], nil
 }
+
+func getPrivateKey(envVar string) ([]byte, error) {
+	envVarValue := os.Getenv(envVar)
+	if envVarValue != "" {
+		bytes, err := base64.StdEncoding.DecodeString(envVarValue)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read base64 value %s (defined by env var %s): %s", envVarValue, envVar, err)
+		}
+		return bytes, nil
+	}
+
+	fileVar := envVar + "_FILE"
+	fileVarValue := os.Getenv(fileVar)
+	if fileVarValue == "" {
+		return nil, fmt.Errorf("no value provided for: %s or %s", envVar, fileVar)
+	}
+
+	fileContents, err := ioutil.ReadFile(fileVarValue)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
+	}
+
+	return fileContents, nil
+}
diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go
index eee59720..ee30e5d2 100644
--- a/providers/dns/oraclecloud/oraclecloud.go
+++ b/providers/dns/oraclecloud/oraclecloud.go
@@ -43,7 +43,7 @@ type DNSProvider struct {
 
 // NewDNSProvider returns a DNSProvider instance configured for OracleCloud.
 func NewDNSProvider() (*DNSProvider, error) {
-	values, err := env.Get(ociPrivkeyBase64, ociTenancyOCID, ociUserOCID, ociPubkeyFingerprint, ociRegion, "OCI_COMPARTMENT_OCID")
+	values, err := env.Get(ociPrivkey, ociTenancyOCID, ociUserOCID, ociPubkeyFingerprint, ociRegion, "OCI_COMPARTMENT_OCID")
 	if err != nil {
 		return nil, fmt.Errorf("oraclecloud: %v", err)
 	}


Test patch:
diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go
index 25cea905..52f38956 100644
--- a/providers/dns/oraclecloud/oraclecloud_test.go
+++ b/providers/dns/oraclecloud/oraclecloud_test.go
@@ -6,6 +6,8 @@ import (
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
+	"io/ioutil"
+	"os"
 	"testing"
 	"time"
 
@@ -15,7 +17,8 @@ import (
 )
 
 var envTest = tester.NewEnvTest(
-	ociPrivkeyBase64,
+	ociPrivkey,
+	ociPrivkey+"_FILE",
 	ociPrivkeyPass,
 	ociTenancyOCID,
 	ociUserOCID,
@@ -33,7 +36,19 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc: "success",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret1"),
+				ociPrivkey:             mustGeneratePrivateKey("secret1"),
+				ociPrivkeyPass:         "secret1",
+				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
+				ociUserOCID:            "ocid1.user.oc1..secret",
+				ociPubkeyFingerprint:   "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
+				ociRegion:              "us-phoenix-1",
+				"OCI_COMPARTMENT_OCID": "123",
+			},
+		},
+		{
+			desc: "success file",
+			envVars: map[string]string{
+				ociPrivkey + "_FILE":   mustGeneratePrivateKeyFile("secret1"),
 				ociPrivkeyPass:         "secret1",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -45,12 +60,12 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc:     "missing credentials",
 			envVars:  map[string]string{},
-			expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY_BASE64,OCI_TENANCY_OCID,OCI_USER_OCID,OCI_PUBKEY_FINGERPRINT,OCI_REGION,OCI_COMPARTMENT_OCID",
+			expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY,OCI_TENANCY_OCID,OCI_USER_OCID,OCI_PUBKEY_FINGERPRINT,OCI_REGION,OCI_COMPARTMENT_OCID",
 		},
 		{
 			desc: "missing CompartmentID",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -61,9 +76,9 @@ func TestNewDNSProvider(t *testing.T) {
 			expected: "oraclecloud: some credentials information are missing: OCI_COMPARTMENT_OCID",
 		},
 		{
-			desc: "missing OCI_PRIVKEY_BASE64",
+			desc: "missing OCI_PRIVKEY",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       "",
+				ociPrivkey:             "",
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -71,12 +86,12 @@ func TestNewDNSProvider(t *testing.T) {
 				ociRegion:              "us-phoenix-1",
 				"OCI_COMPARTMENT_OCID": "123",
 			},
-			expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY_BASE64",
+			expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY",
 		},
 		{
 			desc: "missing OCI_PRIVKEY_PASS",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -89,7 +104,7 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc: "missing OCI_TENANCY_OCID",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -102,7 +117,7 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc: "missing OCI_USER_OCID",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "",
@@ -115,7 +130,7 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc: "missing OCI_PUBKEY_FINGERPRINT",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -128,7 +143,7 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc: "missing OCI_REGION",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -141,7 +156,7 @@ func TestNewDNSProvider(t *testing.T) {
 		{
 			desc: "missing OCI_REGION",
 			envVars: map[string]string{
-				ociPrivkeyBase64:       mustGeneratePrivateKey("secret"),
+				ociPrivkey:             mustGeneratePrivateKey("secret"),
 				ociPrivkeyPass:         "secret",
 				ociTenancyOCID:         "ocid1.tenancy.oc1..secret",
 				ociUserOCID:            "ocid1.user.oc1..secret",
@@ -155,7 +170,13 @@ func TestNewDNSProvider(t *testing.T) {
 
 	for _, test := range testCases {
 		t.Run(test.desc, func(t *testing.T) {
-			defer envTest.RestoreEnv()
+			defer func() {
+				privKeyFile := os.Getenv(ociPrivkey + "_FILE")
+				if privKeyFile != "" {
+					_ = os.Remove(privKeyFile)
+				}
+				envTest.RestoreEnv()
+			}()
 			envTest.ClearEnv()
 
 			envTest.Apply(test.envVars)
@@ -185,7 +206,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
 			desc:                  "invalid configuration",
 			configurationProvider: &configProvider{},
 			compartmentID:         "123",
-			expected:              "oraclecloud: can not create client, bad configuration: PEM data was not found in buffer",
+			expected:              "oraclecloud: can not create client, bad configuration: x509: decryption password incorrect",
 		},
 		{
 			desc:          "OCIConfigProvider is missing",
@@ -247,11 +268,39 @@ func TestLiveCleanUp(t *testing.T) {
 }
 
 func mustGeneratePrivateKey(pwd string) string {
-	key, err := rsa.GenerateKey(rand.Reader, 512)
+	block, err := generatePrivateKey(pwd)
 	if err != nil {
 		panic(err)
 	}
 
+	return base64.StdEncoding.EncodeToString(pem.EncodeToMemory(block))
+}
+
+func mustGeneratePrivateKeyFile(pwd string) string {
+	block, err := generatePrivateKey(pwd)
+	if err != nil {
+		panic(err)
+	}
+
+	file, err := ioutil.TempFile("", "lego_oci_*.pem")
+	if err != nil {
+		panic(err)
+	}
+
+	err = pem.Encode(file, block)
+	if err != nil {
+		panic(err)
+	}
+
+	return file.Name()
+}
+
+func generatePrivateKey(pwd string) (*pem.Block, error) {
+	key, err := rsa.GenerateKey(rand.Reader, 512)
+	if err != nil {
+		return nil, err
+	}
+
 	block := &pem.Block{
 		Type:  "RSA PRIVATE KEY",
 		Bytes: x509.MarshalPKCS1PrivateKey(key),
@@ -260,9 +309,9 @@ func mustGeneratePrivateKey(pwd string) string {
 	if pwd != "" {
 		block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(pwd), x509.PEMCipherAES256)
 		if err != nil {
-			panic(err)
+			return nil, err
 		}
 	}
 
-	return base64.StdEncoding.EncodeToString(pem.EncodeToMemory(block))
+	return block, nil
 }