Skip to content

Commit f5f1c14

Browse files
Support AppRole authentication (spiffe#5058)
1 parent 89c8b77 commit f5f1c14

File tree

3 files changed

+137
-4
lines changed

3 files changed

+137
-4
lines changed

pkg/server/plugin/keymanager/hashicorpvault/hashicorp_vault.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,35 @@ type pluginHooks struct {
5151
type Config struct {
5252
// A URL of Vault server. (e.g., https://vault.example.com:8443/)
5353
VaultAddr string `hcl:"vault_addr" json:"vault_addr"`
54+
// Name of the Vault namespace
55+
Namespace string `hcl:"namespace" json:"namespace"`
5456

5557
// Configuration for the Token authentication method
5658
TokenAuth *TokenAuthConfig `hcl:"token_auth" json:"token_auth,omitempty"`
57-
58-
// Name of the Vault namespace
59-
Namespace string `hcl:"namespace" json:"namespace"`
59+
// Configuration for the AppRole authentication method
60+
AppRoleAuth *AppRoleAuthConfig `hcl:"approle_auth" json:"approle_auth,omitempty"`
6061

6162
// TODO: Support other auth methods
6263
// TODO: Support client certificate and key
6364
}
6465

66+
// TokenAuthConfig represents parameters for token auth method
6567
type TokenAuthConfig struct {
6668
// Token string to set into "X-Vault-Token" header
6769
Token string `hcl:"token" json:"token"`
6870
}
6971

72+
// AppRoleAuthConfig represents parameters for AppRole auth method.
73+
type AppRoleAuthConfig struct {
74+
// Name of the mount point where AppRole auth method is mounted. (e.g., /auth/<mount_point>/login)
75+
// If the value is empty, use default mount point (/auth/approle)
76+
AppRoleMountPoint string `hcl:"approle_auth_mount_point" json:"approle_auth_mount_point"`
77+
// An identifier that selects the AppRole
78+
RoleID string `hcl:"approle_id" json:"approle_id"`
79+
// A credential that is required for login.
80+
SecretID string `hcl:"approle_secret_id" json:"approle_secret_id"`
81+
}
82+
7083
// Plugin is the main representation of this keymanager plugin
7184
type Plugin struct {
7285
keymanagerv1.UnsafeKeyManagerServer
@@ -138,11 +151,25 @@ func parseAuthMethod(config *Config) (AuthMethod, error) {
138151
authMethod = TOKEN
139152
}
140153

154+
if config.AppRoleAuth != nil {
155+
if err := checkForAuthMethodConfigured(authMethod); err != nil {
156+
return 0, err
157+
}
158+
authMethod = APPROLE
159+
}
160+
141161
if authMethod != 0 {
142162
return authMethod, nil
143163
}
144164

145-
return 0, status.Error(codes.InvalidArgument, "must be configured one of these authentication method 'Token'")
165+
return 0, status.Error(codes.InvalidArgument, "one of the available authentication methods must be configured: 'Token, AppRole'")
166+
}
167+
168+
func checkForAuthMethodConfigured(authMethod AuthMethod) error {
169+
if authMethod != 0 {
170+
return status.Error(codes.InvalidArgument, "only one authentication method can be configured")
171+
}
172+
return nil
146173
}
147174

148175
func (p *Plugin) genClientParams(method AuthMethod, config *Config) (*ClientParams, error) {
@@ -154,6 +181,10 @@ func (p *Plugin) genClientParams(method AuthMethod, config *Config) (*ClientPara
154181
switch method {
155182
case TOKEN:
156183
cp.Token = p.getEnvOrDefault(envVaultToken, config.TokenAuth.Token)
184+
case APPROLE:
185+
cp.AppRoleAuthMountPoint = config.AppRoleAuth.AppRoleMountPoint
186+
cp.AppRoleID = p.getEnvOrDefault(envVaultAppRoleID, config.AppRoleAuth.RoleID)
187+
cp.AppRoleSecretID = p.getEnvOrDefault(envVaultAppRoleSecretID, config.AppRoleAuth.SecretID)
157188
}
158189

159190
return cp, nil

pkg/server/plugin/keymanager/hashicorpvault/vault_client_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,70 @@ func TestNewAuthenticatedClientTokenAuth(t *testing.T) {
143143
}
144144
}
145145

146+
func TestNewAuthenticatedClientAppRoleAuth(t *testing.T) {
147+
fakeVaultServer := newFakeVaultServer()
148+
fakeVaultServer.AppRoleAuthResponseCode = 200
149+
for _, tt := range []struct {
150+
name string
151+
response []byte
152+
renew bool
153+
namespace string
154+
}{
155+
{
156+
name: "AppRole Authentication success / Token is renewable",
157+
response: []byte(testAppRoleAuthResponse),
158+
renew: true,
159+
},
160+
{
161+
name: "AppRole Authentication success / Token is not renewable",
162+
response: []byte(testAppRoleAuthResponseNotRenewable),
163+
},
164+
{
165+
name: "AppRole Authentication success / Token is renewable / Namespace is given",
166+
response: []byte(testAppRoleAuthResponse),
167+
renew: true,
168+
namespace: "test-ns",
169+
},
170+
} {
171+
tt := tt
172+
t.Run(tt.name, func(t *testing.T) {
173+
fakeVaultServer.AppRoleAuthResponse = tt.response
174+
175+
s, addr, err := fakeVaultServer.NewTLSServer()
176+
require.NoError(t, err)
177+
178+
s.Start()
179+
defer s.Close()
180+
181+
cp := &ClientParams{
182+
VaultAddr: fmt.Sprintf("https://%v/", addr),
183+
Namespace: tt.namespace,
184+
CACertPath: testRootCert,
185+
AppRoleID: "test-approle-id",
186+
AppRoleSecretID: "test-approle-secret-id",
187+
}
188+
cc, err := NewClientConfig(cp, hclog.Default())
189+
require.NoError(t, err)
190+
191+
renewCh := make(chan struct{})
192+
client, err := cc.NewAuthenticatedClient(APPROLE, renewCh)
193+
require.NoError(t, err)
194+
195+
select {
196+
case <-renewCh:
197+
require.Equal(t, false, tt.renew)
198+
default:
199+
require.Equal(t, true, tt.renew)
200+
}
201+
202+
if cp.Namespace != "" {
203+
headers := client.vaultClient.Headers()
204+
require.Equal(t, cp.Namespace, headers.Get(consts.NamespaceHeaderName))
205+
}
206+
})
207+
}
208+
}
209+
146210
func TestRenewTokenFailed(t *testing.T) {
147211
fakeVaultServer := newFakeVaultServer()
148212
fakeVaultServer.LookupSelfResponse = []byte(testLookupSelfResponseShortTTL)

pkg/server/plugin/keymanager/hashicorpvault/vault_fake_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,44 @@ var (
3030
}
3131
}`
3232

33+
testAppRoleAuthResponse = `{
34+
"auth": {
35+
"renewable": true,
36+
"lease_duration": 1200,
37+
"metadata": null,
38+
"token_policies": [
39+
"default"
40+
],
41+
"accessor": "fd6c9a00-d2dc-3b11-0be5-af7ae0e1d374",
42+
"client_token": "5b1a0318-679c-9c45-e5c6-d1b9a9035d49"
43+
},
44+
"warnings": null,
45+
"wrap_info": null,
46+
"data": null,
47+
"lease_duration": 0,
48+
"renewable": false,
49+
"lease_id": ""
50+
}`
51+
52+
testAppRoleAuthResponseNotRenewable = `{
53+
"auth": {
54+
"renewable": false,
55+
"lease_duration": 3600,
56+
"metadata": null,
57+
"token_policies": [
58+
"default"
59+
],
60+
"accessor": "fd6c9a00-d2dc-3b11-0be5-af7ae0e1d374",
61+
"client_token": "5b1a0318-679c-9c45-e5c6-d1b9a9035d49"
62+
},
63+
"warnings": null,
64+
"wrap_info": null,
65+
"data": null,
66+
"lease_duration": 0,
67+
"renewable": false,
68+
"lease_id": ""
69+
}`
70+
3371
testRenewResponse = `{
3472
"auth": {
3573
"client_token": "test-client-token",

0 commit comments

Comments
 (0)