Allow OpenStack SSL fields to be specified by contents

- OpenStack provider now supports either a path or the file contents for
  `cacert_file`, `cert`, and `key`
- Makes it easier to automate TF by passing in certs as environment
  variables
- set `OS_SSL_TESTS=true` to run the acceptance tests
This commit is contained in:
Lyle Franklin 2016-11-21 08:31:25 -08:00 committed by Lyle Franklin
parent 2d894bae48
commit 3b7cf41b83
3 changed files with 193 additions and 18 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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