Skip to content

Commit 0b91571

Browse files
bosouzampalmischavis
authored
VAULT-32657 deprecate duplicate attributes in HCL configs and policies (#30386)
* upgrade hcl dependency on api pkg This upgrades the hcl dependency for the API pkg, and adapts its usage so users of our API pkg are not affected. There's no good way of communicating a warning via a library call so we don't. The tokenHelper which is used by all Vault CLI commands in order to create the Vault client, as well as directly used by the login and server commands, is implemented on the api pkg, so this upgrade also affects all of those commands. Seems like this was only moved to the api pkg because the Terraform provider uses it, and I thought creating a full copy of all those files back under command would be too much spaghetti. Also leaving some TODOs to make next deprecation steps easier. * upgrade hcl dependency in vault and sdk pkgs * upgrade hcl dependency in vault and sdk pkgs * add CLI warnings to commands that take a config - vault agent (unit test on CMD warning) - vault proxy (unit test on CMD warning) - vault server (no test for the warning) - vault operator diagnose (no tests at all, uses the same function as vault server * ignore duplicates on ParseKMSes function * Extend policy parsing functions and warn on policy store * Add warning on policy fmt with duplicate attributes * Add warnings when creating/updating policy with duplicate HCL attrs * Add log warning when switchedGetPolicy finds duplicate attrs Following operations can trigger this warning when they run into a policy with duplicate attributes: * replication filtered path namespaces invalidation * policy read API * building an ACL (for many different purposes like most authZ operations) * looking up DR token policies * creating a token with named policies * when caching the policies for all namespaces during unseal * Print log warnings when token inline policy has duplicate attrs No unit tests on these as new test infra would have to be built on all. Operations affected, which will now print a log warning when the retrieved token has an inline policy with duplicate attributes: * capabilities endpoints in sys mount * handing events under a subscription with a token with duplicate attrs in inline policies * token used to create another token has duplicate attrs in inline policies (sudo check) * all uses of fetchACLTokenEntryAndEntity when the request uses a token with inline policies with duplicate attrs. Almost all reqs are subject to this * when tokens are created with inline policies (unclear exactly how that can happen) * add changelog and deprecation notice * add missing copywrite notice * fix copy-paste mistake good thing it was covered by unit tests * Fix manual parsing of telemetry field in SharedConfig This commit in the hcl library was not in the v1.0.1-vault-5 version we're using but is included in v1.0.1-vault-7: hashicorp/hcl@e80118a This thing of reusing when parsing means that our approach of manually re-parsing fields on top of fields that have already been parsed by the hcl annotation causes strings (maybe more?) to concatenate. Fix that by removing annotation. There's actually more occurrences of this thing of automatically parsing something that is also manually parsing. In some places we could just remove the boilerplate manual parsing, in others we better remove the auto parsing, but I don't wanna pull at that thread right now. I just checked that all places at least fully overwrite the automatically parsed field instead of reusing it as the target of the decode call. The only exception is the AOP field on ent but that doesn't have maps or slices, so I think it's fine. An alternative approach would be to ensure that the auto-parsed value is discarded, like the current parseCache function does note how it's template not templates * Fix linter complaints * Update command/base_predict.go Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com> * address review * remove copywrite headers * re-add copywrite headers * make fmt * Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * undo changes to deprecation.mdx * remove deprecation doc * fix conflict with changes from main --------- Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com> Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
1 parent 3fdaa60 commit 0b91571

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+799
-405
lines changed

api/cliconfig/config.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type defaultConfig struct {
3535
// loadConfig reads the configuration from the given path. If path is
3636
// empty, then the default path will be used, or the environment variable
3737
// if set.
38-
func loadConfig(path string) (*defaultConfig, error) {
38+
func loadConfig(path string) (config *defaultConfig, duplicate bool, err error) {
3939
if path == "" {
4040
path = defaultConfigPath
4141
}
@@ -44,35 +44,37 @@ func loadConfig(path string) (*defaultConfig, error) {
4444
}
4545

4646
// NOTE: requires HOME env var to be set
47-
path, err := homedir.Expand(path)
47+
path, err = homedir.Expand(path)
4848
if err != nil {
49-
return nil, fmt.Errorf("error expanding config path %q: %w", path, err)
49+
return nil, false, fmt.Errorf("error expanding config path %q: %w", path, err)
5050
}
5151

5252
contents, err := os.ReadFile(path)
5353
if err != nil && !os.IsNotExist(err) {
54-
return nil, err
54+
return nil, false, err
5555
}
5656

57-
conf, err := parseConfig(string(contents))
57+
conf, duplicate, err := parseConfig(string(contents))
5858
if err != nil {
59-
return nil, fmt.Errorf("error parsing config file at %q: %w; ensure that the file is valid; Ansible Vault is known to conflict with it", path, err)
59+
return nil, duplicate, fmt.Errorf("error parsing config file at %q: %w; ensure that the file is valid; Ansible Vault is known to conflict with it", path, err)
6060
}
6161

62-
return conf, nil
62+
return conf, duplicate, nil
6363
}
6464

6565
// parseConfig parses the given configuration as a string.
66-
func parseConfig(contents string) (*defaultConfig, error) {
67-
root, err := hcl.Parse(contents)
66+
func parseConfig(contents string) (config *defaultConfig, duplicate bool, err error) {
67+
// TODO (HCL_DUP_KEYS_DEPRECATION): on removal stage change this to a simple hcl.Parse, effectively treating
68+
// duplicate keys as an error. Also get rid of all of these "duplicate" named return values
69+
root, duplicate, err := parseAndCheckForDuplicateHclAttributes(contents)
6870
if err != nil {
69-
return nil, err
71+
return nil, duplicate, err
7072
}
7173

7274
// Top-level item should be the object list
7375
list, ok := root.Node.(*ast.ObjectList)
7476
if !ok {
75-
return nil, fmt.Errorf("failed to parse config; does not contain a root object")
77+
return nil, duplicate, fmt.Errorf("failed to parse config; does not contain a root object")
7678
}
7779

7880
valid := map[string]struct{}{
@@ -88,12 +90,12 @@ func parseConfig(contents string) (*defaultConfig, error) {
8890
}
8991

9092
if validationErrors != nil {
91-
return nil, validationErrors
93+
return nil, duplicate, validationErrors
9294
}
9395

9496
var c defaultConfig
9597
if err := hcl.DecodeObject(&c, list); err != nil {
96-
return nil, err
98+
return nil, duplicate, err
9799
}
98-
return &c, nil
100+
return &c, duplicate, nil
99101
}

api/cliconfig/config_test.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func TestLoadConfig(t *testing.T) {
14-
config, err := loadConfig(filepath.Join("testdata", "config.hcl"))
14+
config, duplicate, err := loadConfig(filepath.Join("testdata", "config.hcl"))
1515
if err != nil {
1616
t.Fatalf("err: %s", err)
1717
}
@@ -22,21 +22,29 @@ func TestLoadConfig(t *testing.T) {
2222
if !reflect.DeepEqual(expected, config) {
2323
t.Fatalf("bad: %#v", config)
2424
}
25+
26+
if duplicate {
27+
t.Fatal("expected no duplicate")
28+
}
2529
}
2630

2731
func TestLoadConfig_noExist(t *testing.T) {
28-
config, err := loadConfig("nope/not-once/.never")
32+
config, duplicate, err := loadConfig("nope/not-once/.never")
2933
if err != nil {
3034
t.Fatal(err)
3135
}
3236

3337
if config.TokenHelper != "" {
3438
t.Errorf("expected %q to be %q", config.TokenHelper, "")
3539
}
40+
41+
if duplicate {
42+
t.Fatal("expected no duplicate")
43+
}
3644
}
3745

3846
func TestParseConfig_badKeys(t *testing.T) {
39-
_, err := parseConfig(`
47+
_, duplicate, err := parseConfig(`
4048
token_helper = "/token"
4149
nope = "true"
4250
`)
@@ -47,4 +55,23 @@ nope = "true"
4755
if !strings.Contains(err.Error(), `invalid key "nope" on line 3`) {
4856
t.Errorf("bad error: %s", err.Error())
4957
}
58+
59+
if duplicate {
60+
t.Fatal("expected no duplicate")
61+
}
62+
}
63+
64+
func TestParseConfig_HclDuplicateKey(t *testing.T) {
65+
_, duplicate, err := parseConfig(`
66+
token_helper = "/token"
67+
token_helper = "/token"
68+
`)
69+
// TODO (HCL_DUP_KEYS_DEPRECATION): change this to expect an error once support for duplicate keys is fully removed
70+
if err != nil {
71+
t.Fatal("expected no error")
72+
}
73+
74+
if !duplicate {
75+
t.Fatal("expected duplicate")
76+
}
5077
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package cliconfig
5+
6+
import (
7+
"strings"
8+
9+
"github.com/hashicorp/hcl"
10+
"github.com/hashicorp/hcl/hcl/ast"
11+
hclParser "github.com/hashicorp/hcl/hcl/parser"
12+
)
13+
14+
// parseAndCheckForDuplicateHclAttributes parses the input JSON/HCL file and if it is HCL it also checks
15+
// for duplicate keys in the HCL file, allowing callers to handle the issue accordingly. In a future release we'll
16+
// change the behavior to treat duplicate keys as an error and eventually remove this helper altogether.
17+
// TODO (HCL_DUP_KEYS_DEPRECATION): remove once not used anymore
18+
func parseAndCheckForDuplicateHclAttributes(input string) (res *ast.File, duplicate bool, err error) {
19+
res, err = hcl.Parse(input)
20+
if err != nil && strings.Contains(err.Error(), "Each argument can only be defined once") {
21+
duplicate = true
22+
res, err = hclParser.ParseDontErrorOnDuplicateKeys([]byte(input))
23+
}
24+
return res, duplicate, err
25+
}

api/cliconfig/util.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ import (
1010
// DefaultTokenHelper returns the token helper that is configured for Vault.
1111
// This helper should only be used for non-server CLI commands.
1212
func DefaultTokenHelper() (tokenhelper.TokenHelper, error) {
13-
config, err := loadConfig("")
13+
config, _, err := DefaultTokenHelperCheckDuplicates()
14+
return config, err
15+
}
16+
17+
// TODO (HCL_DUP_KEYS_DEPRECATION): eventually make this consider duplicates an error. Ideally we should remove it but
18+
// maybe we can't since it's become part of the API pkg.
19+
func DefaultTokenHelperCheckDuplicates() (helper tokenhelper.TokenHelper, duplicate bool, err error) {
20+
config, duplicate, err := loadConfig("")
1421
if err != nil {
15-
return nil, err
22+
return nil, duplicate, err
1623
}
1724

1825
path := config.TokenHelper
1926
if path == "" {
20-
return tokenhelper.NewInternalTokenHelper()
27+
helper, err = tokenhelper.NewInternalTokenHelper()
28+
return helper, duplicate, err
2129
}
2230

2331
path, err = tokenhelper.ExternalTokenHelperPath(path)
2432
if err != nil {
25-
return nil, err
33+
return nil, duplicate, err
2634
}
27-
return &tokenhelper.ExternalTokenHelper{BinaryPath: path}, nil
35+
return &tokenhelper.ExternalTokenHelper{BinaryPath: path}, duplicate, nil
2836
}

api/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/hashicorp/go-rootcerts v1.0.2
2222
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6
2323
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2
24-
github.com/hashicorp/hcl v1.0.0
24+
github.com/hashicorp/hcl v1.0.1-vault-7
2525
github.com/mitchellh/go-homedir v1.1.0
2626
github.com/mitchellh/mapstructure v1.5.0
2727
github.com/natefinch/atomic v1.0.1

api/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S
3838
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
3939
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
4040
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
41+
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
42+
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
4143
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
4244
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
4345
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=

api/hcl_dup_attr_deprecation.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package api
5+
6+
import (
7+
"strings"
8+
9+
"github.com/hashicorp/hcl"
10+
"github.com/hashicorp/hcl/hcl/ast"
11+
hclParser "github.com/hashicorp/hcl/hcl/parser"
12+
)
13+
14+
// parseAndCheckForDuplicateHclAttributes parses the input JSON/HCL file and if it is HCL it also checks
15+
// for duplicate keys in the HCL file, allowing callers to handle the issue accordingly. In a future release we'll
16+
// change the behavior to treat duplicate keys as an error and eventually remove this helper altogether.
17+
// TODO (HCL_DUP_KEYS_DEPRECATION): remove once not used anymore
18+
func parseAndCheckForDuplicateHclAttributes(input string) (res *ast.File, duplicate bool, err error) {
19+
res, err = hcl.Parse(input)
20+
if err != nil && strings.Contains(err.Error(), "Each argument can only be defined once") {
21+
duplicate = true
22+
res, err = hclParser.ParseDontErrorOnDuplicateKeys([]byte(input))
23+
}
24+
return res, duplicate, err
25+
}

api/ssh_agent.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ func LoadSSHHelperConfig(path string) (*SSHHelperConfig, error) {
150150
// ParseSSHHelperConfig parses the given contents as a string for the SSHHelper
151151
// configuration.
152152
func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) {
153-
root, err := hcl.Parse(string(contents))
153+
// TODO (HCL_DUP_KEYS_DEPRECATION): replace with simple call to hcl.Parse once deprecation of duplicate attributes
154+
// is over, for now just ignore duplicates
155+
root, _, err := parseAndCheckForDuplicateHclAttributes(contents)
154156
if err != nil {
155157
return nil, errwrap.Wrapf("error parsing config: {{err}}", err)
156158
}

api/ssh_agent_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,20 @@ import (
88
"net/http"
99
"strings"
1010
"testing"
11+
12+
"github.com/stretchr/testify/require"
1113
)
1214

15+
// TestSSH_CanLoadDuplicateKeys verifies that during the deprecation process of duplicate HCL attributes this function
16+
// will still allow them.
17+
func TestSSH_CanLoadDuplicateKeys(t *testing.T) {
18+
_, err := LoadSSHHelperConfig("./test-fixtures/agent_config_duplicate_keys.hcl")
19+
require.NoError(t, err)
20+
// TODO (HCL_DUP_KEYS_DEPRECATION): Change test to expect an error
21+
// require.Error(t, err)
22+
// require.Contains(t, err.Error(), "Each argument can only be defined once")
23+
}
24+
1325
func TestSSH_CreateTLSClient(t *testing.T) {
1426
// load the default configuration
1527
config, err := LoadSSHHelperConfig("./test-fixtures/agent_config.hcl")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
vault_addr="http://127.0.0.1:8200"
5+
vault_addr="http://127.0.0.1:8201"
6+
ssh_mount_point="ssh"

0 commit comments

Comments
 (0)