diff --git a/state/remote/swift.go b/state/remote/swift.go index 6fc4597f62..92d21948d8 100644 --- a/state/remote/swift.go +++ b/state/remote/swift.go @@ -4,7 +4,9 @@ import ( "bytes" "crypto/md5" "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "log" "net/http" "os" @@ -20,8 +22,21 @@ const TFSTATE_NAME = "tfstate.tf" // SwiftClient implements the Client interface for an Openstack Swift server. type SwiftClient struct { - client *gophercloud.ServiceClient - path string + client *gophercloud.ServiceClient + authurl string + cacert string + cert string + domainid string + domainname string + insecure bool + key string + password string + path string + region string + tenantid string + tenantname string + userid string + username string } func swiftFactory(conf map[string]string) (Client, error) { @@ -35,30 +50,127 @@ func swiftFactory(conf map[string]string) (Client, error) { } func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { - if val := os.Getenv("OS_AUTH_URL"); val == "" { - return fmt.Errorf("missing OS_AUTH_URL environment variable") + authUrl, ok := conf["auth_url"] + if !ok { + authUrl = os.Getenv("OS_AUTH_URL") + if authUrl == "" { + return fmt.Errorf("missing 'auth_url' configuration or OS_AUTH_URL environment variable") + } } - if val := os.Getenv("OS_USERNAME"); val == "" { - return fmt.Errorf("missing OS_USERNAME environment variable") + c.authurl = authUrl + + username, ok := conf["user_name"] + if !ok { + username = os.Getenv("OS_USERNAME") } - if val := os.Getenv("OS_TENANT_NAME"); val == "" { - return fmt.Errorf("missing OS_TENANT_NAME environment variable") + c.username = username + + userID, ok := conf["user_id"] + if !ok { + userID = os.Getenv("OS_USER_ID") } - if val := os.Getenv("OS_PASSWORD"); val == "" { - return fmt.Errorf("missing OS_PASSWORD environment variable") + c.userid = userID + + password, ok := conf["password"] + if !ok { + password = os.Getenv("OS_PASSWORD") + if password == "" { + return fmt.Errorf("missing 'password' configuration or OS_PASSWORD environment variable") + } } + c.password = password + + region, ok := conf["region_name"] + if !ok { + region = os.Getenv("OS_REGION_NAME") + } + c.region = region + + tenantID, ok := conf["tenant_id"] + if !ok { + tenantID = multiEnv([]string{ + "OS_TENANT_ID", + "OS_PROJECT_ID", + }) + } + c.tenantid = tenantID + + tenantName, ok := conf["tenant_name"] + if !ok { + tenantName = multiEnv([]string{ + "OS_TENANT_NAME", + "OS_PROJECT_NAME", + }) + } + c.tenantname = tenantName + + domainID, ok := conf["domain_id"] + if !ok { + domainID = multiEnv([]string{ + "OS_USER_DOMAIN_ID", + "OS_PROJECT_DOMAIN_ID", + "OS_DOMAIN_ID", + }) + } + c.domainid = domainID + + domainName, ok := conf["domain_name"] + if !ok { + domainName = multiEnv([]string{ + "OS_USER_DOMAIN_NAME", + "OS_PROJECT_DOMAIN_NAME", + "OS_DOMAIN_NAME", + "DEFAULT_DOMAIN", + }) + } + c.domainname = domainName + path, ok := conf["path"] if !ok || path == "" { return fmt.Errorf("missing 'path' configuration") } + c.path = path + + c.insecure = false + raw, ok := conf["insecure"] + if !ok { + raw = os.Getenv("OS_INSECURE") + } + if raw != "" { + v, err := strconv.ParseBool(raw) + if err != nil { + return fmt.Errorf("'insecure' and 'OS_INSECURE' could not be parsed as bool: %s", err) + } + c.insecure = v + } + + cacertFile, ok := conf["cacert_file"] + if !ok { + cacertFile = os.Getenv("OS_CACERT") + } + c.cacert = cacertFile + + cert, ok := conf["cert"] + if !ok { + cert = os.Getenv("OS_CERT") + } + c.cert = cert + + key, ok := conf["key"] + if !ok { + key = os.Getenv("OS_KEY") + } + c.key = key ao := gophercloud.AuthOptions{ - IdentityEndpoint: os.Getenv("OS_AUTH_URL"), - Username: os.Getenv("OS_USERNAME"), - TenantName: os.Getenv("OS_TENANT_NAME"), - Password: os.Getenv("OS_PASSWORD"), - DomainName: os.Getenv("OS_DOMAIN_NAME"), - DomainID: os.Getenv("OS_DOMAIN_ID"), + IdentityEndpoint: c.authurl, + UserID: c.userid, + Username: c.username, + TenantID: c.tenantid, + TenantName: c.tenantname, + Password: c.password, + DomainID: c.domainid, + DomainName: c.domainname, } provider, err := openstack.NewClient(ao.IdentityEndpoint) @@ -67,19 +179,33 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { } config := &tls.Config{} - insecure := false - if insecure_env := os.Getenv("OS_INSECURE"); insecure_env != "" { - insecure, err = strconv.ParseBool(insecure_env) + + if c.cacert != "" { + caCert, err := ioutil.ReadFile(c.cacert) if err != nil { return err } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + config.RootCAs = caCertPool } - if insecure { + if c.insecure { log.Printf("[DEBUG] Insecure mode set") config.InsecureSkipVerify = true } + if c.cert != "" && c.key != "" { + cert, err := tls.LoadX509KeyPair(c.cert, c.key) + if err != nil { + return err + } + + config.Certificates = []tls.Certificate{cert} + config.BuildNameToCertificate() + } + transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} provider.HTTPClient.Transport = transport @@ -88,9 +214,8 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { return err } - c.path = path c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{ - Region: os.Getenv("OS_REGION_NAME"), + Region: c.region, }) return err @@ -149,3 +274,12 @@ func (c *SwiftClient) ensureContainerExists() error { return nil } + +func multiEnv(ks []string) string { + for _, k := range ks { + if v := os.Getenv(k); v != "" { + return v + } + } + return "" +} diff --git a/website/source/docs/state/remote/swift.html.md b/website/source/docs/state/remote/swift.html.md index 293fb9ca18..8cbbe672b1 100644 --- a/website/source/docs/state/remote/swift.html.md +++ b/website/source/docs/state/remote/swift.html.md @@ -10,38 +10,74 @@ description: |- Stores the state as an artifact in [Swift](http://docs.openstack.org/developer/swift/). +-> **Note:** Passing credentials directly via configuration options will +make them included in cleartext inside the persisted state. Use of +environment variables is recommended. + ## Example Usage ``` terraform remote config \ - -backend=swift \ - -backend-config="path=random/path" + -backend=swift \ + -backend-config="path=terraform_state" ``` ## Example Referencing ``` data "terraform_remote_state" "foo" { - backend = "swift" - config { - path = "random/path" - } + backend = "swift" + config { + path = "terraform_state" + } } ``` ## Configuration variables -The following configuration option is supported: +The following configuration options are supported: - * `path` - (Required) The path where to store `terraform.tfstate` - * `insecure` - (Optional) Allow "insecure" SSL requests. Defaults to `false`. + * `auth_url` - (Required) The Identity authentication URL. If omitted, the + `OS_AUTH_URL` environment variable is used. -The following environment variables are supported: + * `path` - (Required) The path where to store `terraform.tfstate`. + * `user_name` - (Optional) The Username to login with. If omitted, the + `OS_USERNAME` environment variable is used. - * `OS_AUTH_URL` - (Required) The identity endpoint - * `OS_USERNAME` - (Required) The username - * `OS_PASSWORD` - (Required) The password - * `OS_REGION_NAME` - (Required) The region - * `OS_TENANT_NAME` - (Required) The name of the tenant - * `OS_DOMAIN_ID` - (Optional) The ID of the domain - * `OS_DOMAIN_NAME` - (Optional) The name of the domain + * `user_id` - (Optional) The User ID to login with. If omitted, the + `OS_USER_ID` environment variable is used. + + * `password` - (Optional) The Password to login with. If omitted, the + `OS_PASSWORD` environment variable is used. + + * `region_name` (Required) - The region in which to store `terraform.tfstate`. If + omitted, the `OS_REGION_NAME` environment variable is used. + + * `tenant_id` (Optional) The ID of the Tenant (Identity v2) or Project + (Identity v3) to login with. If omitted, the `OS_TENANT_ID` or + `OS_PROJECT_ID` environment variables are used. + + * `tenant_name` - (Optional) The Name of the Tenant (Identity v2) or Project + (Identity v3) to login with. If omitted, the `OS_TENANT_NAME` or + `OS_PROJECT_NAME` environment variable are used. + + * `domain_id` - (Optional) The ID of the Domain to scope to (Identity v3). If + If omitted, the following environment variables are checked (in this order): + `OS_USER_DOMAIN_ID`, `OS_PROJECT_DOMAIN_ID`, `OS_DOMAIN_ID`. + + * `domain_name` - (Optional) The Name of the Domain to scope to (Identity v3). + If omitted, the following environment variables are checked (in this order): + `OS_USER_DOMAIN_NAME`, `OS_PROJECT_DOMAIN_NAME`, `OS_DOMAIN_NAME`, + `DEFAULT_DOMAIN`. + + * `insecure` - (Optional) Trust self-signed SSL certificates. If omitted, the + `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. + + * `cert` - (Optional) Specify client certificate file for SSL client + authentication. 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. \ No newline at end of file