Skip to content

Commit 59592ba

Browse files
committed
Add OIDC test server
1 parent 98406e2 commit 59592ba

File tree

3 files changed

+375
-0
lines changed

3 files changed

+375
-0
lines changed

test/utils/oidc/handlers.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
//go:generate mockgen -source=handlers.go -destination=handlers.mock.go -package=oidc TokenHandler JWKsHandler
18+
19+
package oidc
20+
21+
import (
22+
"gopkg.in/square/go-jose.v2"
23+
)
24+
25+
type Token struct {
26+
IDToken string `json:"id_token"`
27+
AccessToken string `json:"access_token"`
28+
TokenType string `json:"token_type"`
29+
RefreshToken string `json:"refresh_token"`
30+
ExpiresIn int64 `json:"expires_in"`
31+
}
32+
33+
type TokenHandler interface {
34+
Token() (Token, error)
35+
}
36+
37+
type JWKsHandler interface {
38+
KeySet() jose.JSONWebKeySet
39+
}

test/utils/oidc/handlers.mock.go

Lines changed: 103 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/utils/oidc/testserver.go

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package oidc
18+
19+
import (
20+
"crypto"
21+
"crypto/rsa"
22+
"crypto/tls"
23+
"encoding/hex"
24+
"encoding/json"
25+
"errors"
26+
"fmt"
27+
"net/http"
28+
"net/http/httptest"
29+
"net/url"
30+
"os"
31+
"testing"
32+
33+
"github.com/golang/mock/gomock"
34+
"github.com/stretchr/testify/require"
35+
"gopkg.in/square/go-jose.v2"
36+
)
37+
38+
const (
39+
openIDWellKnownWebPath = "/.well-known/openid-configuration"
40+
authWebPath = "/auth"
41+
tokenWebPath = "/token"
42+
jwksWebPath = "/jwks"
43+
)
44+
45+
var (
46+
ErrRefreshTokenExpired = errors.New("refresh token is expired")
47+
ErrBadClientID = errors.New("client ID is bad")
48+
)
49+
50+
type TestServer struct {
51+
httpServer *httptest.Server
52+
tokenHandler *MockTokenHandler
53+
jwksHandler *MockJWKsHandler
54+
}
55+
56+
// JwksHandler is getter of JSON Web Key Sets handler
57+
func (ts *TestServer) JwksHandler() *MockJWKsHandler {
58+
return ts.jwksHandler
59+
}
60+
61+
// TokenHandler is getter of JWT token handler
62+
func (ts *TestServer) TokenHandler() *MockTokenHandler {
63+
return ts.tokenHandler
64+
}
65+
66+
// URL returns the public URL of server
67+
func (ts *TestServer) URL() string {
68+
return ts.httpServer.URL
69+
}
70+
71+
// TokenURL returns the public URL of JWT token endpoint
72+
func (ts *TestServer) TokenURL() (string, error) {
73+
url, err := url.JoinPath(ts.httpServer.URL, tokenWebPath)
74+
if err != nil {
75+
return "", fmt.Errorf("error joining paths: %v", err)
76+
}
77+
78+
return url, nil
79+
}
80+
81+
// BuildAndRunTestServer configures OIDC TLS server and its routing
82+
func BuildAndRunTestServer(t *testing.T, caPath, caKeyPath string) *TestServer {
83+
t.Helper()
84+
85+
certContent, err := os.ReadFile(caPath)
86+
require.NoError(t, err)
87+
keyContent, err := os.ReadFile(caKeyPath)
88+
require.NoError(t, err)
89+
90+
cert, err := tls.X509KeyPair(certContent, keyContent)
91+
require.NoError(t, err)
92+
93+
mux := http.NewServeMux()
94+
httpServer := httptest.NewUnstartedServer(mux)
95+
httpServer.TLS = &tls.Config{
96+
Certificates: []tls.Certificate{cert},
97+
}
98+
httpServer.StartTLS()
99+
100+
mockCtrl := gomock.NewController(t)
101+
102+
t.Cleanup(func() {
103+
mockCtrl.Finish()
104+
httpServer.Close()
105+
})
106+
107+
oidcServer := &TestServer{
108+
httpServer: httpServer,
109+
tokenHandler: NewMockTokenHandler(mockCtrl),
110+
jwksHandler: NewMockJWKsHandler(mockCtrl),
111+
}
112+
113+
mux.HandleFunc(openIDWellKnownWebPath, func(writer http.ResponseWriter, request *http.Request) {
114+
authURL, err := url.JoinPath(httpServer.URL + authWebPath)
115+
require.NoError(t, err)
116+
tokenURL, err := url.JoinPath(httpServer.URL + tokenWebPath)
117+
require.NoError(t, err)
118+
jwksURL, err := url.JoinPath(httpServer.URL + jwksWebPath)
119+
require.NoError(t, err)
120+
userInfoURL, err := url.JoinPath(httpServer.URL + authWebPath)
121+
122+
err = json.NewEncoder(writer).Encode(struct {
123+
Issuer string `json:"issuer"`
124+
AuthURL string `json:"authorization_endpoint"`
125+
TokenURL string `json:"token_endpoint"`
126+
JWKSURL string `json:"jwks_uri"`
127+
UserInfoURL string `json:"userinfo_endpoint"`
128+
}{
129+
Issuer: httpServer.URL,
130+
AuthURL: authURL,
131+
TokenURL: tokenURL,
132+
JWKSURL: jwksURL,
133+
UserInfoURL: userInfoURL,
134+
})
135+
require.NoError(t, err)
136+
137+
writer.Header().Add("Content-Type", "application/json")
138+
writer.WriteHeader(http.StatusOK)
139+
})
140+
141+
mux.HandleFunc(tokenWebPath, func(writer http.ResponseWriter, request *http.Request) {
142+
token, err := oidcServer.tokenHandler.Token()
143+
if err != nil {
144+
http.Error(writer, err.Error(), http.StatusBadRequest)
145+
return
146+
}
147+
148+
writer.Header().Add("Content-Type", "application/json")
149+
writer.WriteHeader(http.StatusOK)
150+
151+
err = json.NewEncoder(writer).Encode(token)
152+
require.NoError(t, err)
153+
})
154+
155+
mux.HandleFunc(authWebPath, func(writer http.ResponseWriter, request *http.Request) {
156+
writer.WriteHeader(http.StatusOK)
157+
})
158+
159+
mux.HandleFunc(jwksWebPath, func(writer http.ResponseWriter, request *http.Request) {
160+
keySet := oidcServer.jwksHandler.KeySet()
161+
162+
writer.Header().Add("Content-Type", "application/json")
163+
writer.WriteHeader(http.StatusOK)
164+
165+
err := json.NewEncoder(writer).Encode(keySet)
166+
require.NoError(t, err)
167+
})
168+
169+
return oidcServer
170+
}
171+
172+
// TokenHandlerBehaviourReturningPredefinedJWT describes the scenario when signed JWT token is being created.
173+
// This behaviour should being applied to the MockTokenHandler.
174+
func TokenHandlerBehaviourReturningPredefinedJWT(
175+
t *testing.T,
176+
rsaPrivateKey *rsa.PrivateKey,
177+
issClaim,
178+
audClaim,
179+
subClaim,
180+
accessToken,
181+
refreshToken string,
182+
expClaim int64,
183+
) func() (Token, error) {
184+
t.Helper()
185+
186+
return func() (Token, error) {
187+
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: rsaPrivateKey}, nil)
188+
require.NoError(t, err)
189+
190+
payload := struct {
191+
Iss string `json:"iss"`
192+
Aud string `json:"aud"`
193+
Sub string `json:"sub"`
194+
Exp int64 `json:"exp"`
195+
}{
196+
Iss: issClaim,
197+
Aud: audClaim,
198+
Sub: subClaim,
199+
Exp: expClaim,
200+
}
201+
payloadJSON, err := json.Marshal(payload)
202+
require.NoError(t, err)
203+
204+
idTokenSignature, err := signer.Sign(payloadJSON)
205+
require.NoError(t, err)
206+
idToken, err := idTokenSignature.CompactSerialize()
207+
require.NoError(t, err)
208+
209+
return Token{
210+
IDToken: idToken,
211+
AccessToken: accessToken,
212+
RefreshToken: refreshToken,
213+
}, nil
214+
}
215+
}
216+
217+
// DefaultJwksHandlerBehaviour describes the scenario when JSON Web Key Set token is being returned.
218+
// This behaviour should being applied to the MockJWKsHandler.
219+
func DefaultJwksHandlerBehaviour(t *testing.T, verificationPublicKey *rsa.PublicKey) func() jose.JSONWebKeySet {
220+
t.Helper()
221+
222+
return func() jose.JSONWebKeySet {
223+
key := jose.JSONWebKey{Key: verificationPublicKey, Use: "sig", Algorithm: string(jose.RS256)}
224+
225+
thumbprint, err := key.Thumbprint(crypto.SHA256)
226+
require.NoError(t, err)
227+
228+
key.KeyID = hex.EncodeToString(thumbprint)
229+
return jose.JSONWebKeySet{
230+
Keys: []jose.JSONWebKey{key},
231+
}
232+
}
233+
}

0 commit comments

Comments
 (0)