mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #30797 from hashicorp/brandonc/env_credentials
Allow remote service creds to be configured using env
This commit is contained in:
commit
bb7f4f8b8f
@ -8,6 +8,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
@ -113,6 +114,47 @@ func (c *Config) credentialsSource(helperType string, helper svcauth.Credentials
|
||||
}
|
||||
}
|
||||
|
||||
// hostCredentialsFromEnv returns a token credential by searching for a hostname-specific
|
||||
// environment variable. The host parameter is expected to be in the "comparison" form,
|
||||
// for example, hostnames containing non-ASCII characters like "café.fr"
|
||||
// should be expressed as "xn--caf-dma.fr". If the variable based on the hostname is not
|
||||
// defined, nil is returned. Variable names must have dot characters translated to
|
||||
// underscores, which are not allowed in DNS names. For example, token credentials
|
||||
// for app.terraform.io should be set in the variable named TF_TOKEN_app_terraform_io.
|
||||
//
|
||||
// Hyphen characters are allowed in environment variable names, but are not valid POSIX
|
||||
// variable names. Usually, it's still possible to set variable names with hyphens using
|
||||
// utilities like env or docker. But, as a fallback, host names may encode their
|
||||
// hyphens as double underscores in the variable name. For the example "café.fr",
|
||||
// the variable name "TF_TOKEN_xn____caf__dma_fr" or "TF_TOKEN_xn--caf-dma_fr"
|
||||
// may be used.
|
||||
func hostCredentialsFromEnv(host svchost.Hostname) svcauth.HostCredentials {
|
||||
if len(host) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert dots to underscores when looking for environment configuration for a specific host.
|
||||
// DNS names do not allow underscore characters so this is unambiguous.
|
||||
translated := strings.ReplaceAll(host.String(), ".", "_")
|
||||
|
||||
if token, ok := os.LookupEnv(fmt.Sprintf("TF_TOKEN_%s", translated)); ok {
|
||||
return svcauth.HostCredentialsToken(token)
|
||||
}
|
||||
|
||||
if strings.ContainsRune(translated, '-') {
|
||||
// This host name contains a hyphen. Replace hyphens with double underscores as a fallback
|
||||
// (see godoc above for details)
|
||||
translated = strings.ReplaceAll(host.String(), "-", "__")
|
||||
translated = strings.ReplaceAll(translated, ".", "_")
|
||||
|
||||
if token, ok := os.LookupEnv(fmt.Sprintf("TF_TOKEN_%s", translated)); ok {
|
||||
return svcauth.HostCredentialsToken(token)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CredentialsSource is an implementation of svcauth.CredentialsSource
|
||||
// that can read and write the CLI configuration, and possibly also delegate
|
||||
// to a credentials helper when configured.
|
||||
@ -153,11 +195,18 @@ type CredentialsSource struct {
|
||||
var _ svcauth.CredentialsSource = (*CredentialsSource)(nil)
|
||||
|
||||
func (s *CredentialsSource) ForHost(host svchost.Hostname) (svcauth.HostCredentials, error) {
|
||||
// The first order of precedence for credentials is a host-specific environment variable
|
||||
if envCreds := hostCredentialsFromEnv(host); envCreds != nil {
|
||||
return envCreds, nil
|
||||
}
|
||||
|
||||
// Then, any credentials block present in the CLI config
|
||||
v, ok := s.configured[host]
|
||||
if ok {
|
||||
return svcauth.HostCredentialsFromObject(v), nil
|
||||
}
|
||||
|
||||
// And finally, the credentials helper
|
||||
if s.helper != nil {
|
||||
return s.helper.ForHost(host)
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ package cliconfig
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform-svchost"
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
||||
)
|
||||
|
||||
@ -83,6 +84,78 @@ func TestCredentialsForHost(t *testing.T) {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("set in environment", func(t *testing.T) {
|
||||
envName := "TF_TOKEN_configured_example_com"
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv(envName)
|
||||
})
|
||||
|
||||
expectedToken := "configured-by-env"
|
||||
os.Setenv(envName, expectedToken)
|
||||
|
||||
creds, err := credSrc.ForHost(svchost.Hostname("configured.example.com"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if creds == nil {
|
||||
t.Fatal("no credentials found")
|
||||
}
|
||||
|
||||
if got := creds.Token(); got != expectedToken {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("punycode name set in environment", func(t *testing.T) {
|
||||
envName := "TF_TOKEN_env_xn--eckwd4c7cu47r2wf_com"
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv(envName)
|
||||
})
|
||||
|
||||
expectedToken := "configured-by-env"
|
||||
os.Setenv(envName, expectedToken)
|
||||
|
||||
hostname, _ := svchost.ForComparison("env.ドメイン名例.com")
|
||||
creds, err := credSrc.ForHost(hostname)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if creds == nil {
|
||||
t.Fatal("no credentials found")
|
||||
}
|
||||
|
||||
if got := creds.Token(); got != expectedToken {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("hyphens can be encoded as double underscores", func(t *testing.T) {
|
||||
envName := "TF_TOKEN_env_xn____caf__dma_fr"
|
||||
expectedToken := "configured-by-fallback"
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv(envName)
|
||||
})
|
||||
|
||||
os.Setenv(envName, expectedToken)
|
||||
|
||||
hostname, _ := svchost.ForComparison("env.café.fr")
|
||||
creds, err := credSrc.ForHost(hostname)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if creds == nil {
|
||||
t.Fatal("no credentials found")
|
||||
}
|
||||
|
||||
if got := creds.Token(); got != expectedToken {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentialsStoreForget(t *testing.T) {
|
||||
|
@ -115,12 +115,23 @@ is available at multiple hostnames, use only one of them consistently.
|
||||
Terraform Cloud responds to API calls at both its current hostname
|
||||
`app.terraform.io`, and its historical hostname `atlas.hashicorp.com`.
|
||||
|
||||
### Credentials Helpers
|
||||
### Environment Variable Credentials
|
||||
|
||||
If you would prefer not to store your API tokens directly in the CLI
|
||||
configuration as described in the previous section, you can optionally instruct
|
||||
Terraform to use a different credentials storage mechanism by configuring a
|
||||
special kind of plugin program called a _credentials helper_.
|
||||
configuration, you may use a host-specific environment variable. Environment variable names should
|
||||
have the prefix `TF_TOKEN_` added to the domain name, with periods encoded as underscores.
|
||||
For example, the value of a variable named `TF_TOKEN_app_terraform_io` will be used as a
|
||||
bearer authorization token when the CLI makes service requests to the hostname "app.terraform.io".
|
||||
If multiple variables evaluate to the same hostname, Terraform will use the one defined later in the
|
||||
operating system's variable table.
|
||||
|
||||
When using domain names as a variable name, you must convert domain names containing non-ASCII characters to their [punycode equivalent](https://www.charset.org/punycode) with an ACE prefix. For example, token credentials for 例えば.com must be set in a variable called `TF_TOKEN_xn--r8j3dr99h_com`.
|
||||
|
||||
Some tools like the `env` utility allow hyphens in variable names, but hyphens create invalid POSIX variable names. Therefore, you can encode hyphens as double underscores when you set variables with interactive tools like Bash or Zsh. For example, you can set a token for the domain name "café.fr" as either `TF_TOKEN_xn--caf-dma_fr` or `TF_TOKEN_xn____caf__dma_fr`. If both are defined, Terraform will use the version containing hyphens.
|
||||
|
||||
### Credentials Helpers
|
||||
|
||||
You can configure a `credentials_helper` to instruct Terraform to use a different credentials storage mechanism.
|
||||
|
||||
```hcl
|
||||
credentials_helper "example" {
|
||||
@ -146,6 +157,16 @@ To learn how to write and install your own credentials helpers to integrate
|
||||
with existing in-house credentials management systems, see
|
||||
[the guide to Credentials Helper internals](/internals/credentials-helpers).
|
||||
|
||||
### Credentials Source Priority Order
|
||||
|
||||
Credentials found in an environment variable for a particular service host
|
||||
as described above will be preferred over those in CLI config as set by `terraform login`.
|
||||
If neither are set, any configured credentials helper will be consulted.
|
||||
|
||||
~> **Note:** For users of [terraform-credentials-helper](https://github.com/apparentlymart/terraform-credentials-env), this priority has been effectively reversed following the
|
||||
release of Terraform 1.2. Previously, credentials found within CLI config or set by
|
||||
`terraform login` were preferred to `TF_TOKEN_*` variables.
|
||||
|
||||
## Provider Installation
|
||||
|
||||
The default way to install provider plugins is from a provider registry. The
|
||||
|
@ -90,8 +90,7 @@ At present, the following service identifiers are in use:
|
||||
## Authentication
|
||||
|
||||
If credentials for the given hostname are available in
|
||||
[the CLI config](/cli/config/config-file) then they will be included
|
||||
in the request for the discovery document.
|
||||
[the CLI config](/cli/config/config-file#Credentials) through a `credentials_helper` or a host-specific environment variable, then they will be included in the request for the discovery document.
|
||||
|
||||
The credentials may also be provided to endpoints declared in the discovery
|
||||
document, depending on the requirements of the service in question.
|
||||
|
Loading…
Reference in New Issue
Block a user