Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
* `k8s.io/api` v0.31.0 -> v0.31.2
* `k8s.io/apimachinery` v0.31.0 -> v0.31.2

### Improvements

* Allow using `bound_service_account_namespace_selector` with `disable_local_ca_jwt`. [GH-271](https://github.com/hashicorp/vault-plugin-auth-kubernetes/pull/271)

## 0.20.0 (Sept 4, 2024)

### Build:
Expand Down
27 changes: 27 additions & 0 deletions integrationtest/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package integrationtest

const (
testCACert = `
-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIX6+U83J51BwwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDEyMTgxMjE1MjZaFw0zNDEyMTYxMjIwMjZaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDM2ovtucwJL4w4eiPRkTDpkmdOQs6H7DgSf6bsANc5Xgjvfe649aPMn94O
6hWpaqI3yaZMnnin6jJ3W2YWJqGHyvGIFn52iWoM8pB1fpTlERoSPEZDPQV5by8Y
MMa5kQKFkkXOkD9DmYYXQzMm+RzeJsd/L+m8FnxgcaYvqQzjKEuNR8Rn7PjYP1sB
ek4cIcQBd1c9CSwhxszKnB9QKcsVw0/1ClLHHT/kvNlZ1IYWPRhiKbBYb3cshH6g
KmFrO95V70uxBftO2g6eBXYOtx/z9Az8Mf68PU9g0H3uT7fYguMoYeo6bsz5KgYW
xKwdY09eRR5kZmfdzlNIQ3afj+HBAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRxqA2CZFgT6+9wrrdorwruAbEJQDAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQB/qVZg2f+q
1G4zcM51wJV29Iy1fI5TkI3WtsKM/bigrFFLMc93unRgoAHF7SyWkbQjHVRTN7+3
/nsUPNNOd055kP40/BWzqoFLVZMu/swGWiLKgHLTOX2+PDl/0qO/eXyw6ZQe4Ebi
Ep1u0rj1IJRHbT6LMNlXiVndZW8Ev1njJvn9CM5/UZDpGVl5CuVTIbtgSZ+gf2sA
v9t2ll2WlXY063oQ0uqGIYRmBuqTPCYcKvt8vMnEB2u5V4djJrIs77RobVR/4wEP
C820u4GAErIBaOvg4HZc+5cBKievDMFhsap5KEWHOeH2bgZyyU5ofm5MehTQMCsh
RIeNWngqwjWb
-----END CERTIFICATE-----`
)
48 changes: 48 additions & 0 deletions integrationtest/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,54 @@ func TestFailWithMismatchNamespaceLabels(t *testing.T) {
}
}

func TestSuccessWithoutTokenReviewerJwtAndDisabledLocalCAJwtAndNamespaceLabels(t *testing.T) {
client := setupKubernetesAuth(t, map[string]interface{}{
"kubernetes_host": "https://kubernetes.default.svc.cluster.local",
"kubernetes_ca_cert": testCACert,
"disable_local_ca_jwt": "true",
})
roleConfigOverride := map[string]interface{}{
"bound_service_account_names": "*",
"bound_service_account_namespace_selector": matchLabelsKeyValue,
}

setupKubernetesAuthRole(t, client, "vault", roleConfigOverride)

_, err := client.Logical().Write("auth/kubernetes/login", map[string]interface{}{
"role": "test-role",
"jwt": createToken(t, "vault", nil),
})
if err != nil {
t.Fatalf("Expected successful login but got: %v", err)
}
}

func TestFailWithoutTokenReviewerJwtAndDisabledLocalCAJwtAndMismatchNamespaceLabels(t *testing.T) {
client := setupKubernetesAuth(t, map[string]interface{}{
"kubernetes_host": "https://kubernetes.default.svc.cluster.local",
"kubernetes_ca_cert": testCACert,
"disable_local_ca_jwt": "true",
})
roleConfigOverride := map[string]interface{}{
"bound_service_account_names": "*",
"bound_service_account_namespace_selector": mismatchLabelsKeyValue,
}

setupKubernetesAuthRole(t, client, "vault", roleConfigOverride)

_, err := client.Logical().Write("auth/kubernetes/login", map[string]interface{}{
"role": "test-role",
"jwt": createToken(t, "vault", nil),
})
respErr, ok := err.(*api.ResponseError)
if !ok {
t.Fatalf("Expected api.ResponseError but was: %T", err)
}
if respErr.StatusCode != http.StatusForbidden {
t.Fatalf("Expected 403 but was %d: %s", respErr.StatusCode, respErr.Error())
}
}

