mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
Add snowflake DB API warning (#30327)
* Add API warning based on DB type * Add deprecation notice * Add warning to the top of the docs pages * Update capabilities table * Filter SQLConnectionProducer fields from unrecognized parameters warning * Add test case
This commit is contained in:
parent
8a84d13c60
commit
bf339bc50d
10 changed files with 134 additions and 42 deletions
|
|
@ -648,6 +648,15 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
|
|||
"Vault (or the sdk if using a custom plugin) to gain password policy support", config.PluginName))
|
||||
}
|
||||
|
||||
// We can ignore the error at this point since we're simply adding a warning.
|
||||
dbType, _ := dbw.Type()
|
||||
if dbType == "snowflake" && config.ConnectionDetails["password"] != nil {
|
||||
resp.AddWarning(`[DEPRECATED] Single-factor password authentication is deprecated in Snowflake and will
|
||||
be removed by November 2025. Key pair authentication will be required after this date. Please
|
||||
see the Vault documentation for details on the removal of this feature. More information is
|
||||
available at https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification`)
|
||||
}
|
||||
|
||||
b.dbEvent(ctx, "config-write", req.Path, name, true)
|
||||
if len(resp.Warnings) == 0 {
|
||||
return nil, nil
|
||||
|
|
|
|||
3
changelog/30327.txt
Normal file
3
changelog/30327.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
database: no longer incorrectly add an "unrecognized parameters" warning for certain SQL database secrets config operations when another warning is returned
|
||||
```
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -79,6 +80,19 @@ type SQLConnectionProducer struct {
|
|||
sync.Mutex
|
||||
}
|
||||
|
||||
// This provides the field names for SQLConnectionProducer for field validation in the framework handler.
|
||||
func SQLConnectionProducerFieldNames() map[string]any {
|
||||
scp := &SQLConnectionProducer{}
|
||||
rType := reflect.TypeOf(scp).Elem()
|
||||
|
||||
fieldNames := make(map[string]any, rType.NumField())
|
||||
for i := range rType.NumField() {
|
||||
fieldNames[rType.Field(i).Tag.Get("json")] = 1
|
||||
}
|
||||
|
||||
return fieldNames
|
||||
}
|
||||
|
||||
func (c *SQLConnectionProducer) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error {
|
||||
_, err := c.Init(ctx, conf, verifyConnection)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/hashicorp/go-kms-wrapping/entropy/v2"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
"github.com/hashicorp/vault/sdk/database/helper/connutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/sdk/helper/errutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/license"
|
||||
|
|
@ -247,13 +248,18 @@ func (b *Backend) HandleRequest(ctx context.Context, req *logical.Request) (*log
|
|||
}
|
||||
}
|
||||
|
||||
// We need to check SQLConnectionProducer fields separately since they are not top-level Path fields.
|
||||
var sqlFields map[string]any
|
||||
if req.MountType == "database" && strings.HasPrefix(req.Path, "config") {
|
||||
sqlFields = connutil.SQLConnectionProducerFieldNames()
|
||||
}
|
||||
// Build up the data for the route, with the URL taking priority
|
||||
// for the fields over the PUT data.
|
||||
raw := make(map[string]interface{}, len(path.Fields))
|
||||
var ignored []string
|
||||
for k, v := range req.Data {
|
||||
raw[k] = v
|
||||
if !path.TakesArbitraryInput && path.Fields[k] == nil {
|
||||
if !path.TakesArbitraryInput && path.Fields[k] == nil && sqlFields[k] == nil {
|
||||
ignored = append(ignored, k)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,49 +52,86 @@ func TestBackend_impl(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBackendHandleRequestFieldWarnings(t *testing.T) {
|
||||
handler := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"an_int": data.Get("an_int"),
|
||||
"a_string": data.Get("a_string"),
|
||||
"name": data.Get("name"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
t.Run("check replaced and ignored endpoints", func(t *testing.T) {
|
||||
handler := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"an_int": data.Get("an_int"),
|
||||
"a_string": data.Get("a_string"),
|
||||
"name": data.Get("name"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
backend := &Backend{
|
||||
Paths: []*Path{
|
||||
{
|
||||
Pattern: "foo/bar/(?P<name>.+)",
|
||||
Fields: map[string]*FieldSchema{
|
||||
"an_int": {Type: TypeInt},
|
||||
"a_string": {Type: TypeString},
|
||||
"name": {Type: TypeString},
|
||||
},
|
||||
Operations: map[logical.Operation]OperationHandler{
|
||||
logical.UpdateOperation: &PathOperation{Callback: handler},
|
||||
backend := &Backend{
|
||||
Paths: []*Path{
|
||||
{
|
||||
Pattern: "foo/bar/(?P<name>.+)",
|
||||
Fields: map[string]*FieldSchema{
|
||||
"an_int": {Type: TypeInt},
|
||||
"a_string": {Type: TypeString},
|
||||
"name": {Type: TypeString},
|
||||
},
|
||||
Operations: map[logical.Operation]OperationHandler{
|
||||
logical.UpdateOperation: &PathOperation{Callback: handler},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := backend.HandleRequest(ctx, &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "foo/bar/baz",
|
||||
Data: map[string]interface{}{
|
||||
"an_int": 10,
|
||||
"a_string": "accepted",
|
||||
"unrecognized1": "unrecognized",
|
||||
"unrecognized2": 20.2,
|
||||
"name": "noop",
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := backend.HandleRequest(ctx, &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "foo/bar/baz",
|
||||
Data: map[string]interface{}{
|
||||
"an_int": 10,
|
||||
"a_string": "accepted",
|
||||
"unrecognized1": "unrecognized",
|
||||
"unrecognized2": 20.2,
|
||||
"name": "noop",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.Len(t, resp.Warnings, 2)
|
||||
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint ignored these unrecognized parameters: [unrecognized1 unrecognized2]"))
|
||||
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint replaced the value of these parameters with the values captured from the endpoint's path: [name]"))
|
||||
})
|
||||
|
||||
t.Run("check ignored DB secrets config ignored fields", func(t *testing.T) {
|
||||
handler := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
backend := &Backend{
|
||||
Paths: []*Path{
|
||||
{
|
||||
Pattern: "config/sqldb",
|
||||
Fields: map[string]*FieldSchema{},
|
||||
Operations: map[logical.Operation]OperationHandler{
|
||||
logical.UpdateOperation: &PathOperation{Callback: handler},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
resp, err := backend.HandleRequest(ctx, &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/sqldb",
|
||||
MountType: "database",
|
||||
Data: map[string]interface{}{
|
||||
"connection_url": "localhost",
|
||||
"username": "user",
|
||||
"password": "pass",
|
||||
"unrecognized1": "unrecognized",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
require.Len(t, resp.Warnings, 1)
|
||||
require.Equal(t, resp.Warnings[0], "Endpoint ignored these unrecognized parameters: [unrecognized1]")
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
t.Log(resp.Warnings)
|
||||
require.Len(t, resp.Warnings, 2)
|
||||
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint ignored these unrecognized parameters: [unrecognized1 unrecognized2]"))
|
||||
require.True(t, strutil.StrListContains(resp.Warnings, "Endpoint replaced the value of these parameters with the values captured from the endpoint's path: [name]"))
|
||||
}
|
||||
|
||||
func TestBackendHandleRequest(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ description: >-
|
|||
|
||||
# Snowflake database plugin HTTP API
|
||||
|
||||
<Warning title="Password authentication removal">
|
||||
Snowflake is disabling password authentication for all users in
|
||||
<a href="https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification">November of 2025.</a>
|
||||
HashiCorp is working to support key pair authentication in place of passwords.
|
||||
</Warning>
|
||||
|
||||
The Snowflake database plugin is one of the supported plugins for the database
|
||||
secrets engine. This plugin generates database credentials dynamically based on
|
||||
configured roles for the Snowflake database.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ or raise a ticket with your support team.
|
|||
|
||||
@include 'deprecation/ruby-client-library.mdx'
|
||||
|
||||
@include 'deprecation/snowflake-password-auth.mdx'
|
||||
|
||||
</Tab>
|
||||
<Tab heading="PENDING REMOVAL">
|
||||
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ and private key pair to authenticate.
|
|||
| [Redis](/vault/docs/secrets/databases/redis) | No | Yes | Yes | Yes | No | password |
|
||||
| [Redis ElastiCache](/vault/docs/secrets/databases/rediselasticache) | No | No | No | Yes | No | password |
|
||||
| [Redshift](/vault/docs/secrets/databases/redshift) | No | Yes | Yes | Yes | Yes (1.8+) | password |
|
||||
| [Snowflake](/vault/docs/secrets/databases/snowflake) | No | Yes | Yes | Yes | Yes (1.8+) | password, rsa_private_key |
|
||||
| [Snowflake](/vault/docs/secrets/databases/snowflake) | No | Yes | Yes | Yes | Yes (1.8+) | password(deprecated), rsa_private_key |
|
||||
|
||||
## Custom plugins
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ description: >-
|
|||
|
||||
# Snowflake database secrets engine
|
||||
|
||||
<Warning title="Password authentication removal">
|
||||
Snowflake is disabling password authentication for all users in
|
||||
<a href="https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification">November of 2025.</a>
|
||||
HashiCorp is working to support key pair authentication in place of passwords.
|
||||
</Warning>
|
||||
|
||||
Snowflake is one of the supported plugins for the database secrets engine. This plugin
|
||||
generates database credentials dynamically based on configured roles for Snowflake-hosted
|
||||
databases and supports [Static Roles](/vault/docs/secrets/databases#static-roles).
|
||||
|
|
@ -23,7 +29,7 @@ The Snowflake database secrets engine uses
|
|||
|
||||
| Plugin Name | Root Credential Rotation | Dynamic Roles | Static Roles | Username Customization | Credential Types |
|
||||
| --------------------------- | ------------------------ | ------------- | ------------ | ---------------------- |---------------------------|
|
||||
| `snowflake-database-plugin` | Yes | Yes | Yes | Yes (1.8+) | password, rsa_private_key |
|
||||
| `snowflake-database-plugin` | Yes | Yes | Yes | Yes (1.8+) | password(deprecated), rsa_private_key |
|
||||
|
||||
## Setup
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
## Snowflake DB password authentication ((#snowflake-db-password-auth))
|
||||
|
||||
| Announced | Expected end of support | Expected removal |
|
||||
| :-------: | :---------------------: | :--------------: |
|
||||
| APR 2025 | NOV 2025 | N/A
|
||||
|
||||
Snowflake is disabling password authentication for all users in [November of 2025](https://www.snowflake.com/en/blog/blocking-single-factor-password-authentification).
|
||||
HashiCorp is working to support key pair authentication in place of passwords
|
||||
for this database secrets engine.
|
||||
Loading…
Reference in a new issue