add provider ca support for approle auth-method

Adds support for the approle auth-method. Only handles using the approle
role/secret to auth and it doesn't support the agent's extra management
configuration options (wrap and delete after read) as they are not
required as part of the auth (ie. they are vault agent things).
This commit is contained in:
John Eikenberry 2023-03-03 19:29:53 +00:00 committed by GitHub
parent 42cd35f13a
commit 95288615fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 166 additions and 2 deletions

3
.changelog/16259.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ca: support Vault agent auto-auth config for Vault CA provider using AppRole authentication.
```

View File

@ -944,6 +944,8 @@ func configureVaultAuthMethod(authMethod *structs.VaultAuthMethod) (VaultAuthent
return NewGCPAuthClient(authMethod)
case VaultAuthMethodTypeJWT:
return NewJwtAuthClient(authMethod)
case VaultAuthMethodTypeAppRole:
return NewAppRoleAuthClient(authMethod)
case VaultAuthMethodTypeKubernetes:
return NewK8sAuthClient(authMethod)
// These auth methods require a username for the login API path.
@ -968,7 +970,6 @@ func configureVaultAuthMethod(authMethod *structs.VaultAuthMethod) (VaultAuthent
"please provide the token with the 'token' parameter in the CA configuration")
// The rest of the auth methods use auth/<auth method path> login API path.
case VaultAuthMethodTypeAliCloud,
VaultAuthMethodTypeAppRole,
VaultAuthMethodTypeCloudFoundry,
VaultAuthMethodTypeGitHub,
VaultAuthMethodTypeKerberos,

View File

@ -0,0 +1,66 @@
package ca
import (
"bytes"
"fmt"
"os"
"strings"
"github.com/hashicorp/consul/agent/structs"
)
// left out 2 config options as we are re-using vault agent's auth config.
// Why?
// remove_secret_id_file_after_reading - don't remove what we don't own
// secret_id_response_wrapping_path - wrapping the secret before writing to disk
// (which we don't need to do)
func NewAppRoleAuthClient(authMethod *structs.VaultAuthMethod) (*VaultAuthClient, error) {
authClient := NewVaultAPIAuthClient(authMethod, "")
// check for hardcoded /login params
if legacyCheck(authMethod.Params, "role_id", "secret_id") {
return authClient, nil
}
// check for required config params
key := "role_id_file_path"
if val, ok := authMethod.Params[key].(string); !ok {
return nil, fmt.Errorf("missing '%s' value", key)
} else if strings.TrimSpace(val) == "" {
return nil, fmt.Errorf("'%s' value is empty", key)
}
authClient.LoginDataGen = ArLoginDataGen
return authClient, nil
}
func ArLoginDataGen(authMethod *structs.VaultAuthMethod) (map[string]any, error) {
// don't need to check for legacy params as this func isn't used in that case
params := authMethod.Params
// role_id is required
roleIdFilePath := params["role_id_file_path"].(string)
// secret_id is optional (secret_ok is used in check below)
// secretIdFilePath, secret_ok := params["secret_id_file_path"].(string)
secretIdFilePath, hasSecret := params["secret_id_file_path"].(string)
if hasSecret && strings.TrimSpace(secretIdFilePath) == "" {
hasSecret = false
}
var err error
var rawRoleID, rawSecretID []byte
data := make(map[string]any)
if rawRoleID, err = os.ReadFile(roleIdFilePath); err != nil {
return nil, err
}
data["role_id"] = string(rawRoleID)
if hasSecret {
switch rawSecretID, err = os.ReadFile(secretIdFilePath); {
case err != nil:
return nil, err
case len(bytes.TrimSpace(rawSecretID)) > 0:
data["secret_id"] = strings.TrimSpace(string(rawSecretID))
}
}
return data, nil
}

View File

@ -568,3 +568,97 @@ func TestVaultCAProvider_K8sAuthClient(t *testing.T) {
})
}
}
func TestVaultCAProvider_AppRoleAuthClient(t *testing.T) {
roleID, secretID := "test_role_id", "test_secret_id"
roleFd, err := os.CreateTemp("", "role")
require.NoError(t, err)
_, err = roleFd.WriteString(roleID)
require.NoError(t, err)
err = roleFd.Close()
require.NoError(t, err)
secretFd, err := os.CreateTemp("", "secret")
require.NoError(t, err)
_, err = secretFd.WriteString(secretID)
require.NoError(t, err)
err = secretFd.Close()
require.NoError(t, err)
roleIdPath := roleFd.Name()
secretIdPath := secretFd.Name()
defer func() {
os.Remove(secretFd.Name())
os.Remove(roleFd.Name())
}()
cases := map[string]struct {
authMethod *structs.VaultAuthMethod
expData map[string]any
expErr error
}{
"base-case": {
authMethod: &structs.VaultAuthMethod{
Type: "approle",
Params: map[string]any{
"role_id_file_path": roleIdPath,
"secret_id_file_path": secretIdPath,
},
},
expData: map[string]any{
"role_id": roleID,
"secret_id": secretID,
},
},
"optional-secret-left-out": {
authMethod: &structs.VaultAuthMethod{
Type: "approle",
Params: map[string]any{
"role_id_file_path": roleIdPath,
},
},
expData: map[string]any{
"role_id": roleID,
},
},
"missing-role-id-file-path": {
authMethod: &structs.VaultAuthMethod{
Type: "approle",
Params: map[string]any{},
},
expErr: fmt.Errorf("missing '%s' value", "role_id_file_path"),
},
"legacy-direct-values": {
authMethod: &structs.VaultAuthMethod{
Type: "approle",
Params: map[string]any{
"role_id": "test-role",
"secret_id": "test-secret",
},
},
expData: map[string]any{
"role_id": "test-role",
"secret_id": "test-secret",
},
},
}
for k, c := range cases {
t.Run(k, func(t *testing.T) {
auth, err := NewAppRoleAuthClient(c.authMethod)
if c.expErr != nil {
require.Error(t, err)
require.EqualError(t, c.expErr, err.Error())
return
}
require.NoError(t, err)
if auth.LoginDataGen != nil {
data, err := auth.LoginDataGen(c.authMethod)
require.NoError(t, err)
require.Equal(t, c.expData, data)
}
})
}
}

View File

@ -105,7 +105,7 @@ func TestVaultCAProvider_configureVaultAuthMethod(t *testing.T) {
hasLDG bool
}{
"alicloud": {expLoginPath: "auth/alicloud/login"},
"approle": {expLoginPath: "auth/approle/login"},
"approle": {expLoginPath: "auth/approle/login", params: map[string]any{"role_id_file_path": "test-path"}, hasLDG: true},
"aws": {expLoginPath: "auth/aws/login", params: map[string]interface{}{"type": "iam"}, hasLDG: true},
"azure": {expLoginPath: "auth/azure/login", params: map[string]interface{}{"role": "test-role", "resource": "test-resource"}, hasLDG: true},
"cf": {expLoginPath: "auth/cf/login"},