Skip to content

Commit

Permalink
Support AppRole authentication (spiffe#5058)
Browse files Browse the repository at this point in the history
  • Loading branch information
InverseIntegral committed Sep 9, 2024
1 parent 89c8b77 commit f5f1c14
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 4 deletions.
39 changes: 35 additions & 4 deletions pkg/server/plugin/keymanager/hashicorpvault/hashicorp_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,35 @@ type pluginHooks struct {
type Config struct {
// A URL of Vault server. (e.g., https://vault.example.com:8443/)
VaultAddr string `hcl:"vault_addr" json:"vault_addr"`
// Name of the Vault namespace
Namespace string `hcl:"namespace" json:"namespace"`

// Configuration for the Token authentication method
TokenAuth *TokenAuthConfig `hcl:"token_auth" json:"token_auth,omitempty"`

// Name of the Vault namespace
Namespace string `hcl:"namespace" json:"namespace"`
// Configuration for the AppRole authentication method
AppRoleAuth *AppRoleAuthConfig `hcl:"approle_auth" json:"approle_auth,omitempty"`

// TODO: Support other auth methods
// TODO: Support client certificate and key
}

// TokenAuthConfig represents parameters for token auth method
type TokenAuthConfig struct {
// Token string to set into "X-Vault-Token" header
Token string `hcl:"token" json:"token"`
}

// AppRoleAuthConfig represents parameters for AppRole auth method.
type AppRoleAuthConfig struct {
// Name of the mount point where AppRole auth method is mounted. (e.g., /auth/<mount_point>/login)
// If the value is empty, use default mount point (/auth/approle)
AppRoleMountPoint string `hcl:"approle_auth_mount_point" json:"approle_auth_mount_point"`
// An identifier that selects the AppRole
RoleID string `hcl:"approle_id" json:"approle_id"`
// A credential that is required for login.
SecretID string `hcl:"approle_secret_id" json:"approle_secret_id"`
}

// Plugin is the main representation of this keymanager plugin
type Plugin struct {
keymanagerv1.UnsafeKeyManagerServer
Expand Down Expand Up @@ -138,11 +151,25 @@ func parseAuthMethod(config *Config) (AuthMethod, error) {
authMethod = TOKEN
}

if config.AppRoleAuth != nil {
if err := checkForAuthMethodConfigured(authMethod); err != nil {
return 0, err
}
authMethod = APPROLE
}

if authMethod != 0 {
return authMethod, nil
}

return 0, status.Error(codes.InvalidArgument, "must be configured one of these authentication method 'Token'")
return 0, status.Error(codes.InvalidArgument, "one of the available authentication methods must be configured: 'Token, AppRole'")
}

func checkForAuthMethodConfigured(authMethod AuthMethod) error {
if authMethod != 0 {
return status.Error(codes.InvalidArgument, "only one authentication method can be configured")
}
return nil
}

func (p *Plugin) genClientParams(method AuthMethod, config *Config) (*ClientParams, error) {
Expand All @@ -154,6 +181,10 @@ func (p *Plugin) genClientParams(method AuthMethod, config *Config) (*ClientPara
switch method {
case TOKEN:
cp.Token = p.getEnvOrDefault(envVaultToken, config.TokenAuth.Token)
case APPROLE:
cp.AppRoleAuthMountPoint = config.AppRoleAuth.AppRoleMountPoint
cp.AppRoleID = p.getEnvOrDefault(envVaultAppRoleID, config.AppRoleAuth.RoleID)
cp.AppRoleSecretID = p.getEnvOrDefault(envVaultAppRoleSecretID, config.AppRoleAuth.SecretID)
}

return cp, nil
Expand Down
64 changes: 64 additions & 0 deletions pkg/server/plugin/keymanager/hashicorpvault/vault_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,70 @@ func TestNewAuthenticatedClientTokenAuth(t *testing.T) {
}
}

func TestNewAuthenticatedClientAppRoleAuth(t *testing.T) {
fakeVaultServer := newFakeVaultServer()
fakeVaultServer.AppRoleAuthResponseCode = 200
for _, tt := range []struct {
name string
response []byte
renew bool
namespace string
}{
{
name: "AppRole Authentication success / Token is renewable",
response: []byte(testAppRoleAuthResponse),
renew: true,
},
{
name: "AppRole Authentication success / Token is not renewable",
response: []byte(testAppRoleAuthResponseNotRenewable),
},
{
name: "AppRole Authentication success / Token is renewable / Namespace is given",
response: []byte(testAppRoleAuthResponse),
renew: true,
namespace: "test-ns",
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
fakeVaultServer.AppRoleAuthResponse = tt.response

s, addr, err := fakeVaultServer.NewTLSServer()
require.NoError(t, err)

s.Start()
defer s.Close()

cp := &ClientParams{
VaultAddr: fmt.Sprintf("https://%v/", addr),
Namespace: tt.namespace,
CACertPath: testRootCert,
AppRoleID: "test-approle-id",
AppRoleSecretID: "test-approle-secret-id",
}
cc, err := NewClientConfig(cp, hclog.Default())
require.NoError(t, err)

renewCh := make(chan struct{})
client, err := cc.NewAuthenticatedClient(APPROLE, renewCh)
require.NoError(t, err)

select {
case <-renewCh:
require.Equal(t, false, tt.renew)
default:
require.Equal(t, true, tt.renew)
}

if cp.Namespace != "" {
headers := client.vaultClient.Headers()
require.Equal(t, cp.Namespace, headers.Get(consts.NamespaceHeaderName))
}
})
}
}

func TestRenewTokenFailed(t *testing.T) {
fakeVaultServer := newFakeVaultServer()
fakeVaultServer.LookupSelfResponse = []byte(testLookupSelfResponseShortTTL)
Expand Down
38 changes: 38 additions & 0 deletions pkg/server/plugin/keymanager/hashicorpvault/vault_fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,44 @@ var (
}
}`

testAppRoleAuthResponse = `{
"auth": {
"renewable": true,
"lease_duration": 1200,
"metadata": null,
"token_policies": [
"default"
],
"accessor": "fd6c9a00-d2dc-3b11-0be5-af7ae0e1d374",
"client_token": "5b1a0318-679c-9c45-e5c6-d1b9a9035d49"
},
"warnings": null,
"wrap_info": null,
"data": null,
"lease_duration": 0,
"renewable": false,
"lease_id": ""
}`

testAppRoleAuthResponseNotRenewable = `{
"auth": {
"renewable": false,
"lease_duration": 3600,
"metadata": null,
"token_policies": [
"default"
],
"accessor": "fd6c9a00-d2dc-3b11-0be5-af7ae0e1d374",
"client_token": "5b1a0318-679c-9c45-e5c6-d1b9a9035d49"
},
"warnings": null,
"wrap_info": null,
"data": null,
"lease_duration": 0,
"renewable": false,
"lease_id": ""
}`

testRenewResponse = `{
"auth": {
"client_token": "test-client-token",
Expand Down

0 comments on commit f5f1c14

Please sign in to comment.