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

Allow custom audience for kubernetes in-cluster joining #49528

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 7 additions & 6 deletions lib/auth/join_kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

type k8sTokenReviewValidator interface {
Validate(context.Context, string) (*kubernetestoken.ValidationResult, error)
Validate(ctx context.Context, token, clusterName string) (*kubernetestoken.ValidationResult, error)
}

type k8sJWKSValidator func(now time.Time, jwksData []byte, clusterName string, token string) (*kubernetestoken.ValidationResult, error)
Expand All @@ -52,14 +52,15 @@ func (a *Server) checkKubernetesJoinRequest(ctx context.Context, req *types.Regi
)
}

clusterName, err := a.GetDomainName()
if err != nil {
return nil, trace.Wrap(err)
}

// Switch to join method subtype token validation.
var result *kubernetestoken.ValidationResult
switch token.Spec.Kubernetes.Type {
case types.KubernetesJoinTypeStaticJWKS:
clusterName, err := a.GetDomainName()
if err != nil {
return nil, trace.Wrap(err)
}
result, err = a.k8sJWKSValidator(
a.clock.Now(),
[]byte(token.Spec.Kubernetes.StaticJWKS.JWKS),
Expand All @@ -70,7 +71,7 @@ func (a *Server) checkKubernetesJoinRequest(ctx context.Context, req *types.Regi
return nil, trace.WrapWithMessage(err, "reviewing kubernetes token with static_jwks")
}
case types.KubernetesJoinTypeInCluster, types.KubernetesJoinTypeUnspecified:
result, err = a.k8sTokenReviewValidator.Validate(ctx, req.IDToken)
result, err = a.k8sTokenReviewValidator.Validate(ctx, req.IDToken, clusterName)
if err != nil {
return nil, trace.WrapWithMessage(err, "reviewing kubernetes token with in_cluster")
}
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/join_kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type mockK8STokenReviewValidator struct {
tokens map[string]*kubernetestoken.ValidationResult
}

func (m *mockK8STokenReviewValidator) Validate(_ context.Context, token string) (*kubernetestoken.ValidationResult, error) {
func (m *mockK8STokenReviewValidator) Validate(_ context.Context, token, _ string) (*kubernetestoken.ValidationResult, error) {
result, ok := m.tokens[token]
if !ok {
return nil, errMockInvalidToken
Expand Down
9 changes: 8 additions & 1 deletion lib/kubernetestoken/token_validator.go
hugoShaka marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const (
// Kubernetes should support bound tokens on 1.20 and 1.21,
// but we can have an apiserver running 1.21 and kubelets running 1.19.
kubernetesBoundTokenSupportVersion = "1.22.0"
// kubernetesAudience is the Kubernetes default audience put on SA tokens if we don't specify one.
kubernetesAudience = "https://kubernetes.default.svc"
)

type ValidationResult struct {
Expand Down Expand Up @@ -110,7 +112,7 @@ func (v *TokenReviewValidator) getClient() (kubernetes.Interface, error) {
}

// Validate uses the Kubernetes TokenReview API to validate a token and return its UserInfo
func (v *TokenReviewValidator) Validate(ctx context.Context, token string) (*ValidationResult, error) {
func (v *TokenReviewValidator) Validate(ctx context.Context, token, clusterName string) (*ValidationResult, error) {
client, err := v.getClient()
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -119,6 +121,11 @@ func (v *TokenReviewValidator) Validate(ctx context.Context, token string) (*Val
review := &v1.TokenReview{
Spec: v1.TokenReviewSpec{
Token: token,
// In-cluster used to only allow tokens with the kubernetes audience
// But people kept confusing it with JWKS and set the cluster name
// as the audience/. To avoid his common footgun we now allow tokens
// whose audience is the teleport cluster name.
Audiences: []string{kubernetesAudience, clusterName},
},
}
options := metav1.CreateOptions{}
Expand Down
5 changes: 4 additions & 1 deletion lib/kubernetestoken/token_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import (
"github.com/gravitational/teleport/lib/cryptosuites"
)

const testClusterName = "teleport.example.com"

var userGroups = []string{"system:serviceaccounts", "system:serviceaccounts:namespace", "system:authenticated"}

var boundTokenKubernetesVersion = version.Info{
Expand All @@ -65,6 +67,7 @@ func tokenReviewMock(t *testing.T, reviewResult *v1.TokenReview) func(ctest.Acti
require.True(t, ok)

require.Equal(t, reviewResult.Spec.Token, reviewRequest.Spec.Token)
require.ElementsMatch(t, reviewRequest.Spec.Audiences, []string{kubernetesAudience, testClusterName})
return true, reviewResult, nil
}
}
Expand Down Expand Up @@ -238,7 +241,7 @@ func TestIDTokenValidator_Validate(t *testing.T) {
v := TokenReviewValidator{
client: client,
}
result, err := v.Validate(context.Background(), tt.token)
result, err := v.Validate(context.Background(), tt.token, testClusterName)
if tt.expectedError != nil {
require.ErrorIs(t, err, tt.expectedError)
return
Expand Down
Loading