Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add provider ca auth support for kubernetes #16262

Merged
merged 2 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/16262.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ca: support Vault agent auto-auth config for Vault CA provider using Kubernetes authentication.
```
21 changes: 9 additions & 12 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"fmt"
"io"
"net/http"
"os"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -922,6 +921,14 @@ func vaultLogin(client *vaultapi.Client, authMethod *structs.VaultAuthMethod) (*
return resp, nil
}

// Note the authMethod's parameters (Params) is populated from a freeform map
// in the configuration where they could hardcode values to be passed directly
// to the `auth/*/login` endpoint. Each auth method's authentication code
// needs to handle two cases:
// - The legacy case (which should be deprecated) where the user has
// hardcoded login values directly (eg. a `jwt` string)
// - The case where they use the configuration option used in the
// vault agent's auth methods.
func configureVaultAuthMethod(authMethod *structs.VaultAuthMethod) (VaultAuthenticator, error) {
if authMethod.MountPath == "" {
authMethod.MountPath = authMethod.Type
Expand All @@ -938,17 +945,7 @@ func configureVaultAuthMethod(authMethod *structs.VaultAuthMethod) (VaultAuthent
case VaultAuthMethodTypeJWT:
return NewJwtAuthClient(authMethod)
case VaultAuthMethodTypeKubernetes:
// For the Kubernetes Auth method, we will try to read the JWT token
// from the default service account file location if jwt was not provided.
if jwt, ok := authMethod.Params["jwt"]; !ok || jwt == "" {
serviceAccountToken, err := os.ReadFile(defaultK8SServiceAccountTokenPath)
if err != nil {
return nil, err
}

authMethod.Params["jwt"] = string(serviceAccountToken)
}
return NewVaultAPIAuthClient(authMethod, loginPath), nil
return NewK8sAuthClient(authMethod)
// These auth methods require a username for the login API path.
case VaultAuthMethodTypeLDAP, VaultAuthMethodTypeUserpass, VaultAuthMethodTypeOkta, VaultAuthMethodTypeRadius:
// Get username from the params.
Expand Down
47 changes: 47 additions & 0 deletions agent/connect/ca/provider_vault_auth_k8s.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ca

import (
"fmt"
"os"
"strings"

"github.com/hashicorp/consul/agent/structs"
)

func NewK8sAuthClient(authMethod *structs.VaultAuthMethod) (*VaultAuthClient, error) {
params := authMethod.Params
role, ok := params["role"].(string)
if !ok || strings.TrimSpace(role) == "" {
return nil, fmt.Errorf("missing 'role' value")
}
// don't check for `token_path` as it is optional

authClient := NewVaultAPIAuthClient(authMethod, "")
// Note the `jwt` can be passed directly in the authMethod as a Param value
// is a freeform map in the config where they could hardcode it.
if legacyCheck(params, "jwt") {
return authClient, nil
}

authClient.LoginDataGen = K8sLoginDataGen
return authClient, nil
}

func K8sLoginDataGen(authMethod *structs.VaultAuthMethod) (map[string]any, error) {
params := authMethod.Params
role := params["role"].(string)

// read token from file on path
tokenPath, ok := params["token_path"].(string)
if !ok || strings.TrimSpace(tokenPath) == "" {
tokenPath = defaultK8SServiceAccountTokenPath
}
rawToken, err := os.ReadFile(tokenPath)
if err != nil {
return nil, err
}
return map[string]any{
"role": role,
"jwt": strings.TrimSpace(string(rawToken)),
}, nil
}
66 changes: 66 additions & 0 deletions agent/connect/ca/provider_vault_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,3 +502,69 @@ func TestVaultCAProvider_JwtAuthClient(t *testing.T) {
})
}
}

func TestVaultCAProvider_K8sAuthClient(t *testing.T) {
tokenF, err := os.CreateTemp("", "token-path")
require.NoError(t, err)
defer func() { os.Remove(tokenF.Name()) }()
_, err = tokenF.WriteString("test-token")
require.NoError(t, err)
err = tokenF.Close()
require.NoError(t, err)

cases := map[string]struct {
authMethod *structs.VaultAuthMethod
expData map[string]any
expErr error
}{
"base-case": {
authMethod: &structs.VaultAuthMethod{
Type: "kubernetes",
Params: map[string]any{
"role": "test-role",
"token_path": tokenF.Name(),
},
},
expData: map[string]any{
"role": "test-role",
"jwt": "test-token",
},
},
"legacy-case": {
authMethod: &structs.VaultAuthMethod{
Type: "kubernetes",
Params: map[string]any{
"role": "test-role",
"jwt": "test-token",
},
},
expData: map[string]any{
"role": "test-role",
"jwt": "test-token",
},
},
"no-role": {
authMethod: &structs.VaultAuthMethod{
Type: "kubernetes",
Params: map[string]any{},
},
expErr: fmt.Errorf("missing 'role' value"),
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
auth, err := NewK8sAuthClient(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)
}
})
}
}
2 changes: 1 addition & 1 deletion agent/connect/ca/provider_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestVaultCAProvider_configureVaultAuthMethod(t *testing.T) {
"gcp": {expLoginPath: "auth/gcp/login", params: map[string]interface{}{"type": "iam", "role": "test-role"}},
"jwt": {expLoginPath: "auth/jwt/login", params: map[string]any{"role": "test-role", "path": "test-path"}, hasLDG: true},
"kerberos": {expLoginPath: "auth/kerberos/login"},
"kubernetes": {expLoginPath: "auth/kubernetes/login", params: map[string]interface{}{"jwt": "fake"}},
eikenb marked this conversation as resolved.
Show resolved Hide resolved
"kubernetes": {expLoginPath: "auth/kubernetes/login", params: map[string]interface{}{"role": "test-role"}, hasLDG: true},
"ldap": {expLoginPath: "auth/ldap/login/foo", params: map[string]interface{}{"username": "foo"}},
"oci": {expLoginPath: "auth/oci/login/foo", params: map[string]interface{}{"role": "foo"}},
"okta": {expLoginPath: "auth/okta/login/foo", params: map[string]interface{}{"username": "foo"}},
Expand Down