1package validation 2 3import ( 4 "time" 5 6 "github.com/Bose/minisentinel" 7 "github.com/alicebob/miniredis/v2" 8 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" 9 . "github.com/onsi/ginkgo" 10 . "github.com/onsi/ginkgo/extensions/table" 11 . "github.com/onsi/gomega" 12) 13 14var _ = Describe("Sessions", func() { 15 const ( 16 idTokenConflictMsg = "id_token claim for header \"X-ID-Token\" requires oauth tokens in sessions. session_cookie_minimal cannot be set" 17 accessTokenConflictMsg = "access_token claim for header \"X-Access-Token\" requires oauth tokens in sessions. session_cookie_minimal cannot be set" 18 cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set" 19 ) 20 21 type cookieMinimalTableInput struct { 22 opts *options.Options 23 errStrings []string 24 } 25 26 DescribeTable("validateSessionCookieMinimal", 27 func(o *cookieMinimalTableInput) { 28 Expect(validateSessionCookieMinimal(o.opts)).To(ConsistOf(o.errStrings)) 29 }, 30 Entry("No minimal cookie session", &cookieMinimalTableInput{ 31 opts: &options.Options{ 32 Session: options.SessionOptions{ 33 Cookie: options.CookieStoreOptions{ 34 Minimal: false, 35 }, 36 }, 37 }, 38 errStrings: []string{}, 39 }), 40 Entry("No minimal cookie session & request header has access_token claim", &cookieMinimalTableInput{ 41 opts: &options.Options{ 42 Session: options.SessionOptions{ 43 Cookie: options.CookieStoreOptions{ 44 Minimal: false, 45 }, 46 }, 47 InjectRequestHeaders: []options.Header{ 48 { 49 Name: "X-Access-Token", 50 Values: []options.HeaderValue{ 51 { 52 ClaimSource: &options.ClaimSource{ 53 Claim: "access_token", 54 }, 55 }, 56 }, 57 }, 58 }, 59 }, 60 errStrings: []string{}, 61 }), 62 Entry("Minimal cookie session no conflicts", &cookieMinimalTableInput{ 63 opts: &options.Options{ 64 Session: options.SessionOptions{ 65 Cookie: options.CookieStoreOptions{ 66 Minimal: true, 67 }, 68 }, 69 }, 70 errStrings: []string{}, 71 }), 72 Entry("Request Header id_token conflict", &cookieMinimalTableInput{ 73 opts: &options.Options{ 74 Session: options.SessionOptions{ 75 Cookie: options.CookieStoreOptions{ 76 Minimal: true, 77 }, 78 }, 79 InjectRequestHeaders: []options.Header{ 80 { 81 Name: "X-ID-Token", 82 Values: []options.HeaderValue{ 83 { 84 ClaimSource: &options.ClaimSource{ 85 Claim: "id_token", 86 }, 87 }, 88 }, 89 }, 90 }, 91 }, 92 errStrings: []string{idTokenConflictMsg}, 93 }), 94 Entry("Response Header id_token conflict", &cookieMinimalTableInput{ 95 opts: &options.Options{ 96 Session: options.SessionOptions{ 97 Cookie: options.CookieStoreOptions{ 98 Minimal: true, 99 }, 100 }, 101 InjectResponseHeaders: []options.Header{ 102 { 103 Name: "X-ID-Token", 104 Values: []options.HeaderValue{ 105 { 106 ClaimSource: &options.ClaimSource{ 107 Claim: "id_token", 108 }, 109 }, 110 }, 111 }, 112 }, 113 }, 114 errStrings: []string{idTokenConflictMsg}, 115 }), 116 Entry("Request Header access_token conflict", &cookieMinimalTableInput{ 117 opts: &options.Options{ 118 Session: options.SessionOptions{ 119 Cookie: options.CookieStoreOptions{ 120 Minimal: true, 121 }, 122 }, 123 InjectRequestHeaders: []options.Header{ 124 { 125 Name: "X-Access-Token", 126 Values: []options.HeaderValue{ 127 { 128 ClaimSource: &options.ClaimSource{ 129 Claim: "access_token", 130 }, 131 }, 132 }, 133 }, 134 }, 135 }, 136 errStrings: []string{accessTokenConflictMsg}, 137 }), 138 Entry("CookieRefresh conflict", &cookieMinimalTableInput{ 139 opts: &options.Options{ 140 Cookie: options.Cookie{ 141 Refresh: time.Hour, 142 }, 143 Session: options.SessionOptions{ 144 Cookie: options.CookieStoreOptions{ 145 Minimal: true, 146 }, 147 }, 148 }, 149 errStrings: []string{cookieRefreshMsg}, 150 }), 151 Entry("Multiple conflicts", &cookieMinimalTableInput{ 152 opts: &options.Options{ 153 Session: options.SessionOptions{ 154 Cookie: options.CookieStoreOptions{ 155 Minimal: true, 156 }, 157 }, 158 InjectResponseHeaders: []options.Header{ 159 { 160 Name: "X-ID-Token", 161 Values: []options.HeaderValue{ 162 { 163 ClaimSource: &options.ClaimSource{ 164 Claim: "id_token", 165 }, 166 }, 167 }, 168 }, 169 }, 170 InjectRequestHeaders: []options.Header{ 171 { 172 Name: "X-Access-Token", 173 Values: []options.HeaderValue{ 174 { 175 ClaimSource: &options.ClaimSource{ 176 Claim: "access_token", 177 }, 178 }, 179 }, 180 }, 181 }, 182 }, 183 errStrings: []string{idTokenConflictMsg, accessTokenConflictMsg}, 184 }), 185 ) 186 187 const ( 188 clusterAndSentinelMsg = "unable to initialize a redis client: options redis-use-sentinel and redis-use-cluster are mutually exclusive" 189 parseWrongSchemeMsg = "unable to initialize a redis client: unable to parse redis url: redis: invalid URL scheme: https" 190 parseWrongFormatMsg = "unable to initialize a redis client: unable to parse redis url: redis: invalid database number: \"wrong\"" 191 invalidPasswordSetMsg = "unable to set a redis initialization key: WRONGPASS invalid username-password pair" 192 invalidPasswordDelMsg = "unable to delete the redis initialization key: WRONGPASS invalid username-password pair" 193 unreachableRedisSetMsg = "unable to set a redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused" 194 unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused" 195 unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels are unreachable" 196 unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels are unreachable" 197 ) 198 199 type redisStoreTableInput struct { 200 // miniredis setup details 201 password string 202 useSentinel bool 203 setAddr bool 204 setSentinelAddr bool 205 setMasterName bool 206 207 opts *options.Options 208 errStrings []string 209 } 210 211 DescribeTable("validateRedisSessionStore", 212 func(o *redisStoreTableInput) { 213 mr, err := miniredis.Run() 214 Expect(err).ToNot(HaveOccurred()) 215 mr.RequireAuth(o.password) 216 defer mr.Close() 217 218 if o.setAddr && !o.useSentinel { 219 o.opts.Session.Redis.ConnectionURL = "redis://" + mr.Addr() 220 } 221 222 if o.useSentinel { 223 ms := minisentinel.NewSentinel(mr) 224 Expect(ms.Start()).To(Succeed()) 225 defer ms.Close() 226 227 if o.setSentinelAddr { 228 o.opts.Session.Redis.SentinelConnectionURLs = []string{"redis://" + ms.Addr()} 229 } 230 if o.setMasterName { 231 o.opts.Session.Redis.SentinelMasterName = ms.MasterInfo().Name 232 } 233 } 234 235 Expect(validateRedisSessionStore(o.opts)).To(ConsistOf(o.errStrings)) 236 }, 237 Entry("cookie sessions are skipped", &redisStoreTableInput{ 238 opts: &options.Options{ 239 Session: options.SessionOptions{ 240 Type: options.CookieSessionStoreType, 241 }, 242 }, 243 errStrings: []string{}, 244 }), 245 Entry("connect successfully to pure redis", &redisStoreTableInput{ 246 setAddr: true, 247 248 opts: &options.Options{ 249 Session: options.SessionOptions{ 250 Type: options.RedisSessionStoreType, 251 }, 252 }, 253 errStrings: []string{}, 254 }), 255 Entry("failed redis connection with wrong address", &redisStoreTableInput{ 256 opts: &options.Options{ 257 Session: options.SessionOptions{ 258 Type: options.RedisSessionStoreType, 259 Redis: options.RedisStoreOptions{ 260 ConnectionURL: "redis://127.0.0.1:65535", 261 }, 262 }, 263 }, 264 errStrings: []string{unreachableRedisSetMsg, unreachableRedisDelMsg}, 265 }), 266 Entry("fail to parse an invalid connection URL with wrong scheme", &redisStoreTableInput{ 267 opts: &options.Options{ 268 Session: options.SessionOptions{ 269 Type: options.RedisSessionStoreType, 270 Redis: options.RedisStoreOptions{ 271 ConnectionURL: "https://example.com", 272 }, 273 }, 274 }, 275 errStrings: []string{parseWrongSchemeMsg}, 276 }), 277 Entry("fail to parse an invalid connection URL with invalid format", &redisStoreTableInput{ 278 opts: &options.Options{ 279 Session: options.SessionOptions{ 280 Type: options.RedisSessionStoreType, 281 Redis: options.RedisStoreOptions{ 282 ConnectionURL: "redis://127.0.0.1:6379/wrong", 283 }, 284 }, 285 }, 286 errStrings: []string{parseWrongFormatMsg}, 287 }), 288 Entry("connect successfully to pure redis with password", &redisStoreTableInput{ 289 password: "abcdef123", 290 setAddr: true, 291 292 opts: &options.Options{ 293 Session: options.SessionOptions{ 294 Type: options.RedisSessionStoreType, 295 Redis: options.RedisStoreOptions{ 296 Password: "abcdef123", 297 }, 298 }, 299 }, 300 errStrings: []string{}, 301 }), 302 Entry("failed connection with wrong password", &redisStoreTableInput{ 303 password: "abcdef123", 304 setAddr: true, 305 306 opts: &options.Options{ 307 Session: options.SessionOptions{ 308 Type: options.RedisSessionStoreType, 309 Redis: options.RedisStoreOptions{ 310 Password: "zyxwtuv987", 311 }, 312 }, 313 }, 314 errStrings: []string{invalidPasswordSetMsg, invalidPasswordDelMsg}, 315 }), 316 Entry("connect successfully to sentinel redis", &redisStoreTableInput{ 317 useSentinel: true, 318 setSentinelAddr: true, 319 setMasterName: true, 320 321 opts: &options.Options{ 322 Session: options.SessionOptions{ 323 Type: options.RedisSessionStoreType, 324 Redis: options.RedisStoreOptions{ 325 UseSentinel: true, 326 }, 327 }, 328 }, 329 errStrings: []string{}, 330 }), 331 Entry("connect successfully to sentinel redis with password", &redisStoreTableInput{ 332 password: "abcdef123", 333 useSentinel: true, 334 setSentinelAddr: true, 335 setMasterName: true, 336 337 opts: &options.Options{ 338 Session: options.SessionOptions{ 339 Type: options.RedisSessionStoreType, 340 Redis: options.RedisStoreOptions{ 341 Password: "abcdef123", 342 UseSentinel: true, 343 }, 344 }, 345 }, 346 errStrings: []string{}, 347 }), 348 Entry("failed connection to sentinel redis with wrong password", &redisStoreTableInput{ 349 password: "abcdef123", 350 useSentinel: true, 351 setSentinelAddr: true, 352 setMasterName: true, 353 354 opts: &options.Options{ 355 Session: options.SessionOptions{ 356 Type: options.RedisSessionStoreType, 357 Redis: options.RedisStoreOptions{ 358 Password: "zyxwtuv987", 359 UseSentinel: true, 360 }, 361 }, 362 }, 363 errStrings: []string{invalidPasswordSetMsg, invalidPasswordDelMsg}, 364 }), 365 Entry("failed connection to sentinel redis with wrong master name", &redisStoreTableInput{ 366 useSentinel: true, 367 setSentinelAddr: true, 368 369 opts: &options.Options{ 370 Session: options.SessionOptions{ 371 Type: options.RedisSessionStoreType, 372 Redis: options.RedisStoreOptions{ 373 UseSentinel: true, 374 SentinelMasterName: "WRONG", 375 }, 376 }, 377 }, 378 errStrings: []string{unreachableSentinelSetMsg, unrechableSentinelDelMsg}, 379 }), 380 Entry("failed connection to sentinel redis with wrong address", &redisStoreTableInput{ 381 useSentinel: true, 382 setMasterName: true, 383 384 opts: &options.Options{ 385 Session: options.SessionOptions{ 386 Type: options.RedisSessionStoreType, 387 Redis: options.RedisStoreOptions{ 388 UseSentinel: true, 389 SentinelConnectionURLs: []string{"redis://127.0.0.1:65535"}, 390 }, 391 }, 392 }, 393 errStrings: []string{unreachableSentinelSetMsg, unrechableSentinelDelMsg}, 394 }), 395 Entry("sentinel and cluster both enabled fails", &redisStoreTableInput{ 396 opts: &options.Options{ 397 Session: options.SessionOptions{ 398 Type: options.RedisSessionStoreType, 399 Redis: options.RedisStoreOptions{ 400 UseSentinel: true, 401 UseCluster: true, 402 }, 403 }, 404 }, 405 errStrings: []string{clusterAndSentinelMsg}, 406 }), 407 ) 408}) 409