diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index 860db0b5d6..91785f22d9 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -4,12 +4,12 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "io/ioutil" "net/http" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/objectstorage/v1/swauth" + "github.com/hashicorp/terraform/helper/pathorcontents" ) type Config struct { @@ -70,13 +70,13 @@ func (c *Config) loadAndValidate() error { config := &tls.Config{} if c.CACertFile != "" { - caCert, err := ioutil.ReadFile(c.CACertFile) + caCert, _, err := pathorcontents.Read(c.CACertFile) if err != nil { - return err + return fmt.Errorf("Error reading CA Cert: %s", err) } caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) + caCertPool.AppendCertsFromPEM([]byte(caCert)) config.RootCAs = caCertPool } @@ -85,7 +85,16 @@ func (c *Config) loadAndValidate() error { } if c.ClientCertFile != "" && c.ClientKeyFile != "" { - cert, err := tls.LoadX509KeyPair(c.ClientCertFile, c.ClientKeyFile) + clientCert, _, err := pathorcontents.Read(c.ClientCertFile) + if err != nil { + return fmt.Errorf("Error reading Client Cert: %s", err) + } + clientKey, _, err := pathorcontents.Read(c.ClientKeyFile) + if err != nil { + return fmt.Errorf("Error reading Client Key: %s", err) + } + + cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) if err != nil { return err } diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 2964fd5573..d12cf108d2 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -1,9 +1,13 @@ package openstack import ( + "fmt" + "io/ioutil" "os" "testing" + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -23,16 +27,6 @@ func init() { } } -func TestProvider(t *testing.T) { - if err := Provider().(*schema.Provider).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = Provider() -} - func testAccPreCheck(t *testing.T) { v := os.Getenv("OS_AUTH_URL") if v == "" { @@ -73,3 +67,172 @@ func testAccPreCheck(t *testing.T) { t.Fatal("OS_EXTGW_ID must be set for acceptance tests") } } + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +// Steps for configuring OpenStack with SSL validation are here: +// https://github.com/hashicorp/terraform/pull/6279#issuecomment-219020144 +func TestAccProvider_caCertFile(t *testing.T) { + if os.Getenv("TF_ACC") == "" || os.Getenv("OS_SSL_TESTS") == "" { + t.Skip("TF_ACC or OS_SSL_TESTS not set, skipping OpenStack SSL test.") + } + if os.Getenv("OS_CACERT") == "" { + t.Skip("OS_CACERT is not set; skipping OpenStack CA test.") + } + + p := Provider() + + caFile, err := envVarFile("OS_CACERT") + if err != nil { + t.Fatal(err) + } + defer os.Remove(caFile) + + raw := map[string]interface{}{ + "cacert_file": caFile, + } + rawConfig, err := config.NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + err = p.Configure(terraform.NewResourceConfig(rawConfig)) + if err != nil { + t.Fatalf("Unexpected err when specifying OpenStack CA by file: %s", err) + } +} + +func TestAccProvider_caCertString(t *testing.T) { + if os.Getenv("TF_ACC") == "" || os.Getenv("OS_SSL_TESTS") == "" { + t.Skip("TF_ACC or OS_SSL_TESTS not set, skipping OpenStack SSL test.") + } + if os.Getenv("OS_CACERT") == "" { + t.Skip("OS_CACERT is not set; skipping OpenStack CA test.") + } + + p := Provider() + + caContents, err := envVarContents("OS_CACERT") + if err != nil { + t.Fatal(err) + } + raw := map[string]interface{}{ + "cacert_file": caContents, + } + rawConfig, err := config.NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + err = p.Configure(terraform.NewResourceConfig(rawConfig)) + if err != nil { + t.Fatalf("Unexpected err when specifying OpenStack CA by string: %s", err) + } +} + +func TestAccProvider_clientCertFile(t *testing.T) { + if os.Getenv("TF_ACC") == "" || os.Getenv("OS_SSL_TESTS") == "" { + t.Skip("TF_ACC or OS_SSL_TESTS not set, skipping OpenStack SSL test.") + } + if os.Getenv("OS_CERT") == "" || os.Getenv("OS_KEY") == "" { + t.Skip("OS_CERT or OS_KEY is not set; skipping OpenStack client SSL auth test.") + } + + p := Provider() + + certFile, err := envVarFile("OS_CERT") + if err != nil { + t.Fatal(err) + } + defer os.Remove(certFile) + keyFile, err := envVarFile("OS_KEY") + if err != nil { + t.Fatal(err) + } + defer os.Remove(keyFile) + + raw := map[string]interface{}{ + "cert": certFile, + "key": keyFile, + } + rawConfig, err := config.NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + err = p.Configure(terraform.NewResourceConfig(rawConfig)) + if err != nil { + t.Fatalf("Unexpected err when specifying OpenStack Client keypair by file: %s", err) + } +} + +func TestAccProvider_clientCertString(t *testing.T) { + if os.Getenv("TF_ACC") == "" || os.Getenv("OS_SSL_TESTS") == "" { + t.Skip("TF_ACC or OS_SSL_TESTS not set, skipping OpenStack SSL test.") + } + if os.Getenv("OS_CERT") == "" || os.Getenv("OS_KEY") == "" { + t.Skip("OS_CERT or OS_KEY is not set; skipping OpenStack client SSL auth test.") + } + + p := Provider() + + certContents, err := envVarContents("OS_CERT") + if err != nil { + t.Fatal(err) + } + keyContents, err := envVarContents("OS_KEY") + if err != nil { + t.Fatal(err) + } + + raw := map[string]interface{}{ + "cert": certContents, + "key": keyContents, + } + rawConfig, err := config.NewRawConfig(raw) + if err != nil { + t.Fatalf("err: %s", err) + } + + err = p.Configure(terraform.NewResourceConfig(rawConfig)) + if err != nil { + t.Fatalf("Unexpected err when specifying OpenStack Client keypair by contents: %s", err) + } +} + +func envVarContents(varName string) (string, error) { + contents, _, err := pathorcontents.Read(os.Getenv(varName)) + if err != nil { + return "", fmt.Errorf("Error reading %s: %s", varName, err) + } + return contents, nil +} + +func envVarFile(varName string) (string, error) { + contents, err := envVarContents(varName) + if err != nil { + return "", err + } + + tmpFile, err := ioutil.TempFile("", varName) + if err != nil { + return "", fmt.Errorf("Error creating temp file: %s", err) + } + if _, err := tmpFile.Write([]byte(contents)); err != nil { + _ = os.Remove(tmpFile.Name()) + return "", fmt.Errorf("Error writing temp file: %s", err) + } + if err := tmpFile.Close(); err != nil { + _ = os.Remove(tmpFile.Name()) + return "", fmt.Errorf("Error closing temp file: %s", err) + } + return tmpFile.Name(), nil +} diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown index 8ece031493..df4d02f001 100644 --- a/website/source/docs/providers/openstack/index.html.markdown +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -74,13 +74,16 @@ The following arguments are supported: `OS_INSECURE` environment variable is used. * `cacert_file` - (Optional) Specify a custom CA certificate when communicating - over SSL. If omitted, the `OS_CACERT` environment variable is used. + over SSL. You can specify either a path to the file or the contents of the + certificate. If omitted, the `OS_CACERT` environment variable is used. * `cert` - (Optional) Specify client certificate file for SSL client - authentication. If omitted the `OS_CERT` environment variable is used. + authentication. You can specify either a path to the file or the contents of + the certificate. If omitted the `OS_CERT` environment variable is used. * `key` - (Optional) Specify client private key file for SSL client - authentication. If omitted the `OS_KEY` environment variable is used. + authentication. You can specify either a path to the file or the contents of + the key. If omitted the `OS_KEY` environment variable is used. * `endpoint_type` - (Optional) Specify which type of endpoint to use from the service catalog. It can be set using the OS_ENDPOINT_TYPE environment