Skip to content

Commit

Permalink
Support AppRole auth method in Vault provider. Connected to cyberark#…
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMeijer committed Sep 30, 2020
1 parent 94dbc9a commit 99138a8
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 7 deletions.
40 changes: 39 additions & 1 deletion internal/providers/vault/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on the [Vault API client](https://pkg.go.dev/github.com/hashicorp/vault/api) in
Go. It reads the secret object from the configured path and returns the value
navigated to by the configured fields (or default field otherwise).

Please note that Vault API client uses the environment variable `VAULT_ADDR` to
determine the URL to the Vault API base endpoint, e.g. `https://localhost:8200`.

## Usage Documentation

The Vault provider is configured in the `secretless.yml` using:
Expand Down Expand Up @@ -86,9 +89,44 @@ services:
...
```

## Auth Methods

Vault supports various auth methods. To direct the provider to use a specific
auth method, the environment variable `VAULT_AUTH_METHOD` must be set to a
valid value. This is currently `AppRole` or `Token`. If the variable is not set,
the auth method `Token` is implied, for backwards compatibility.

### Token

Token auth method is the default and minimal auth method in Vault. It requires
the token to use in the environment variable `VAULT_TOKEN`. No additional
authentication steps are performed.

Configure the the Token auth method as follows:

```
VAULT_AUTH_METHOD=Token
VAULT_TOKEN=... # some valid Vault token
```

### AppRole

AppRole auth method requires a role ID and a secret ID. The latter may be
wrapped, requiring an unwrapping first. This is referred to as the pull mode.
The provider uses the following environment variables for the AppRole auth
method:

```
VAULT_AUTH_METHOD=AppRole
VAULT_APPROLE_ROLE_ID=... # some valid AppRole role ID
VAULT_APPROLE_SECRET_ID=... # some valid AppRole secret ID, may be wrapped
VAULT_APPROLE_UNWRAP=0 # some truthy value, true indicates unwrapping
```

## Limitations

- Only token-based login to Vault supported at the moment.
- Only Token and basic AppRole auth methods to Vault supported at the moment.
- Only environment variables are supported o configure auth methods.
- Only secrets that are "read" in Vault are supported at the moment. Backends
that require "writes" to obtain the secret (e.g. PKI, dynamic database
credentials) are not supported at the moment.
Expand Down
73 changes: 73 additions & 0 deletions internal/providers/vault/auth/approle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package auth

import (
"fmt"
vault "github.com/hashicorp/vault/api"
"os"
"strconv"
)

// DefaultAppRoleLoginPath path to AppRole login in Vault.
const DefaultAppRoleLoginPath = "auth/approle/login"

// EnvVaultAppRoleRoleID environment variable name of role ID used in AppRole auth method.
const EnvVaultAppRoleRoleID = "VAULT_APPROLE_ROLE_ID"

// EnvVaultAppRoleSecretID environment variable name of (possibly wrapped) secret ID used in AppRole auth method.
const EnvVaultAppRoleSecretID = "VAULT_APPROLE_SECRET_ID"

// EnvVaultAppRoleUnwrap environment variable name of unwrapping of secret ID in AppRole auth method.
const EnvVaultAppRoleUnwrap = "VAULT_APPROLE_UNWRAP"

// AppRoleConfig holds AppRole auth method configuration.
type AppRoleConfig struct {
LoginPath string
RoleID string
SecretID string
Unwrap bool
}

// AppRoleAuthMethodFactory performs AppRole auth method for given client.
func AppRoleAuthMethodFactory(client *vault.Client) (*vault.Secret, error) {
config := getAppRoleConfigFromEnv()

var secret *vault.Secret
var err error
if secret, err = getTokenFromAppRoleLogin(client, config); err != nil {
return nil, err
}

return secret, nil
}

// getAppRoleConfigFromEnv returns AppRole auth method configuration from environment variables.
func getAppRoleConfigFromEnv() *AppRoleConfig {
config := &AppRoleConfig{}
config.RoleID = os.Getenv(EnvVaultAppRoleRoleID)
config.SecretID = os.Getenv(EnvVaultAppRoleSecretID)
// Ignore error, default unwrap = false
config.Unwrap, _ = strconv.ParseBool(os.Getenv(EnvVaultAppRoleUnwrap))
config.LoginPath = getLoginPathFromEnv(DefaultAppRoleLoginPath)
return config
}

// getTokenFromAppRoleLogin performs AppRole login for client using given AppRole auth method configuration.
func getTokenFromAppRoleLogin(client *vault.Client, config *AppRoleConfig) (*vault.Secret, error) {
// Conditionally unwrap secret ID (referred to as pull mode in Vault)
if config.Unwrap {
secret, err := client.Logical().Unwrap(config.SecretID)
if err != nil {
return nil, fmt.Errorf("HashiCorp Vault provider unwrapping failed: %s", err)
}

// Replace with unwrapped secret ID in config
config.SecretID = secret.Data["secret_id"].(string)
}

data := map[string]interface{}{
"role_id": config.RoleID,
"secret_id": config.SecretID,
}

return client.Logical().Write(config.LoginPath, data)
}
68 changes: 68 additions & 0 deletions internal/providers/vault/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package auth

import (
"fmt"
vault "github.com/hashicorp/vault/api"
"os"
)

// EnvVarVaultAuthMethod environment variable name of Vault auth method to use.
const EnvVarVaultAuthMethod = "VAULT_AUTH_METHOD"

// EnvVarVaultLoginPath path to auth method's login in Vault.
const EnvVarVaultLoginPath = "VAULT_LOGIN_PATH"

// VaultAuthMethodFactories mapping of supported Vault auth methods by their IDs. Must have either an auth method
// factory or `nil` to signify no action is required, e.g. for Token auth method.
// NOTE: the "Token" auth methods is added for consistency. Token auth method requires no additional authentication
// steps, unlike other auth methods in Vault.
var VaultAuthMethodFactories = map[string]func(*vault.Client) (*vault.Secret, error) {
"AppRole": AppRoleAuthMethodFactory,
"Token": nil,
}

// Authenticate a Vault client using any of the supported auth methods.
// NOTE: All auth methods require an initialized Vault API client.
func Authenticate(client *vault.Client) error {
authMethod := getAuthMethodFromEnv()
applyAuthMethod, ok := VaultAuthMethodFactories[authMethod]
if !ok {
return fmt.Errorf("HashiCorp Vault provider unsupported auth method: %s", authMethod)
}

// No (additional) authentication steps required, hence done
if applyAuthMethod == nil {
return nil
}

secret, err := applyAuthMethod(client)
if err != nil {
return fmt.Errorf("HashiCorp Vault provider auth method failed: %s", err)
}
if secret == nil || secret.Auth == nil {
return fmt.Errorf("HashiCorp Vault provider login failed (no secret or auth info)")
}

client.SetToken(secret.Auth.ClientToken)
return nil
}

// getAuthMethodFromEnv returns the auth method from environment variable. If not set, it defaults to "Token" for
// backwards compatibility.
func getAuthMethodFromEnv() string {
authMethod, ok := os.LookupEnv(EnvVarVaultAuthMethod)
if !ok {
return "Token"
}
return authMethod
}

// getLoginPathFromEnv returns login path from environment variable or returns given default value otherwise.
func getLoginPathFromEnv(defaultLoginPath string) string {
// Optional path to login, otherwise default path of Vault is used
path := os.Getenv(EnvVarVaultLoginPath)
if path != "" {
return path
}
return defaultLoginPath
}
13 changes: 7 additions & 6 deletions internal/providers/vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vault

import (
"fmt"
"github.com/cyberark/secretless-broker/internal/providers/vault/auth"
"strings"

vault "github.com/hashicorp/vault/api"
Expand All @@ -15,18 +16,18 @@ type Provider struct {
Client *vault.Client
}

// ProviderFactory constructs a Provider. The API client is configured from
// environment variables. Underlying Vault API client by default uses:
// - VAULT_ADDR: endpoint of Vault, e.g. http://vault:8200/
// - VAULT_TOKEN: token to login to Vault
// ProviderFactory constructs a Provider for Vault. It uses the Vault API client.
// See Vault API docs at https://godoc.org/github.com/hashicorp/vault/api
func ProviderFactory(options plugin_v1.ProviderOptions) (plugin_v1.Provider, error) {
config := vault.DefaultConfig()

var client *vault.Client
var err error
if client, err = vault.NewClient(config); err != nil {
return nil, fmt.Errorf("ERROR: Could not create Vault provider: %s", err)
return nil, fmt.Errorf("HashiCorp Vault provider client failed: %s", err)
}

if err = auth.Authenticate(client); err != nil {
return nil, err
}

provider := &Provider{
Expand Down

0 comments on commit 99138a8

Please sign in to comment.