func TestFailWithBadTokenReviewerJwt(t *testing.T) {
client := setupKubernetesAuth(t, map[string]interface{}{
"kubernetes_host": "https://kubernetes.default.svc.cluster.local",
Expand Down
20 changes: 12 additions & 8 deletions namespace_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

// namespaceValidator defines a namespace validator interface
type namespaceValidator interface {
validateLabels(context.Context, *http.Client, string, string) (bool, error)
validateLabels(context.Context, *http.Client, string, string, string) (bool, error)
}

type namespaceValidatorFactory func(*kubeConfig) namespaceValidator
Expand All @@ -38,7 +38,7 @@ func newNsValidatorWrapper(config *kubeConfig) namespaceValidator {
}
}

func (v *namespaceValidatorWrapper) validateLabels(ctx context.Context, client *http.Client, namespace string, namespaceSelector string) (bool, error) {
func (v *namespaceValidatorWrapper) validateLabels(ctx context.Context, client *http.Client, namespace string, namespaceSelector string, jwtStr string) (bool, error) {
labelSelector, err := makeNsLabelSelector(namespaceSelector)
if err != nil {
return false, err
Expand All @@ -49,26 +49,30 @@ func (v *namespaceValidatorWrapper) validateLabels(ctx context.Context, client *
return false, err
}

nsLabels, err := v.getNamespaceLabels(ctx, client, namespace)
nsLabels, err := v.getNamespaceLabels(ctx, client, namespace, jwtStr)
if err != nil {
return false, err
}

return selector.Matches(labels.Set(nsLabels)), nil
}

func (v *namespaceValidatorWrapper) getNamespaceLabels(ctx context.Context, client *http.Client, namespace string) (map[string]string, error) {
func (v *namespaceValidatorWrapper) getNamespaceLabels(ctx context.Context, client *http.Client, namespace string, jwtStr string) (map[string]string, error) {
url := fmt.Sprintf("%s/api/v1/namespaces/%s", strings.TrimSuffix(v.config.Host, "/"), namespace)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}

// Use the configured TokenReviewer JWT as the bearer
if v.config.TokenReviewerJWT == "" {
// Use client JWT as bearer if local CA disabled
// Otherwise use TokenReviewer JWT
bearer := v.config.TokenReviewerJWT
if v.config.DisableLocalCAJwt {
bearer = jwtStr
} else if v.config.TokenReviewerJWT == "" {
return nil, errors.New("namespace lookup failed: TokenReviewer JWT needs to be configured to use namespace selectors")
}
setRequestHeader(req, fmt.Sprintf("Bearer %s", v.config.TokenReviewerJWT))
setRequestHeader(req, fmt.Sprintf("Bearer %s", bearer))

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -137,7 +141,7 @@ func mockNamespaceValidateFactory(labels map[string]string) namespaceValidatorFa
}
}

func (v *mockNamespaceValidator) validateLabels(ctx context.Context, client *http.Client, namespace string, namespaceSelector string) (bool, error) {
func (v *mockNamespaceValidator) validateLabels(ctx context.Context, client *http.Client, namespace string, namespaceSelector string, jwtStr string) (bool, error) {
labelSelector, err := makeNsLabelSelector(namespaceSelector)
if err != nil {
return false, err
Expand Down
2 changes: 1 addition & 1 deletion path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ func (b *kubeAuthBackend) parseAndValidateJWT(ctx context.Context, client *http.
// verify the namespace selector matches the namespace
if !allowed && role.ServiceAccountNamespaceSelector != "" {
allowed, err = b.namespaceValidatorFactory(config).validateLabels(ctx,
client, sa.namespace(), role.ServiceAccountNamespaceSelector)
client, sa.namespace(), role.ServiceAccountNamespaceSelector, jwtStr)
}

if !allowed {
Expand Down