blob: 31cb30eb8c8f76ceefe0710662ead59c1325a856 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kubeauth
import (
"fmt"
"reflect"
"testing"
)
import (
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
meshconfig "istio.io/api/mesh/v1alpha1"
k8sauth "k8s.io/api/authentication/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
ktesting "k8s.io/client-go/testing"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/cluster"
"github.com/apache/dubbo-go-pixiu/pkg/jwt"
"github.com/apache/dubbo-go-pixiu/pkg/security"
"github.com/apache/dubbo-go-pixiu/security/pkg/server/ca/authenticate"
)
type mockMeshConfigHolder struct {
trustDomain string
}
func (mh mockMeshConfigHolder) Mesh() *meshconfig.MeshConfig {
return &meshconfig.MeshConfig{
TrustDomain: mh.trustDomain,
}
}
func TestNewKubeJWTAuthenticator(t *testing.T) {
meshHolder := mockMeshConfigHolder{"testdomain.com"}
jwtPolicy := jwt.PolicyThirdParty
authenticator := NewKubeJWTAuthenticator(meshHolder, nil, "kubernetes", nil, jwtPolicy)
expectedAuthenticator := &KubeJWTAuthenticator{
meshHolder: meshHolder,
jwtPolicy: jwtPolicy,
clusterID: "kubernetes",
}
if !reflect.DeepEqual(authenticator, expectedAuthenticator) {
t.Errorf("Unexpected authentication result: want %v but got %v",
expectedAuthenticator, authenticator)
}
}
func TestAuthenticate(t *testing.T) {
primaryCluster := "Kubernetes"
remoteCluster := cluster.ID("remote")
invlidToken := "invalid-token"
meshHolder := mockMeshConfigHolder{"example.com"}
testCases := map[string]struct {
remoteCluster bool
metadata metadata.MD
token string
jwtPolicy string
expectedID string
expectedErrMsg string
}{
"No bearer token": {
metadata: metadata.MD{
"clusterid": []string{primaryCluster},
"authorization": []string{
"Basic callername",
},
},
expectedErrMsg: "target JWT extraction error: no bearer token exists in HTTP authorization header",
},
"token not authenticated": {
token: invlidToken,
metadata: metadata.MD{
"clusterid": []string{primaryCluster},
"authorization": []string{
"Basic callername",
},
},
jwtPolicy: jwt.PolicyFirstParty,
expectedErrMsg: `failed to validate the JWT from cluster "Kubernetes": the token is not authenticated`,
},
"token authenticated": {
token: "bearer-token",
metadata: metadata.MD{
"clusterid": []string{primaryCluster},
"authorization": []string{
"Basic callername",
},
},
jwtPolicy: jwt.PolicyFirstParty,
expectedID: fmt.Sprintf(authenticate.IdentityTemplate, "example.com", "default", "example-pod-sa"),
expectedErrMsg: "",
},
"not found remote cluster results in error": {
remoteCluster: false,
token: "bearer-token",
metadata: metadata.MD{
"clusterid": []string{"non-exist"},
"authorization": []string{
"Basic callername",
},
},
jwtPolicy: jwt.PolicyFirstParty,
expectedErrMsg: "could not get cluster non-exist's kube client",
},
}
for id, tc := range testCases {
t.Run(id, func(t *testing.T) {
ctx := context.Background()
if tc.metadata != nil {
if tc.token != "" {
token := security.BearerTokenPrefix + tc.token
tc.metadata.Append("authorization", token)
}
ctx = metadata.NewIncomingContext(ctx, tc.metadata)
}
tokenReview := &k8sauth.TokenReview{
Spec: k8sauth.TokenReviewSpec{
Token: tc.token,
},
}
if tc.jwtPolicy == jwt.PolicyThirdParty {
tokenReview.Spec.Audiences = security.TokenAudiences
}
tokenReview.Status.Audiences = []string{}
if tc.token != invlidToken {
tokenReview.Status.Authenticated = true
}
tokenReview.Status.User = k8sauth.UserInfo{
Username: "system:serviceaccount:default:example-pod-sa",
Groups: []string{"system:serviceaccounts"},
}
client := fake.NewSimpleClientset()
if !tc.remoteCluster {
client.PrependReactor("create", "tokenreviews", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, tokenReview, nil
})
}
remoteKubeClientGetter := func(clusterID cluster.ID) kubernetes.Interface {
if clusterID == remoteCluster {
client := fake.NewSimpleClientset()
if tc.remoteCluster {
client.PrependReactor("create", "tokenreviews", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, tokenReview, nil
})
}
}
return nil
}
authenticator := NewKubeJWTAuthenticator(meshHolder, client, "Kubernetes", remoteKubeClientGetter, tc.jwtPolicy)
actualCaller, err := authenticator.Authenticate(ctx)
if len(tc.expectedErrMsg) > 0 {
if err == nil {
t.Errorf("Case %s: Succeeded. Error expected: %v", id, err)
} else if err.Error() != tc.expectedErrMsg {
t.Errorf("Case %s: Incorrect error message: \n%s\nVS\n%s",
id, err.Error(), tc.expectedErrMsg)
}
return
} else if err != nil {
t.Errorf("Case %s: Unexpected Error: %v", id, err)
return
}
expectedCaller := &security.Caller{
AuthSource: security.AuthSourceIDToken,
Identities: []string{tc.expectedID},
}
if !reflect.DeepEqual(actualCaller, expectedCaller) {
t.Errorf("Case %q: Unexpected token: want %v but got %v", id, expectedCaller, actualCaller)
}
})
}
}
func TestIsAllowedKubernetesAudience(t *testing.T) {
tests := []struct {
in string
want bool
}{
{"kubernetes.default.svc", true},
{"kubernetes.default.svc.cluster.local", true},
{"https://kubernetes.default.svc", true},
{"https://kubernetes.default.svc.cluster.local", true},
{"foo.default.svc", false},
{"foo.default.svc:80", false},
{"https://foo.default.svc:80", false},
}
for _, tt := range tests {
t.Run(tt.in, func(t *testing.T) {
if got := isAllowedKubernetesAudience(tt.in); got != tt.want {
t.Errorf("isAllowedKubernetesAudience() = %v, want %v", got, tt.want)
}
})
}
}