From 235ab6653f32883cbf468da1f220a3b9b2121da7 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 5 Mar 2019 19:57:04 +0100 Subject: [PATCH] oraclecloud: better way to get private key. (#814) --- cmd/cmd_dnshelp.go | 2 +- providers/dns/oraclecloud/README.md | 28 +++++- providers/dns/oraclecloud/configprovider.go | 32 ++++++- providers/dns/oraclecloud/oraclecloud.go | 2 +- providers/dns/oraclecloud/oraclecloud_test.go | 85 +++++++++++++++---- 5 files changed, 122 insertions(+), 27 deletions(-) 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) } 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 }