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

Commit 9ff574e

Browse files
authored
Merge pull request #115 from JoshVanL/readiness-check
Makes readiness probe rely on proxy initialization on issuer
2 parents 20f9258 + 834e72b commit 9ff574e

File tree

10 files changed

+221
-91
lines changed

10 files changed

+221
-91
lines changed

cmd/app/options/kube_oidc_proxy.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type TokenPassthroughOptions struct {
1313
type KubeOIDCProxyOptions struct {
1414
DisableImpersonation bool
1515
TokenPassthrough TokenPassthroughOptions
16+
17+
ReadinessProbePort int
1618
}
1719

1820
func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
@@ -34,5 +36,8 @@ func (k *KubeOIDCProxyOptions) AddFlags(fs *pflag.FlagSet) {
3436
"(Alpha) Disable the impersonation of authenticated requests. All "+
3537
"authenticated requests will be forwarded as is.")
3638

39+
fs.IntVarP(&k.ReadinessProbePort, "readiness-probe-port", "P", 8080,
40+
"Port to expose readiness probe.")
41+
3742
k.TokenPassthrough.AddFlags(fs)
3843
}

cmd/app/run.go

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@ import (
66
"fmt"
77
"net"
88
"strconv"
9-
"time"
109

1110
"github.com/spf13/cobra"
12-
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
1311
"k8s.io/apiserver/pkg/server"
1412
apiserveroptions "k8s.io/apiserver/pkg/server/options"
1513
"k8s.io/apiserver/pkg/util/term"
16-
"k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
1714
"k8s.io/client-go/rest"
1815
cliflag "k8s.io/component-base/cli/flag"
1916
"k8s.io/component-base/cli/globalflag"
@@ -22,11 +19,12 @@ import (
2219
"github.com/jetstack/kube-oidc-proxy/pkg/probe"
2320
"github.com/jetstack/kube-oidc-proxy/pkg/proxy"
2421
"github.com/jetstack/kube-oidc-proxy/pkg/proxy/tokenreview"
22+
"github.com/jetstack/kube-oidc-proxy/pkg/util"
2523
"github.com/jetstack/kube-oidc-proxy/pkg/version"
2624
)
2725

2826
const (
29-
readinessProbePort = 8080
27+
appName = "kube-oidc-proxy"
3028
)
3129

3230
func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
@@ -38,7 +36,7 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
3836
BindPort: 6443,
3937
Required: true,
4038
ServerCert: apiserveroptions.GeneratableKeyCert{
41-
PairName: "kube-oidc-proxy",
39+
PairName: appName,
4240
CertDirectory: "/var/run/kubernetes",
4341
},
4442
}
@@ -48,11 +46,9 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
4846

4947
clientConfigOptions := options.NewClientFlags()
5048

51-
healthCheck := probe.New(strconv.Itoa(readinessProbePort))
52-
5349
// proxy command
5450
cmd := &cobra.Command{
55-
Use: "kube-oidc-proxy",
51+
Use: appName,
5652
Long: "kube-oidc-proxy is a reverse proxy to authenticate users to Kubernetes API servers with Open ID Connect Authentication.",
5753
RunE: func(cmd *cobra.Command, args []string) error {
5854
var err error
@@ -65,19 +61,19 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
6561
return err
6662
}
6763

68-
if ssoptionsWithLB.SecureServingOptions.BindPort == readinessProbePort {
64+
if ssoptionsWithLB.SecureServingOptions.BindPort == kopOptions.ReadinessProbePort {
6965
return errors.New("unable to securely serve on port 8080, used by readiness prob")
7066
}
7167

7268
var restConfig *rest.Config
73-
7469
if clientConfigOptions.ClientFlagsChanged(cmd) {
7570
// one or more client flags have been set to use client flag built
7671
// config
7772
restConfig, err = clientConfigOptions.ToRESTConfig()
7873
if err != nil {
7974
return err
8075
}
76+
8177
} else {
8278
// no client flags have been set so default to in-cluster config
8379
restConfig, err = rest.InClusterConfig()
@@ -86,24 +82,7 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
8682
}
8783
}
8884

89-
// oidc config
90-
oidcAuther, err := oidc.New(oidc.Options{
91-
APIAudiences: oidcOptions.APIAudiences,
92-
CAFile: oidcOptions.CAFile,
93-
ClientID: oidcOptions.ClientID,
94-
GroupsClaim: oidcOptions.GroupsClaim,
95-
GroupsPrefix: oidcOptions.GroupsPrefix,
96-
IssuerURL: oidcOptions.IssuerURL,
97-
RequiredClaims: oidcOptions.RequiredClaims,
98-
SupportedSigningAlgs: oidcOptions.SigningAlgs,
99-
UsernameClaim: oidcOptions.UsernameClaim,
100-
UsernamePrefix: oidcOptions.UsernamePrefix,
101-
})
102-
if err != nil {
103-
return err
104-
}
105-
106-
// Init token reviewer if enabled
85+
// Initialise token reviewer if enabled
10786
var tokenReviewer *tokenreview.TokenReview
10887
if kopOptions.TokenPassthrough.Enabled {
10988
tokenReviewer, err = tokenreview.New(restConfig, kopOptions.TokenPassthrough.Audiences)
@@ -112,8 +91,7 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
11291
}
11392
}
11493

