Skip to content

Commit 6ea8432

Browse files
backport of commit bf339bc (#30418)
Co-authored-by: Robert <[email protected]>
1 parent 9ce916d commit 6ea8432

File tree

10 files changed

+132
-40
lines changed

10 files changed

+132
-40
lines changed

builtin/logical/database/path_config_connection.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,15 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
648648
"Vault (or the sdk if using a custom plugin) to gain password policy support", config.PluginName))
649649
}
650650

651+
// We can ignore the error at this point since we're simply adding a warning.
652+
dbType, _ := dbw.Type()
653+
if dbType == "snowflake" && config.ConnectionDetails["password"] != nil {
654+
resp.AddWarning(`[DEPRECATED] Single-factor password authentication is deprecated in Snowflake and will
655+
be removed by November 2025. Key pair authentication will be required after this date. Please
656+
see the Vault documentation for details on the removal of this feature. More information is
657+
available at https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification`)
658+
}
659+
651660
b.dbEvent(ctx, "config-write", req.Path, name, true)
652661
if len(resp.Warnings) == 0 {
653662
return nil, nil

changelog/30327.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
database: no longer incorrectly add an "unrecognized parameters" warning for certain SQL database secrets config operations when another warning is returned
3+
```

sdk/database/helper/connutil/sql.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"net/url"
1212
"os"
13+
"reflect"
1314
"strings"
1415
"sync"
1516
"time"
@@ -79,6 +80,19 @@ type SQLConnectionProducer struct {
7980
sync.Mutex
8081
}
8182

83+
// This provides the field names for SQLConnectionProducer for field validation in the framework handler.
84+
func SQLConnectionProducerFieldNames() map[string]any {
85+
scp := &SQLConnectionProducer{}
86+
rType := reflect.TypeOf(scp).Elem()
87+
88+
fieldNames := make(map[string]any, rType.NumField())
89+
for i := range rType.NumField() {
90+
fieldNames[rType.Field(i).Tag.Get("json")] = 1
91+
}
92+
93+
return fieldNames
94+
}
95+
8296
func (c *SQLConnectionProducer) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error {
8397
_, err := c.Init(ctx, conf, verifyConnection)
8498
return err

sdk/framework/backend.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/hashicorp/go-kms-wrapping/entropy/v2"
2525
"github.com/hashicorp/go-multierror"
2626
"github.com/hashicorp/go-secure-stdlib/parseutil"
27+
"github.com/hashicorp/vault/sdk/database/helper/connutil"
2728
"github.com/hashicorp/vault/sdk/helper/consts"
2829
"github.com/hashicorp/vault/sdk/helper/errutil"
2930
"github.com/hashicorp/vault/sdk/helper/license"
@@ -247,13 +248,18 @@ func (b *Backend) HandleRequest(ctx context.Context, req *logical.Request) (*log
247248
}
248249
}
249250

251+
// We need to check SQLConnectionProducer fields separately since they are not top-level Path fields.
252+
var sqlFields map[string]any
253+
if req.MountType == "database" && strings.HasPrefix(req.Path, "config") {
254+
sqlFields = connutil.SQLConnectionProducerFieldNames()
255+
}
250256
// Build up the data for the route, with the URL taking priority
251257
// for the fields over the PUT data.
252258
raw := make(map[string]interface{}, len(path.Fields))
253259
var ignored []string
254260
for k, v := range req.Data {
255261
raw[k] = v
256-
if !path.TakesArbitraryInput && path.Fields[k] == nil {
262+
if !path.TakesArbitraryInput && path.Fields[k] == nil && sqlFields[k] == nil {
257263
ignored = append(ignored, k)
258264
}
259265
}

sdk/framework/backend_test.go

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,49 +52,86 @@ func TestBackend_impl(t *testing.T) {
5252
}
5353

5454
func TestBackendHandleRequestFieldWarnings(t *testing.T) {
55-
handler := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
56-
return &logical.Response{
55+
t.Run("check replaced and ignored endpoints", func(t *testing.T) {
56+
handler := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
57+
return &logical.Response{
58+
Data: map[string]interface{}{
59+
"an_int": data.Get("an_int"),
60+
"a_string": data.Get("a_string"),
61+
"name": data.Get("name"),
62+
},
63+
}, nil
64+
}
65+
66+
backend := &Backend{
67+
Paths: []*Path{
68+
{
69+
Pattern: "foo/bar/(?P<name>.+)",
70+
Fields: map[string]*FieldSchema{
71+
"an_int": {Type: TypeInt},
72+
"a_string": {Type: TypeString},
73+
"name": {Type: TypeString},
74+
},
75+
Operations: map[logical.Operation]OperationHandler{
76+
logical.UpdateOperation: &PathOperation{Callback: handler},
77+
},
78+
},
79+
},
80+
}
81+
ctx := context.Background()
82+
resp, err := backend.HandleRequest(ctx, &logical.Request{
83+
Operation: logical.UpdateOperation,
84+
Path: "foo/bar/baz",
5785
Data: map[string]interface{}{
58-
"an_int": data.Get("an_int"),
59-
"a_string": data.Get("a_string"),
60-
"name": data.Get("name"),
86+
"an_int": 10,
87+
"a_string": "accepted",
88+
"unrecognized1": "unrecognized",
89+
"unrecognized2": 20.2,
90+
"name": "noop",
6191
},
62-
}, nil
63-
}
92+
})
93+
require.NoError(t, err)
94+
require.NotNil(t, resp)
95+
require.Len(t, resp.Warnings, 2)
96+
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint ignored these unrecognized parameters: [unrecognized1 unrecognized2]"))
97+
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint replaced the value of these parameters with the values captured from the endpoint's path: [name]"))
98+
})
6499

65-
backend := &Backend{
66-
Paths: []*Path{
67-
{
68-
Pattern: "foo/bar/(?P<name>.+)",
69-
Fields: map[string]*FieldSchema{
70-
"an_int": {Type: TypeInt},
71-
"a_string": {Type: TypeString},
72-
"name": {Type: TypeString},
73-
},
74-
Operations: map[logical.Operation]OperationHandler{
75-
logical.UpdateOperation: &PathOperation{Callback: handler},
100+
t.Run("check ignored DB secrets config ignored fields", func(t *testing.T) {
101+
handler := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
102+
return &logical.Response{
103+
Data: map[string]interface{}{},
104+
}, nil
105+
}
106+
107+
backend := &Backend{
108+
Paths: []*Path{
109+
{
110+
Pattern: "config/sqldb",
111+
Fields: map[string]*FieldSchema{},
112+
Operations: map[logical.Operation]OperationHandler{
113+
logical.UpdateOperation: &PathOperation{Callback: handler},
114+
},
76115
},
77116
},
78-
},
79-
}
80-
ctx := context.Background()
81-
resp, err := backend.HandleRequest(ctx, &logical.Request{
82-
Operation: logical.UpdateOperation,
83-
Path: "foo/bar/baz",
84-
Data: map[string]interface{}{
85-
"an_int": 10,
86-
"a_string": "accepted",
87-
"unrecognized1": "unrecognized",
88-
"unrecognized2": 20.2,
89-
"name": "noop",
90-
},
117+
}
118+
ctx := context.Background()
119+
resp, err := backend.HandleRequest(ctx, &logical.Request{
120+
Operation: logical.UpdateOperation,
121+
Path: "config/sqldb",
122+
MountType: "database",
123+
Data: map[string]interface{}{
124+
"connection_url": "localhost",
125+
"username": "user",
126+
"password": "pass",
127+
"unrecognized1": "unrecognized",
128+
},
129+
})
130+
require.NoError(t, err)
131+
require.NotNil(t, resp)
132+
require.Len(t, resp.Warnings, 1)
133+
require.Equal(t, resp.Warnings[0], "Endpoint ignored these unrecognized parameters: [unrecognized1]")
91134
})
92-
require.NoError(t, err)
93-
require.NotNil(t, resp)
94-
t.Log(resp.Warnings)
95-
require.Len(t, resp.Warnings, 2)
96-
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint ignored these unrecognized parameters: [unrecognized1 unrecognized2]"))
97-
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint replaced the value of these parameters with the values captured from the endpoint's path: [name]"))
98135
}
99136

100137
func TestBackendHandleRequest(t *testing.T) {

website/content/api-docs/secret/databases/snowflake.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ description: >-
88

99
# Snowflake database plugin HTTP API
1010

11+
<Warning title="Password authentication removal">
12+
Snowflake is disabling password authentication for all users in&nbsp;
13+
<a href="https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification">November of 2025.</a>
14+
&nbsp;HashiCorp is working to support key pair authentication in place of passwords.
15+
</Warning>
16+
1117
The Snowflake database plugin is one of the supported plugins for the database
1218
secrets engine. This plugin generates database credentials dynamically based on
1319
configured roles for the Snowflake database.

website/content/docs/deprecation/index.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ or raise a ticket with your support team.
3636

3737
@include 'deprecation/ruby-client-library.mdx'
3838

39+
@include 'deprecation/snowflake-password-auth.mdx'
40+
3941
</Tab>
4042
<Tab heading="PENDING REMOVAL">
4143

website/content/docs/secrets/databases/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ and private key pair to authenticate.
218218
| [Redis](/vault/docs/secrets/databases/redis) | No | Yes | Yes | Yes | No | password |
219219
| [Redis ElastiCache](/vault/docs/secrets/databases/rediselasticache) | No | No | No | Yes | No | password |
220220
| [Redshift](/vault/docs/secrets/databases/redshift) | No | Yes | Yes | Yes | Yes (1.8+) | password |
221-
| [Snowflake](/vault/docs/secrets/databases/snowflake) | No | Yes | Yes | Yes | Yes (1.8+) | password, rsa_private_key |
221+
| [Snowflake](/vault/docs/secrets/databases/snowflake) | No | Yes | Yes | Yes | Yes (1.8+) | password(deprecated), rsa_private_key |
222222

223223
## Custom plugins
224224

website/content/docs/secrets/databases/snowflake.mdx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ description: >-
99

1010
# Snowflake database secrets engine
1111

12+
<Warning title="Password authentication removal">
13+
Snowflake is disabling password authentication for all users in&nbsp;
14+
<a href="https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification">November of 2025.</a>
15+
&nbsp;HashiCorp is working to support key pair authentication in place of passwords.
16+
</Warning>
17+
1218
Snowflake is one of the supported plugins for the database secrets engine. This plugin
1319
generates database credentials dynamically based on configured roles for Snowflake-hosted
1420
databases and supports [Static Roles](/vault/docs/secrets/databases#static-roles).
@@ -23,7 +29,7 @@ The Snowflake database secrets engine uses
2329

2430
| Plugin Name | Root Credential Rotation | Dynamic Roles | Static Roles | Username Customization | Credential Types |
2531
| --------------------------- | ------------------------ | ------------- | ------------ | ---------------------- |---------------------------|
26-
| `snowflake-database-plugin` | Yes | Yes | Yes | Yes (1.8+) | password, rsa_private_key |
32+
| `snowflake-database-plugin` | Yes | Yes | Yes | Yes (1.8+) | password(deprecated), rsa_private_key |
2733

2834
## Setup
2935

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Snowflake DB password authentication ((#snowflake-db-password-auth))
2+
3+
| Announced | Expected end of support | Expected removal |
4+
| :-------: | :---------------------: | :--------------: |
5+
| APR 2025 | NOV 2025 | N/A
6+
7+
Snowflake is disabling password authentication for all users in [November of 2025](https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification).
8+
HashiCorp is working to support key pair authentication in place of passwords
9+
for this database secrets engine.

0 commit comments

Comments
 (0)