Skip to content
This repository was archived by the owner on May 17, 2024. It is now read-only.

Commit 6e22b04

Browse files
committed
Adds passthrough e2e tests
Signed-off-by: JoshVanL <[email protected]>
1 parent 0f0f33d commit 6e22b04

File tree

9 files changed

+214
-37
lines changed

9 files changed

+214
-37
lines changed

cmd/app/options/kube_oidc_proxy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ type KubeOIDCProxyOptions struct {
1818
func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
1919
fs.StringSliceVar(&t.Audiences, "token-passthrough-audiences", t.Audiences, ""+
2020
"(Alpha) List of the identifiers that the resource server presented with the token "+
21-
"identifies as. The resoure server will verify that non OIDC tokens are intended "+
21+
"identifies as. The resource server will verify that non OIDC tokens are intended "+
2222
"for at least one of the audiences in this list. If no audiences are "+
2323
"provided, the audience will default to the audience of the Kubernetes "+
24-
"apiserver.")
24+
"apiserver. Only used when --token-passthrough is also enabled.")
2525

2626
fs.BoolVar(&t.Enabled, "token-passthrough", t.Enabled, ""+
2727
"(Alpha) Requests with Bearer tokens that fail OIDC validation are tried against "+

test/e2e/framework/helper/pod.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func (h *Helper) WaitForPodReady(namespace, name string, timeout time.Duration) error {
1414
log.Infof("Waiting for Pod to become ready %s/%s", namespace, name)
1515

16-
err := wait.PollImmediate(time.Second*2, timeout, func() (bool, error) {
16+
err := wait.PollImmediate(time.Second*5, timeout, func() (bool, error) {
1717
pod, err := h.KubeClient.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
1818
if err != nil {
1919
return false, err
@@ -50,7 +50,8 @@ func (h *Helper) WaitForPodReady(namespace, name string, timeout time.Duration)
5050

5151
func (h *Helper) WaitForPodDeletion(namespace, name string, timeout time.Duration) error {
5252
log.Infof("Waiting for Pod to be deleted %s/%s", namespace, name)
53-
err := wait.PollImmediate(time.Second*2, timeout, func() (bool, error) {
53+
54+
err := wait.PollImmediate(time.Second*5, timeout, func() (bool, error) {
5455
pod, err := h.KubeClient.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
5556
if k8sErrors.IsNotFound(err) {
5657
return true, nil
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package helper
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"net/http"
7+
)
8+
9+
type Requester struct {
10+
transport http.RoundTripper
11+
token string
12+
client *http.Client
13+
}
14+
15+
func (h *Helper) NewRequester(transport http.RoundTripper, token string) *Requester {
16+
r := &Requester{
17+
token: token,
18+
}
19+
20+
r.client = http.DefaultClient
21+
r.client.Transport = r
22+
23+
return r
24+
}
25+
26+
func (r *Requester) RoundTrip(req *http.Request) (*http.Response, error) {
27+
req.Header.Add("Authorization", fmt.Sprintf("bearer %s", r.token))
28+
return r.transport.RoundTrip(req)
29+
}
30+
31+
func (r *Requester) Get(target string) ([]byte, int, error) {
32+
resp, err := r.client.Get(target)
33+
if err != nil {
34+
return nil, 0, err
35+
}
36+
37+
body, err := ioutil.ReadAll(resp.Body)
38+
if err != nil {
39+
return nil, 0, err
40+
}
41+
42+
return body, resp.StatusCode, nil
43+
}

test/e2e/framework/helper/secrets.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package helper
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
)
7+
8+
func (h *Helper) GetServiceAccountSecret(ns, name string) (*corev1.Secret, error) {
9+
sa, err := h.KubeClient.CoreV1().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
10+
if err != nil {
11+
return nil, err
12+
}
13+
sec, err := h.KubeClient.CoreV1().Secrets(ns).Get(sa.Secrets[0].Name, metav1.GetOptions{})
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
return sec, nil
19+
}

test/e2e/framework/helper/token.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
func (h *Helper) NewValidRestConfig(issuerBundle, proxyBundle *util.KeyBundle,
1717
issuerURL, proxyURL, clientID string) (*rest.Config, error) {
1818

19-
// valid token with exp in 10 minutes
19+
// Valid token with exp in 10 minutes
2020
tokenPayload := h.NewTokenPayload(issuerURL, clientID,
2121
time.Now().Add(time.Minute*10))
2222
signedToken, err := h.SignToken(issuerBundle, tokenPayload)
@@ -64,7 +64,7 @@ func (h *Helper) SignToken(issuerBundle *util.KeyBundle, tokenPayload []byte) (s
6464
}
6565

6666
func (h *Helper) NewTokenPayload(issuerURL, clientID string, exp time.Time) []byte {
67-
// valid for 10 mins
67+
// Valid for 10 mins
6868
return []byte(fmt.Sprintf(`{
6969
"iss":"%s",
7070
"aud":["%s","aud-2"],

test/e2e/suite/cases/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cases
22

33
import (
4+
_ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/impersonation"
45
_ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/rbac"
56
_ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/token"
67
)

test/e2e/suite/cases/impersonation/impersonation.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,14 @@ var _ = framework.CasesDescribe("Impersonation", func() {
6464
})
6565

6666
It("should not error at proxy when impersonation is disabled impersonation is attempted on a request", func() {
67+
By("Enabling the disabling of impersonation")
6768
f.DeployProxyWith("--disable-impersonation")
6869

69-
// Should return a normal RBAC forbidden from Kubernetes. If is not a
70-
// Kubernetes error then it came from the kube-oidc-proxy so error
71-
client := f.NewProxyClient()
72-
_, err := client.CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
73-
if !k8sErrors.IsForbidden(err) {
70+
// Should return an Unauthorized response from Kubernetes as it does not
71+
// trust the OIDC token we have presented however it has been authenticated
72+
// by kube-oidc-proxy.
73+
_, err := f.NewProxyClient().CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
74+
if !k8sErrors.IsUnauthorized(err) {
7475
Expect(err).NotTo(HaveOccurred())
7576
}
7677
})
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package impersonation
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net/http"
7+
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
11+
corev1 "k8s.io/api/core/v1"
12+
rbacv1 "k8s.io/api/rbac/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/client-go/kubernetes"
15+
16+
"github.com/jetstack/kube-oidc-proxy/test/e2e/framework"
17+
)
18+
19+
var _ = framework.CasesDescribe("Passthrough", func() {
20+
f := framework.NewDefaultFramework("passthrough")
21+
22+
var saToken string
23+
24+
JustBeforeEach(func() {
25+
By("Creating List Pods Role")
26+
_, err := f.Helper().KubeClient.RbacV1().Roles(f.Namespace.Name).Create(
27+
&rbacv1.Role{
28+
ObjectMeta: metav1.ObjectMeta{
29+
Name: "e2e-impersonation-pods-list",
30+
},
31+
Rules: []rbacv1.PolicyRule{
32+
{
33+
APIGroups: []string{""},
34+
Resources: []string{"pods"},
35+
Verbs: []string{"get", "list"},
36+
},
37+
},
38+
})
39+
Expect(err).NotTo(HaveOccurred())
40+
41+
// Create bindings for both the OIDC user and default ServiceAccount
42+
By("Creating List Pods RoleBinding")
43+
_, err = f.Helper().KubeClient.RbacV1().RoleBindings(f.Namespace.Name).Create(
44+
&rbacv1.RoleBinding{
45+
ObjectMeta: metav1.ObjectMeta{
46+
Name: "e2e-impersonation-pods-list",
47+
},
48+
Subjects: []rbacv1.Subject{
49+
{Name: "[email protected]", Kind: "User"},
50+
{Name: "default", Kind: "ServiceAccount"},
51+
},
52+
RoleRef: rbacv1.RoleRef{
53+
Name: "e2e-impersonation-pods-list", Kind: "Role"},
54+
})
55+
Expect(err).NotTo(HaveOccurred())
56+
57+
By("Geting the token for the default ServiceAccount")
58+
sec, err := f.Helper().GetServiceAccountSecret(f.Namespace.Name, "default")
59+
Expect(err).NotTo(HaveOccurred())
60+
61+
saTokenBytes, ok := sec.Data[corev1.ServiceAccountTokenKey]
62+
if !ok {
63+
err = fmt.Errorf("expected token to be present in secret %s/%s (%s): %+v",
64+
sec.Name, sec.Namespace, corev1.ServiceAccountTokenKey, sec.Data)
65+
Expect(err).NotTo(HaveOccurred())
66+
}
67+
68+
saToken = string(saTokenBytes)
69+
})
70+
71+
JustAfterEach(func() {
72+
By("Deleting List Pods Role")
73+
err := f.Helper().KubeClient.RbacV1().Roles(f.Namespace.Name).Delete(
74+
"e2e-impersonation-pods-list", nil)
75+
Expect(err).NotTo(HaveOccurred())
76+
77+
By("Creating List Pods RoleBinding")
78+
err = f.Helper().KubeClient.RbacV1().RoleBindings(f.Namespace.Name).Delete(
79+
"e2e-impersonation-pods-list", nil)
80+
Expect(err).NotTo(HaveOccurred())
81+
})
82+
83+
It("error when a valid OIDC token is used but return correct when passthrough is disabled", func() {
84+
By("A valid OIDC token should respond without error")
85+
proxyClient := f.NewProxyClient()
86+
_, err := proxyClient.CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
87+
Expect(err).NotTo(HaveOccurred())
88+
89+
By("Using a ServiceAccount token should error by the proxy")
90+
91+
// Create requester using the ServiceAccount token
92+
proxyConfig := f.NewProxyRestConfig()
93+
requester := f.Helper().NewRequester(proxyConfig.Transport, saToken)
94+
95+
// Send request with signed token to proxy
96+
target := fmt.Sprintf("%s/api/v1/namespaces/%s/pods",
97+
proxyConfig.Host, f.Namespace.Name)
98+
99+
body, statusCode, err := requester.Get(target)
100+
Expect(err).NotTo(HaveOccurred())
101+
102+
// Check body and status code the token was rejected
103+
if statusCode != http.StatusForbidden ||
104+
!bytes.Equal(body, []byte("Unauthorized")) {
105+
}
106+
Expect(fmt.Errorf("expected status code %d with body Unauthorized, got= %d %q",
107+
http.StatusForbidden, statusCode, body)).NotTo(HaveOccurred())
108+
})
109+
110+
It("should not error on a valid OIDC token nor a valid ServiceAccount token with passthrough enabled", func() {
111+
By("Enabling passthrough with Audience of the API Server")
112+
f.DeployProxyWith("--token-passthrough")
113+
114+
By("A valid OIDC token should respond without error")
115+
proxyClient := f.NewProxyClient()
116+
_, err := proxyClient.CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
117+
Expect(err).NotTo(HaveOccurred())
118+
119+
By("Using a ServiceAccount token should not error")
120+
121+
// Create kube client using ServiceAccount token
122+
proxyConfig := f.NewProxyRestConfig()
123+
proxyConfig.BearerToken = saToken
124+
kubeProxyClient, err := kubernetes.NewForConfig(proxyConfig)
125+
Expect(err).NotTo(HaveOccurred())
126+
127+
_, err = kubeProxyClient.CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
128+
Expect(err).NotTo(HaveOccurred())
129+
})
130+
})

test/e2e/suite/cases/token/token.go

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package token
33
import (
44
"bytes"
55
"fmt"
6-
"io/ioutil"
76
"net/http"
87
"time"
98

@@ -16,16 +15,6 @@ import (
1615
"github.com/jetstack/kube-oidc-proxy/test/e2e/framework"
1716
)
1817

19-
type wraperRT struct {
20-
transport http.RoundTripper
21-
token string
22-
}
23-
24-
func (w *wraperRT) RoundTrip(r *http.Request) (*http.Response, error) {
25-
r.Header.Add("Authorization", fmt.Sprintf("bearer %s", w.token))
26-
return w.transport.RoundTrip(r)
27-
}
28-
2918
var _ = framework.CasesDescribe("Token", func() {
3019
f := framework.NewDefaultFramework("token")
3120

@@ -51,7 +40,7 @@ var _ = framework.CasesDescribe("Token", func() {
5140
By("Valid token should return Kubernetes forbidden")
5241
client := f.NewProxyClient()
5342

54-
// if does not return with Kubernetes forbidden error then error
43+
// If does not return with Kubernetes forbidden error then error
5544
_, err := client.CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
5645
if !k8sErrors.IsForbidden(err) {
5746
Expect(err).NotTo(HaveOccurred())
@@ -60,31 +49,24 @@ var _ = framework.CasesDescribe("Token", func() {
6049
})
6150

6251
func expectProxyUnauthorized(f *framework.Framework, tokenPayload []byte) {
63-
// build client using given token payload
52+
// Build client using given token payload
6453
signedToken, err := f.Helper().SignToken(f.IssuerKeyBundle(), tokenPayload)
6554
Expect(err).NotTo(HaveOccurred())
6655

6756
proxyConfig := f.NewProxyRestConfig()
68-
client := http.DefaultClient
69-
client.Transport = &wraperRT{
70-
transport: proxyConfig.Transport,
71-
token: signedToken,
72-
}
57+
requester := f.Helper().NewRequester(proxyConfig.Transport, signedToken)
7358

74-
// send request with signed token to proxy
59+
// Send request with signed token to proxy
7560
target := fmt.Sprintf("%s/api/v1/namespaces/%s/pods",
7661
proxyConfig.Host, f.Namespace.Name)
7762

78-
resp, err := client.Get(target)
79-
Expect(err).NotTo(HaveOccurred())
80-
81-
body, err := ioutil.ReadAll(resp.Body)
63+
body, statusCode, err := requester.Get(target)
8264
Expect(err).NotTo(HaveOccurred())
8365

84-
// check body and status code the token was rejected
85-
if resp.StatusCode != http.StatusForbidden ||
66+
// Check body and status code the token was rejected
67+
if statusCode != http.StatusForbidden ||
8668
!bytes.Equal(body, []byte("Unauthorized")) {
8769
}
8870
Expect(fmt.Errorf("expected status code %d with body Unauthorized, got= %d %q",
89-
http.StatusForbidden, resp.StatusCode, body)).NotTo(HaveOccurred())
71+
http.StatusForbidden, statusCode, body)).NotTo(HaveOccurred())
9072
}

0 commit comments

Comments
 (0)