Skip to content

Commit

Permalink
Allow OpenStack SSL fields to be specified by contents
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
ljfranklin committed Dec 22, 2016
1 parent 2d894ba commit 39f06ff
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 8 deletions.
19 changes: 14 additions & 5 deletions builtin/providers/openstack/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
Expand Down
155 changes: 155 additions & 0 deletions builtin/providers/openstack/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package openstack

import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"testing"

"github.com/hashicorp/terraform/helper/pathorcontents"
)

const testFakeCertPath = "./test-fixtures/fake_cert.pem"
const testFakeKeyPath = "./test-fixtures/fake_key.pem"

func baseConfig(t *testing.T) Config {
config := Config{
IdentityEndpoint: os.Getenv("OS_AUTH_URL"),
EndpointType: os.Getenv("OS_ENDPOINT_TYPE"),
CACertFile: os.Getenv("OS_CACERT"),
ClientCertFile: os.Getenv("OS_CERT"),
ClientKeyFile: os.Getenv("OS_KEY"),
Insecure: envBool("OS_INSECURE"),
TenantID: multiEnv("OS_TENANT_ID", "OS_PROJECT_ID"),
TenantName: multiEnv("OS_TENANT_NAME", "OS_PROJECT_NAME"),
DomainID: multiEnv("OS_USER_DOMAIN_ID", "OS_PROJECT_DOMAIN_ID", "OS_DOMAIN_ID"),
DomainName: multiEnv("OS_USER_DOMAIN_NAME", "OS_PROJECT_DOMAIN_NAME", "OS_DOMAIN_NAME", "OS_DEFAULT_DOMAIN"),
Username: os.Getenv("OS_USERNAME"),
UserID: os.Getenv("OS_USER_ID"),
Password: os.Getenv("OS_PASSWORD"),
Token: os.Getenv("OS_AUTH_TOKEN"),
}

return config
}

func TestConfigLoadAndValidate_defaultSettings(t *testing.T) {
config := baseConfig(t)
err := config.loadAndValidate()
if err != nil {
t.Fatalf("Unexpected err when validating OpenStack config: %s", err)
}
}

func TestConfigLoadAndValidate_caCertFile(t *testing.T) {
config := baseConfig(t)
config.CACertFile = testFakeCertPath

err := config.loadAndValidate()
if err == nil {
t.Fatal("Expected SSL validation error, but got nil")
}
if !strings.Contains(err.Error(), "x509") {
t.Fatalf("Expected error to contain 'x509', but it did not: %s", err)
}
}

func TestConfigLoadAndValidate_caCertString(t *testing.T) {
fakeCertContents, err := ioutil.ReadFile(testFakeCertPath)
if err != nil {
t.Fatalf("Failed to read cert fixture: %s", err)
}
config := baseConfig(t)
config.CACertFile = string(fakeCertContents)

err = config.loadAndValidate()
if err == nil {
t.Fatal("Expected SSL validation error, but got nil")
}
if !strings.Contains(err.Error(), "x509") {
t.Fatalf("Expected error to contain 'x509', but it did not: %s", err)
}
}

func TestConfigLoadAndValidate_clientCert(t *testing.T) {
// Since client SSL validation is enforced server-side and not enabled by default,
// it is difficult to write a failing test here
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.")
}

certContents, _, err := pathorcontents.Read(os.Getenv("OS_CERT"))
if err != nil {
t.Fatalf("Error reading OS_CERT: %s", err)
}
certFile, err := tempFileWithContents("os-cert", certContents)
if err != nil {
t.Fatal(err)
}
defer os.Remove(certFile.Name())

keyContents, _, err := pathorcontents.Read(os.Getenv("OS_KEY"))
if err != nil {
t.Fatalf("Error reading OS_KEY: %s", err)
}
keyFile, err := tempFileWithContents("os-key", keyContents)
if err != nil {
t.Fatal(err)
}
defer os.Remove(keyFile.Name())

// Specify key pair as files
config := baseConfig(t)
config.ClientCertFile = certFile.Name()
config.ClientKeyFile = keyFile.Name()

err = config.loadAndValidate()
if err != nil {
t.Fatalf("Unexpected err when specifying OpenStack Client keypair by file: %s", err)
}

// Specify key pair as contents
config = baseConfig(t)
config.ClientCertFile = certContents
config.ClientKeyFile = keyContents

err = config.loadAndValidate()
if err != nil {
t.Fatalf("Unexpected err when specifying OpenStack Client keypair by contents: %s", err)
}
}

func envBool(envVar string) bool {
v := os.Getenv(envVar)
if b, err := strconv.ParseBool(v); err == nil {
return b
}
return false
}

func multiEnv(envVars ...string) string {
for _, v := range envVars {
if os.Getenv(v) != "" {
return os.Getenv(v)
}
}
return ""
}

func tempFileWithContents(prefix string, contents string) (*os.File, error) {
tmpFile, err := ioutil.TempFile("", prefix)
if err != nil {
return nil, fmt.Errorf("Error creating temp file: %s", err)
}
if _, err := tmpFile.Write([]byte(contents)); err != nil {
_ = os.Remove(tmpFile.Name())
return nil, fmt.Errorf("Error writing temp file: %s", err)
}
if err := tmpFile.Close(); err != nil {
_ = os.Remove(tmpFile.Name())
return nil, fmt.Errorf("Error closing temp file: %s", err)
}
return tmpFile, nil
}
34 changes: 34 additions & 0 deletions builtin/providers/openstack/test-fixtures/fake_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
51 changes: 51 additions & 0 deletions builtin/providers/openstack/test-fixtures/fake_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
9 changes: 6 additions & 3 deletions website/source/docs/providers/openstack/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 39f06ff

Please sign in to comment.