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:
parent
42cd35f13a
commit
95288615fa
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ca: support Vault agent auto-auth config for Vault CA provider using AppRole authentication.
|
||||
```
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
|
|
Loading…
Reference in New Issue