115-
// oidc auther from config
116-
reqAuther := bearertoken.New(oidcAuther)
94+
// Initialise Secure Serving Config
11795
secureServingInfo := new(server.SecureServingInfo)
11896
if err := ssoptionsWithLB.ApplyTo(&secureServingInfo, nil); err != nil {
11997
return err
@@ -124,17 +102,30 @@ func NewRunCommand(stopCh <-chan struct{}) *cobra.Command {
124102
DisableImpersonation: kopOptions.DisableImpersonation,
125103
}
126104

127-
p := proxy.New(restConfig, reqAuther,
105+
// Initialise proxy with OIDC token authenticator
106+
p, err := proxy.New(restConfig, oidcOptions,
128107
tokenReviewer, secureServingInfo, proxyOptions)
108+
if err != nil {
109+
return err
110+
}
129111

130-
// run proxy
131-
waitCh, err := p.Run(stopCh)
112+
// Create a fake JWT to set up readiness probe
113+
fakeJWT, err := util.FakeJWT(oidcOptions.IssuerURL, oidcOptions.APIAudiences)
132114
if err != nil {
133115
return err
134116
}
135117

136-
time.Sleep(time.Second * 3)
137-
healthCheck.SetReady()
118+
// Start readiness probe
119+
if err := probe.Run(strconv.Itoa(kopOptions.ReadinessProbePort),
120+
fakeJWT, p.OIDCTokenAuthenticator()); err != nil {
121+
return err
122+
}
123+
124+
// Run proxy
125+
waitCh, err := p.Run(stopCh)
126+
if err != nil {
127+
return err
128+
}
138129

139130
<-waitCh
140131

pkg/probe/probe.go

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,32 @@
22
package probe
33

44
import (
5-
"errors"
5+
"context"
6+
"fmt"
67
"net"
78
"net/http"
8-
"sync"
9+
"strings"
910
"time"
1011

11-
"k8s.io/klog"
12-
1312
"github.com/heptiolabs/healthcheck"
13+
"k8s.io/apiserver/pkg/authentication/authenticator"
14+
"k8s.io/klog"
1415
)
1516

1617
type HealthCheck struct {
1718
handler healthcheck.Handler
18-
mu sync.Mutex
19-
ready bool
19+
20+
oidcAuther authenticator.Token
21+
fakeJWT string
22+
23+
ready bool
2024
}
2125

22-
func New(port string) *HealthCheck {
26+
func Run(port, fakeJWT string, oidcAuther authenticator.Token) error {
2327
h := &HealthCheck{
24-
handler: healthcheck.NewHandler(),
28+
handler: healthcheck.NewHandler(),
29+
oidcAuther: oidcAuther,
30+
fakeJWT: fakeJWT,
2531
}
2632

2733
h.handler.AddReadinessCheck("secure serving", h.Check)
@@ -36,30 +42,25 @@ func New(port string) *HealthCheck {
3642
}
3743
}()
3844

39-
return h
45+
return nil
4046
}
4147

4248
func (h *HealthCheck) Check() error {
43-
h.mu.Lock()
44-
defer h.mu.Unlock()
45-
46-
if !h.ready {
47-
return errors.New("not ready")
49+
if h.ready {
50+
return nil
4851
}
4952

50-
return nil
51-
}
52-
53-
func (h *HealthCheck) SetReady() {
54-
h.mu.Lock()
55-
defer h.mu.Unlock()
53+
_, _, err := h.oidcAuther.AuthenticateToken(context.Background(), h.fakeJWT)
54+
if err != nil && strings.HasSuffix(err.Error(), "authenticator not initialized") {
55+
err = fmt.Errorf("OIDC provider not yet initialized: %s", err)
56+
klog.V(4).Infof(err.Error())
57+
return err
58+
}
5659

5760
h.ready = true
58-
}
5961

60-
func (h *HealthCheck) SetNotReady() {
61-
h.mu.Lock()
62-
defer h.mu.Unlock()
62+
klog.Info("OIDC provider initialized, proxy ready")
63+
klog.V(4).Infof("OIDC provider initialized, readiness check returned error: %s", err)
6364

64-
h.ready = false
65+
return nil
6566
}

pkg/probe/probe_test.go

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,77 @@
22
package probe
33

44
import (
5+
"context"
6+
"errors"
57
"fmt"
68
"net/http"
79
"testing"
8-
"time"
10+
11+
"k8s.io/apiserver/pkg/authentication/authenticator"
912

1013
"github.com/jetstack/kube-oidc-proxy/pkg/util"
1114
)
1215

13-
func Test_Check(t *testing.T) {
16+
type fakeTokenAuthenticator struct {
17+
returnErr bool
18+
}
19+
20+
var _ authenticator.Token = &fakeTokenAuthenticator{}
21+
22+
func (f *fakeTokenAuthenticator) AuthenticateToken(context.Context, string) (*authenticator.Response, bool, error) {
23+
if f.returnErr {
24+
return nil, false, errors.New("foo bar authenticator not initialized")
25+
}
26+
27+
return nil, false, errors.New("some other error")
28+
}
29+
30+
func TestRun(t *testing.T) {
31+
f := &fakeTokenAuthenticator{
32+
returnErr: true,
33+
}
34+
1435
port, err := util.FreePort()
1536
if err != nil {
16-
t.Fatalf("unexpected error: %s", err)
37+
t.Error(err.Error())
38+
t.FailNow()
39+
}
40+
41+
fakeJWT, err := util.FakeJWT("issuer", nil)
42+
if err != nil {
43+
t.Error(err.Error())
44+
t.FailNow()
1745
}
1846

19-
p := New(port)
20-
time.Sleep(time.Second)
47+
if err := Run(port, fakeJWT, f); err != nil {
48+
t.Error(err.Error())
49+
t.FailNow()
50+
}
2151

2252
url := fmt.Sprintf("http://0.0.0.0:%s", port)
2353

24-
resp, err := http.Get(url + "/ready")
25-
if err != nil {
26-
t.Fatalf("unexpected error: %s", err)
54+
var resp *http.Response
55+
var i int
56+
57+
for {
58+
resp, err = http.Get(url + "/ready")
59+
if err == nil {
60+
break
61+
}
62+
63+
if i >= 5 {
64+
t.Errorf("unexpected error: %s", err)
65+
t.FailNow()
66+
}
67+
i++
2768
}
2869

2970
if resp.StatusCode != 503 {
3071
t.Errorf("expected ready probe to be responding and not ready, exp=%d got=%d",
3172
503, resp.StatusCode)
3273
}
3374

34-
p.SetReady()
75+
f.returnErr = false
3576

3677
resp, err = http.Get(url + "/ready")
3778
if err != nil {
@@ -43,15 +84,18 @@ func Test_Check(t *testing.T) {
4384
200, resp.StatusCode)
4485
}
4586

46-
p.SetNotReady()
87+
// Once the authenticator has returned with an non-initialised error, then
88+
// should always return ready
89+
90+
f.returnErr = true
4791

4892
resp, err = http.Get(url + "/ready")
4993
if err != nil {
5094
t.Fatalf("unexpected error: %s", err)
5195
}
5296

53-
if resp.StatusCode != 503 {
54-
t.Errorf("expected ready probe to be responding and not ready, exp=%d got=%d",
55-
503, resp.StatusCode)
97+
if resp.StatusCode != 200 {
98+
t.Errorf("expected ready probe to be responding and ready, exp=%d got=%d",
99+
200, resp.StatusCode)
56100
}
57101
}

0 commit comments

Comments
 (0)