rebase: update github.com/libopenstorage/secrets to latest

With this update, we no longer import github.com/hashicorp/vault
which now is under BSL license.
https://github.com/hashicorp/vault/blob/main/LICENSE

resolves: #4196

Signed-off-by: Rakshith R <rar@redhat.com>
This commit is contained in:
Rakshith R
2023-10-17 10:29:04 +05:30
committed by mergify[bot]
parent 5ff0607360
commit e46a007961
131 changed files with 3931 additions and 16000 deletions

View File

@ -1,6 +1,6 @@
language: go
go:
- 1.14.6
- 1.19.x
script:
- |
make unit-test

View File

@ -6,3 +6,6 @@ unit-test:
ci-test:
go test -timeout 1800s -v github.com/libopenstorage/secrets/vault -tags ci
integration-test:
go test -timeout 1800s -v github.com/libopenstorage/secrets/vault -tags integration

View File

@ -42,16 +42,17 @@ const (
)
const (
TypeAWS = "aws-kms"
TypeAzure = "azure-kv"
TypeDCOS = "dcos"
TypeDocker = "docker"
TypeGCloud = "gcloud-kms"
TypeIBM = "ibm-kp"
TypeK8s = "k8s"
TypeKVDB = "kvdb"
TypeVault = "vault"
TypeVaultTransit = "vault-transit"
TypeAWSKMS = "aws-kms"
TypeAzure = "azure-kv"
TypeDCOS = "dcos"
TypeDocker = "docker"
TypeGCloud = "gcloud-kms"
TypeIBM = "ibm-kp"
TypeK8s = "k8s"
TypeKVDB = "kvdb"
TypeVault = "vault"
TypeVaultTransit = "vault-transit"
TypeAWSSecretsManager = "aws-secrets-manager"
)
const (
@ -64,6 +65,14 @@ const (
DestroySecret = "destroy-all-secret-versions"
)
// Version represents the unique identifier associated with the version of the new secret.
type Version string
const (
// NoVersion indicates that the provider does not support versions for secrets
NoVersion Version = "noversion"
)
// Secrets interface implemented by backend Key Management Systems (KMS)
type Secrets interface {
// String representation of the backend KMS
@ -76,15 +85,17 @@ type Secrets interface {
GetSecret(
secretId string,
keyContext map[string]string,
) (map[string]interface{}, error)
) (map[string]interface{}, Version, error)
// PutSecret will associate an secretId to its secret data
// provided in the arguments and store it into the secret backend
// The caller should ensure they use unique secretIDs so that they won't
// unknowingly overwrite an existing secret.
PutSecret(
secretId string,
plainText map[string]interface{},
keyContext map[string]string,
) error
) (Version, error)
// DeleteSecret deletes the secret data associated with the
// supplied secretId.
@ -141,6 +152,17 @@ func (e *ErrInvalidKeyContext) Error() string {
return fmt.Sprintf("invalid key context: %v", e.Reason)
}
// ErrProviderInternal is returned when an error is received from the secrets provider which
// is not known to this library
type ErrProviderInternal struct {
Provider string
Reason string
}
func (e *ErrProviderInternal) Error() string {
return fmt.Sprintf("%v returned error: %v", e.Provider, e.Reason)
}
// KeyContextChecks performs a series of checks on the keys and values
// passed through the key context map
func KeyContextChecks(

View File

@ -0,0 +1,97 @@
package secrets
import (
"context"
"fmt"
"sync"
)
type ReaderInit func(map[string]interface{}) (SecretReader, error)
type StoreInit func(map[string]interface{}) (SecretStore, error)
var (
secretReaders = make(map[string]ReaderInit)
secretStores = make(map[string]StoreInit)
readersLock sync.RWMutex
storesLock sync.RWMutex
)
// NewReader returns a new instance of SecretReader backend SM identified by
// the supplied name. SecretConfig is a map of key value pairs which could
// be used for authenticating with the backend
func NewReader(name string, secretConfig map[string]interface{}) (SecretReader, error) {
readersLock.RLock()
defer readersLock.RUnlock()
if init, exists := secretReaders[name]; exists {
return init(secretConfig)
}
return nil, ErrNotSupported
}
// NewStore returns a new instance of SecretStore backend SM identified by
// the supplied name. SecretConfig is a map of key value pairs which could
// be used for authenticating with the backend
func NewStore(name string, secretConfig map[string]interface{}) (SecretStore, error) {
storesLock.RLock()
defer storesLock.RUnlock()
if init, exists := secretStores[name]; exists {
return init(secretConfig)
}
return nil, ErrNotSupported
}
// RegisterReader adds a new backend KMS that implements SecretReader
func RegisterReader(name string, init ReaderInit) error {
readersLock.Lock()
defer readersLock.Unlock()
if _, exists := secretReaders[name]; exists {
return fmt.Errorf("secrets reader %v is already registered", name)
}
secretReaders[name] = init
return nil
}
// RegisterStore adds a new backend KMS that implements SecretStore and SecretReader
func RegisterStore(name string, init StoreInit) error {
storesLock.Lock()
defer storesLock.Unlock()
if _, exists := secretStores[name]; exists {
return fmt.Errorf("secrets store %v is already registered", name)
}
secretStores[name] = init
return RegisterReader(name, func(m map[string]interface{}) (SecretReader, error) {
return init(m)
})
}
// A SecretKey identifies a secret
type SecretKey struct {
// Prefix is an optional part of the SecretKey.
Prefix string
// Name is a mandatory part of the SecretKey.
Name string
}
// SecretReader interface implemented by Secrets Managers to read secrets
type SecretReader interface {
// String representation of the backend.
String() string
// Get returns the secret associate with the supplied key.
Get(ctx context.Context, key SecretKey) (secret map[string]interface{}, err error)
}
// SecretStore interface implemented by Secrets Managers to set and delete secrets.
type SecretStore interface {
SecretReader
// Set stores the secret data identified by the key.
// The caller should ensure they use unique key so that they won't
// unknowingly overwrite an existing secret.
Set(ctx context.Context, key SecretKey, secret map[string]interface{}) error
// Delete deletes the secret data associated with the supplied key.
Delete(ctx context.Context, key SecretKey) error
}

View File

@ -3,24 +3,23 @@ package utils
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"path"
"strconv"
"strings"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/agent/auth"
"github.com/hashicorp/vault/command/agent/auth/kubernetes"
"github.com/hashicorp/vault/api/auth/approle"
"github.com/hashicorp/vault/api/auth/kubernetes"
)
const (
vaultAddressPrefix = "http"
vaultAddressPrefix = "http"
// AuthMethodKubernetes is a named auth method.
AuthMethodKubernetes = "kubernetes"
// AuthMethodApprole
AuthMethodAppRole = "approle"
// AuthMethod is a vault authentication method used.
// https://www.vaultproject.io/docs/auth#auth-methods
AuthMethod = "VAULT_AUTH_METHOD"
@ -33,6 +32,10 @@ const (
AuthKubernetesTokenPath = "VAULT_AUTH_KUBERNETES_TOKEN_PATH"
// AuthKubernetesMountPath
AuthKubernetesMountPath = "kubernetes"
// AuthAppRoleRoleID
AuthAppRoleRoleID = "VAULT_APPROLE_ROLE_ID"
// AuthAppRoleSecretID
AuthAppRoleSecretID = "VAULT_APPROLE_SECRET_ID"
)
var (
@ -40,6 +43,7 @@ var (
ErrVaultAddressNotSet = errors.New("VAULT_ADDR not set")
ErrInvalidVaultToken = errors.New("VAULT_TOKEN is invalid")
ErrInvalidSkipVerify = errors.New("VAULT_SKIP_VERIFY is invalid")
ErrAppRoleIDNotSet = errors.New("VAULT_APPROLE_ROLE_ID or VAULT_APPROLE_SECRET_ID not set")
ErrInvalidVaultAddress = errors.New("VAULT_ADDRESS is invalid. " +
"Should be of the form http(s)://<ip>:<port>")
@ -117,23 +121,29 @@ func Authenticate(client *api.Client, config map[string]interface{}) (token stri
return token, false, nil
}
// or try to use the kubernetes auth method
// or use other authentication method: kubernetes, approle
if GetVaultParam(config, AuthMethod) != "" {
token, err = GetAuthToken(client, config)
return token, true, err
}
return "", false, ErrVaultAuthParamsNotSet
}
}
// GetAuthToken tries to get the vault token for the provided authentication method.
func GetAuthToken(client *api.Client, config map[string]interface{}) (string, error) {
path, _, data, err := authenticate(client, config)
if err != nil {
return "", err
method := GetVaultParam(config, AuthMethod)
var secret *api.Secret
var err error
switch method {
case AuthMethodKubernetes:
secret, err = authKubernetes(client, config)
case AuthMethodAppRole:
secret, err = authAppRole(client, config)
default:
return "", ErrAuthMethodUnknown
}
secret, err := client.Logical().Write(path, data)
if err != nil {
return "", err
}
@ -144,49 +154,52 @@ func GetAuthToken(client *api.Client, config map[string]interface{}) (string, er
return "", errors.New("authentication returned empty client token")
}
return secret.Auth.ClientToken, err
return secret.Auth.ClientToken, nil
}
func authenticate(client *api.Client, config map[string]interface{}) (string, http.Header, map[string]interface{}, error) {
method := GetVaultParam(config, AuthMethod)
switch method {
case AuthMethodKubernetes:
return authKubernetes(client, config)
}
return "", nil, nil, fmt.Errorf("%s method: %s", method, ErrAuthMethodUnknown)
}
func authKubernetes(client *api.Client, config map[string]interface{}) (string, http.Header, map[string]interface{}, error) {
authConfig, err := buildAuthConfig(config)
if err != nil {
return "", nil, nil, err
}
method, err := kubernetes.NewKubernetesAuthMethod(authConfig)
if err != nil {
return "", nil, nil, err
}
return method.Authenticate(context.TODO(), client)
}
func buildAuthConfig(config map[string]interface{}) (*auth.AuthConfig, error) {
func authKubernetes(client *api.Client, config map[string]interface{}) (*api.Secret, error) {
role := GetVaultParam(config, AuthKubernetesRole)
if role == "" {
return nil, ErrKubernetesRole
}
loginOpts := []kubernetes.LoginOption{}
mountPath := GetVaultParam(config, AuthMountPath)
if mountPath == "" {
mountPath = AuthKubernetesMountPath
}
tokenPath := GetVaultParam(config, AuthKubernetesTokenPath)
loginOpts = append(loginOpts, kubernetes.WithMountPath(mountPath))
authMountPath := path.Join("auth", mountPath)
return &auth.AuthConfig{
Logger: hclog.NewNullLogger(),
MountPath: authMountPath,
Config: map[string]interface{}{
"role": role,
"token_path": tokenPath,
},
}, nil
tokenPath := GetVaultParam(config, AuthKubernetesTokenPath)
if tokenPath != "" {
loginOpts = append(loginOpts, kubernetes.WithServiceAccountTokenPath(tokenPath))
}
kAuth, err := kubernetes.NewKubernetesAuth(role, loginOpts...)
if err != nil {
return nil, err
}
return kAuth.Login(context.TODO(), client)
}
func authAppRole(client *api.Client, config map[string]interface{}) (*api.Secret, error) {
roleID := GetVaultParam(config, AuthAppRoleRoleID)
secretIDRaw := GetVaultParam(config, AuthAppRoleSecretID)
if roleID == "" || secretIDRaw == "" {
return nil, ErrAppRoleIDNotSet
}
secretID := &approle.SecretID{FromString: secretIDRaw}
appRoleAuth, err := approle.NewAppRoleAuth(
roleID,
secretID,
)
if err != nil {
return nil, err
}
return client.Auth().Login(context.TODO(), appRoleAuth)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/vault/api"
"github.com/libopenstorage/secrets"
"github.com/libopenstorage/secrets/vault/utils"
"github.com/sirupsen/logrus"
)
const (
@ -28,6 +29,8 @@ const (
AuthKubernetesRole = utils.AuthKubernetesRole
AuthKubernetesTokenPath = utils.AuthKubernetesTokenPath
AuthKubernetesMountPath = utils.AuthKubernetesMountPath
AuthAppRoleRoleID = utils.AuthAppRoleRoleID
AuthAppRoleSecretID = utils.AuthAppRoleSecretID
)
func init() {
@ -100,6 +103,14 @@ func New(
}
client.SetToken(token)
authMethod := "token"
method := utils.GetVaultParam(secretConfig, AuthMethod)
if method != "" && utils.GetVaultParam(secretConfig, api.EnvVaultToken) == "" {
authMethod = method
}
logrus.Infof("Authenticated to Vault with %v\n", authMethod)
backendPath := utils.GetVaultParam(secretConfig, VaultBackendPathKey)
if backendPath == "" {
backendPath = DefaultBackendPath
@ -164,25 +175,29 @@ func (v *vaultSecrets) keyPath(secretID string, keyContext map[string]string) ke
func (v *vaultSecrets) GetSecret(
secretID string,
keyContext map[string]string,
) (map[string]interface{}, error) {
) (map[string]interface{}, secrets.Version, error) {
key := v.keyPath(secretID, keyContext)
secretValue, err := v.read(key)
if err != nil {
return nil, fmt.Errorf("failed to get secret: %s: %s", key, err)
return nil, secrets.NoVersion, fmt.Errorf("failed to get secret: %s: %s", key, err)
}
if secretValue == nil {
return nil, secrets.ErrInvalidSecretId
return nil, secrets.NoVersion, secrets.ErrInvalidSecretId
}
if v.isKvBackendV2 {
if data, exists := secretValue.Data[kvDataKey]; exists && data != nil {
if data, ok := data.(map[string]interface{}); ok {
return data, nil
// TODO: Vault does support versioned secrets with KV Backend 2
// However it requires clients to invoke a different metadata API call
// to fetch the version. Once there is a need for versions from Vault
// we can add support for it.
return data, secrets.NoVersion, nil
}
}
return nil, secrets.ErrInvalidSecretId
return nil, secrets.NoVersion, secrets.ErrInvalidSecretId
} else {
return secretValue.Data, nil
return secretValue.Data, secrets.NoVersion, nil
}
}
@ -190,7 +205,7 @@ func (v *vaultSecrets) PutSecret(
secretID string,
secretData map[string]interface{},
keyContext map[string]string,
) error {
) (secrets.Version, error) {
if v.isKvBackendV2 {
secretData = map[string]interface{}{
kvDataKey: secretData,
@ -199,9 +214,9 @@ func (v *vaultSecrets) PutSecret(
key := v.keyPath(secretID, keyContext)
if _, err := v.write(key, secretData); err != nil {
return fmt.Errorf("failed to put secret: %s: %s", key, err)
return secrets.NoVersion, fmt.Errorf("failed to put secret: %s: %s", key, err)
}
return nil
return secrets.NoVersion, nil
}
func (v *vaultSecrets) DeleteSecret(