@@ -1,14 +1,17 @@
11package validation
22
33import (
4− "testing"
54 "time"
65
6+ "github.com/Bose/minisentinel"
7+ "github.com/alicebob/miniredis/v2"
78 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
9+ . "github.com/onsi/ginkgo"
10+ . "github.com/onsi/ginkgo/extensions/table"
811 . "github.com/onsi/gomega"
912)
1013
11−func Test_validateSessionCookieMinimal(t *testing.T) {
14+var _ = Describe("Sessions", func() {
1215 const (
1316 passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
1417 setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
@@ -16,11 +19,16 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
1619 cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
1720 )
1821
19− testCases := map[string]struct {
22+ type cookieMinimalTableInput struct {
2023 opts *options.Options
2124 errStrings []string
22− }{
23− "No minimal cookie session": {
25+ }
26+
27+ DescribeTable("validateSessionCookieMinimal",
28+ func(o *cookieMinimalTableInput) {
29+ Expect(validateSessionCookieMinimal(o.opts)).To(ConsistOf(o.errStrings))
30+ },
31+ Entry("No minimal cookie session", &cookieMinimalTableInput{
2432 opts: &options.Options{
2533 Session: options.SessionOptions{
2634 Cookie: options.CookieStoreOptions{
@@ -29,8 +37,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
2937 },
3038 },
3139 errStrings: []string{},
32− },
33− "No minimal cookie session & passAuthorization": {
40+ }),
41+ Entry("No minimal cookie session & passAuthorization", &cookieMinimalTableInput{
3442 opts: &options.Options{
3543 Session: options.SessionOptions{
3644 Cookie: options.CookieStoreOptions{
@@ -40,8 +48,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
4048 PassAuthorization: true,
4149 },
4250 errStrings: []string{},
43− },
44− "Minimal cookie session no conflicts": {
51+ }),
52+ Entry("Minimal cookie session no conflicts", &cookieMinimalTableInput{
4553 opts: &options.Options{
4654 Session: options.SessionOptions{
4755 Cookie: options.CookieStoreOptions{
@@ -50,8 +58,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
5058 },
5159 },
5260 errStrings: []string{},
53− },
54− "PassAuthorization conflict": {
61+ }),
62+ Entry("PassAuthorization conflict", &cookieMinimalTableInput{
5563 opts: &options.Options{
5664 Session: options.SessionOptions{
5765 Cookie: options.CookieStoreOptions{
@@ -61,8 +69,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
6169 PassAuthorization: true,
6270 },
6371 errStrings: []string{passAuthorizationMsg},
64− },
65− "SetAuthorization conflict": {
72+ }),
73+ Entry("SetAuthorization conflict", &cookieMinimalTableInput{
6674 opts: &options.Options{
6775 Session: options.SessionOptions{
6876 Cookie: options.CookieStoreOptions{
@@ -72,8 +80,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
7280 SetAuthorization: true,
7381 },
7482 errStrings: []string{setAuthorizationMsg},
75− },
76− "PassAccessToken conflict": {
83+ }),
84+ Entry("PassAccessToken conflict", &cookieMinimalTableInput{
7785 opts: &options.Options{
7886 Session: options.SessionOptions{
7987 Cookie: options.CookieStoreOptions{
@@ -83,8 +91,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
8391 PassAccessToken: true,
8492 },
8593 errStrings: []string{passAccessTokenMsg},
86− },
87− "CookieRefresh conflict": {
94+ }),
95+ Entry("CookieRefresh conflict", &cookieMinimalTableInput{
8896 opts: &options.Options{
8997 Cookie: options.Cookie{
9098 Refresh: time.Hour,
@@ -96,8 +104,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
96104 },
97105 },
98106 errStrings: []string{cookieRefreshMsg},
99− },
100− "Multiple conflicts": {
107+ }),
108+ Entry("Multiple conflicts", &cookieMinimalTableInput{
101109 opts: &options.Options{
102110 Session: options.SessionOptions{
103111 Cookie: options.CookieStoreOptions{
@@ -108,14 +116,228 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
108116 PassAccessToken: true,
109117 },
110118 errStrings: []string{passAuthorizationMsg, passAccessTokenMsg},
111− },
112− }
119+ }),
120+ )
113121
114− for testName, tc := range testCases {
115− t.Run(testName, func(t *testing.T) {
116− errStrings := validateSessionCookieMinimal(tc.opts)
117− g := NewWithT(t)
118− g.Expect(errStrings).To(ConsistOf(tc.errStrings))
119− })
122+ const (
123+ clusterAndSentinelMsg = "unable to initialize a redis client: options redis-use-sentinel and redis-use-cluster are mutually exclusive"
124+ parseWrongSchemeMsg = "unable to initialize a redis client: unable to parse redis url: invalid redis URL scheme: https"
125+ parseWrongFormatMsg = "unable to initialize a redis client: unable to parse redis url: invalid redis database number: \"wrong\""
126+ invalidPasswordSetMsg = "unable to set a redis initialization key: WRONGPASS invalid username-password pair"
127+ invalidPasswordDelMsg = "unable to delete the redis initialization key: WRONGPASS invalid username-password pair"
128+ unreachableRedisSetMsg = "unable to set a redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
129+ unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
130+ unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels are unreachable"
131+ unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels are unreachable"
132+ )
133+
134+ type redisStoreTableInput struct {
135+ // miniredis setup details
136+ password string
137+ useSentinel bool
138+ setAddr bool
139+ setSentinelAddr bool
140+ setMasterName bool
141+
142+ opts *options.Options
143+ errStrings []string
120144 }
121−}
145+
146+ DescribeTable("validateRedisSessionStore",
147+ func(o *redisStoreTableInput) {
148+ mr, err := miniredis.Run()
149+ Expect(err).ToNot(HaveOccurred())
150+ mr.RequireAuth(o.password)
151+ defer mr.Close()
152+
153+ if o.setAddr && !o.useSentinel {
154+ o.opts.Session.Redis.ConnectionURL = "redis://" + mr.Addr()
155+ }
156+
157+ if o.useSentinel {
158+ ms := minisentinel.NewSentinel(mr)
159+ Expect(ms.Start()).To(Succeed())
160+ defer ms.Close()
161+
162+ if o.setSentinelAddr {
163+ o.opts.Session.Redis.SentinelConnectionURLs = []string{"redis://" + ms.Addr()}
164+ }
165+ if o.setMasterName {
166+ o.opts.Session.Redis.SentinelMasterName = ms.MasterInfo().Name
167+ }
168+ }
169+
170+ Expect(validateRedisSessionStore(o.opts)).To(ConsistOf(o.errStrings))
171+ },
172+ Entry("cookie sessions are skipped", &redisStoreTableInput{
173+ opts: &options.Options{
174+ Session: options.SessionOptions{
175+ Type: options.CookieSessionStoreType,
176+ },
177+ },
178+ errStrings: []string{},
179+ }),
180+ Entry("connect successfully to pure redis", &redisStoreTableInput{
181+ setAddr: true,
182+
183+ opts: &options.Options{
184+ Session: options.SessionOptions{
185+ Type: options.RedisSessionStoreType,
186+ },
187+ },
188+ errStrings: []string{},
189+ }),
190+ Entry("failed redis connection with wrong address", &redisStoreTableInput{
191+ opts: &options.Options{
192+ Session: options.SessionOptions{
193+ Type: options.RedisSessionStoreType,
194+ Redis: options.RedisStoreOptions{
195+ ConnectionURL: "redis://127.0.0.1:65535",
196+ },
197+ },
198+ },
199+ errStrings: []string{unreachableRedisSetMsg, unreachableRedisDelMsg},
200+ }),
201+ Entry("fail to parse an invalid connection URL with wrong scheme", &redisStoreTableInput{
202+ opts: &options.Options{
203+ Session: options.SessionOptions{
204+ Type: options.RedisSessionStoreType,
205+ Redis: options.RedisStoreOptions{
206+ ConnectionURL: "https://example.com",
207+ },
208+ },
209+ },
210+ errStrings: []string{parseWrongSchemeMsg},
211+ }),
212+ Entry("fail to parse an invalid connection URL with invalid format", &redisStoreTableInput{
213+ opts: &options.Options{
214+ Session: options.SessionOptions{
215+ Type: options.RedisSessionStoreType,
216+ Redis: options.RedisStoreOptions{
217+ ConnectionURL: "redis://127.0.0.1:6379/wrong",
218+ },
219+ },
220+ },
221+ errStrings: []string{parseWrongFormatMsg},
222+ }),
223+ Entry("connect successfully to pure redis with password", &redisStoreTableInput{
224+ password: "abcdef123",
225+ setAddr: true,
226+
227+ opts: &options.Options{
228+ Session: options.SessionOptions{
229+ Type: options.RedisSessionStoreType,
230+ Redis: options.RedisStoreOptions{
231+ Password: "abcdef123",
232+ },
233+ },
234+ },
235+ errStrings: []string{},
236+ }),
237+ Entry("failed connection with wrong password", &redisStoreTableInput{
238+ password: "abcdef123",
239+ setAddr: true,
240+
241+ opts: &options.Options{
242+ Session: options.SessionOptions{
243+ Type: options.RedisSessionStoreType,
244+ Redis: options.RedisStoreOptions{
245+ Password: "zyxwtuv987",
246+ },
247+ },
248+ },
249+ errStrings: []string{invalidPasswordSetMsg, invalidPasswordDelMsg},
250+ }),
251+ Entry("connect successfully to sentinel redis", &redisStoreTableInput{
252+ useSentinel: true,
253+ setSentinelAddr: true,
254+ setMasterName: true,
255+
256+ opts: &options.Options{
257+ Session: options.SessionOptions{
258+ Type: options.RedisSessionStoreType,
259+ Redis: options.RedisStoreOptions{
260+ UseSentinel: true,
261+ },
262+ },
263+ },
264+ errStrings: []string{},
265+ }),
266+ Entry("connect successfully to sentinel redis with password", &redisStoreTableInput{
267+ password: "abcdef123",
268+ useSentinel: true,
269+ setSentinelAddr: true,
270+ setMasterName: true,
271+
272+ opts: &options.Options{
273+ Session: options.SessionOptions{
274+ Type: options.RedisSessionStoreType,
275+ Redis: options.RedisStoreOptions{
276+ Password: "abcdef123",
277+ UseSentinel: true,
278+ },
279+ },
280+ },
281+ errStrings: []string{},
282+ }),
283+ Entry("failed connection to sentinel redis with wrong password", &redisStoreTableInput{
284+ password: "abcdef123",
285+ useSentinel: true,
286+ setSentinelAddr: true,
287+ setMasterName: true,
288+
289+ opts: &options.Options{
290+ Session: options.SessionOptions{
291+ Type: options.RedisSessionStoreType,
292+ Redis: options.RedisStoreOptions{
293+ Password: "zyxwtuv987",
294+ UseSentinel: true,
295+ },
296+ },
297+ },
298+ errStrings: []string{invalidPasswordSetMsg, invalidPasswordDelMsg},
299+ }),
300+ Entry("failed connection to sentinel redis with wrong master name", &redisStoreTableInput{
301+ useSentinel: true,
302+ setSentinelAddr: true,
303+
304+ opts: &options.Options{
305+ Session: options.SessionOptions{
306+ Type: options.RedisSessionStoreType,
307+ Redis: options.RedisStoreOptions{
308+ UseSentinel: true,
309+ SentinelMasterName: "WRONG",
310+ },
311+ },
312+ },
313+ errStrings: []string{unreachableSentinelSetMsg, unrechableSentinelDelMsg},
314+ }),
315+ Entry("failed connection to sentinel redis with wrong address", &redisStoreTableInput{
316+ useSentinel: true,
317+ setMasterName: true,
318+
319+ opts: &options.Options{
320+ Session: options.SessionOptions{
321+ Type: options.RedisSessionStoreType,
322+ Redis: options.RedisStoreOptions{
323+ UseSentinel: true,
324+ SentinelConnectionURLs: []string{"redis://127.0.0.1:65535"},
325+ },
326+ },
327+ },
328+ errStrings: []string{unreachableSentinelSetMsg, unrechableSentinelDelMsg},
329+ }),
330+ Entry("sentinel and cluster both enabled fails", &redisStoreTableInput{
331+ opts: &options.Options{
332+ Session: options.SessionOptions{
333+ Type: options.RedisSessionStoreType,
334+ Redis: options.RedisStoreOptions{
335+ UseSentinel: true,
336+ UseCluster: true,
337+ },
338+ },
339+ },
340+ errStrings: []string{clusterAndSentinelMsg},
341+ }),
342+ )
343+})