Skip to content

Commit

Permalink
feat(terraform): Add hyphen and non-ASCII support for domain names in…
Browse files Browse the repository at this point in the history
… credential extraction (#6108)

Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com>
  • Loading branch information
adam-carruthers and simar7 committed Feb 23, 2024
1 parent 9c5e5a0 commit 4a9ac6d
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 4 deletions.
19 changes: 19 additions & 0 deletions docs/docs/scanner/misconfiguration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,25 @@ This can be repeated for specifying multiple packages.
trivy conf --policy ./policy --namespaces main --namespaces user ./configs
```

### Private terraform registries
Trivy can download terraform code from private registries.
To pass credentials you must use the `TF_TOKEN_` environment variables.
You cannot use a `.terraformrc` or `terraform.rc` file, these are not supported by trivy yet.

From the terraform docs:

> 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`.
>
> You must convert domain names containing non-ASCII characters to their punycode equivalent with an ACE prefix.
> For example, token credentials for `例えば.com` must be set in a variable called `TF_TOKEN_xn--r8j3dr99h_com`.
>
> Hyphens are also valid within host names but usually invalid as variable names and may be encoded as double underscores.
> For example, you can set a token for the domain name café.fr as TF_TOKEN_xn--caf-dma_fr or TF_TOKEN_xn____caf__dma_fr.
If multiple variables evaluate to the same hostname, Trivy will choose the environment variable name where the dashes have not been encoded as double underscores.


### Skipping resources by inline comments
Some configuration file formats (e.g. Terraform) support inline comments.

Expand Down
31 changes: 27 additions & 4 deletions pkg/iac/scanners/terraform/parser/resolvers/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"strings"
"time"

"golang.org/x/net/idna"

"github.com/aquasecurity/go-version/pkg/semver"
)

Expand Down Expand Up @@ -55,12 +57,11 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option
hostname = parts[0]
parts = parts[1:]

envVar := fmt.Sprintf("TF_TOKEN_%s", strings.ReplaceAll(hostname, ".", "_"))
token = os.Getenv(envVar)
if token != "" {
token, err = getPrivateRegistryTokenFromEnvVars(hostname)
if err == nil {
opt.Debug("Found a token for the registry at %s", hostname)
} else {
opt.Debug("No token was found for the registry at %s", hostname)
opt.Debug(err.Error())
}
}

Expand Down Expand Up @@ -136,6 +137,28 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option
return filesystem, prefix, downloadPath, true, nil
}

func getPrivateRegistryTokenFromEnvVars(hostname string) (string, error) {
token := ""
asciiHostname, err := idna.ToASCII(hostname)
if err != nil {
return "", fmt.Errorf("could not convert hostname %s to a punycode encoded ASCII string so cannot find token for this registry", hostname)
}

envVar := fmt.Sprintf("TF_TOKEN_%s", strings.ReplaceAll(asciiHostname, ".", "_"))
token = os.Getenv(envVar)

// Dashes in the hostname can optionally be converted to double underscores
if token == "" {
envVar = strings.ReplaceAll(envVar, "-", "__")
token = os.Getenv(envVar)
}

if token == "" {
return "", fmt.Errorf("no token was found for the registry at %s", hostname)
}
return token, nil
}

func resolveVersion(input string, versions moduleVersions) (string, error) {
if len(versions.Modules) != 1 {
return "", fmt.Errorf("1 module expected, found %d", len(versions.Modules))
Expand Down
56 changes: 56 additions & 0 deletions pkg/iac/scanners/terraform/parser/resolvers/registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package resolvers

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_getPrivateRegistryTokenFromEnvVars_ErrorsWithNoEnvVarSet(t *testing.T) {
token, err := getPrivateRegistryTokenFromEnvVars("registry.example.com")
assert.Equal(t, "", token)
assert.Equal(t, "no token was found for the registry at registry.example.com", err.Error())
}

func Test_getPrivateRegistryTokenFromEnvVars_ConvertsSiteNameToEnvVar(t *testing.T) {
tests := []struct {
name string
siteName string
tokenName string
}{
{
name: "returns string when simple env var set",
siteName: "registry.example.com",
tokenName: "TF_TOKEN_registry_example_com",
},
{
name: "allows dashes in hostname to be dashes",
siteName: "my-registry.example.com",
tokenName: "TF_TOKEN_my-registry_example_com",
},
{
name: "allows dashes in hostname to be double underscores",
siteName: "my-registry.example.com",
tokenName: "TF_TOKEN_my__registry_example_com",
},
{
name: "handles utf8 to punycode correctly",
siteName: "例えば.com",
tokenName: "TF_TOKEN_xn--r8j3dr99h_com",
},
{
name: "handles punycode with dash to underscore conversion",
siteName: "café.fr",
tokenName: "TF_TOKEN_xn____caf__dma_fr",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv(tt.tokenName, "abcd")
token, err := getPrivateRegistryTokenFromEnvVars(tt.siteName)
assert.Equal(t, "abcd", token)
assert.Equal(t, nil, err)
})
}
}

0 comments on commit 4a9ac6d

Please sign in to comment.