Skip to content

Commit 233ec94

Browse files
authored
[installer] Add JWT cookie opts to config WEB-101 (#17332)
* retest * retest * [installer] Add cookie name to config * Fix * fix
1 parent d9ccc1d commit 233ec94

File tree

9 files changed

+143
-18
lines changed

9 files changed

+143
-18
lines changed

components/public-api/go/config/config.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,17 @@ type AuthConfiguration struct {
5252
}
5353

5454
type SessionConfig struct {
55-
LifetimeSeconds int64 `json:"lifetimeSeconds"`
56-
Issuer string `json:"issuer"`
55+
LifetimeSeconds int64 `json:"lifetimeSeconds"`
56+
Issuer string `json:"issuer"`
57+
Cookie CookieConfig `json:"cookie"`
58+
}
59+
60+
type CookieConfig struct {
61+
Name string `json:"name"`
62+
MaxAge int64 `json:"maxAge"`
63+
SameSite string `json:"sameSite"`
64+
Secure bool `json:"secure"`
65+
HTTPOnly bool `json:"httpOnly"`
5766
}
5867

5968
type AuthPKIConfiguration struct {

components/server/src/auth/login-completion-handler.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ export class LoginCompletionHandler {
100100
if (isJWTCookieExperimentEnabled) {
101101
const token = await this.authJWT.sign(user.id, {});
102102

103-
response.cookie(SessionHandlerProvider.getJWTCookieName(this.config.hostUrl), token, {
104-
maxAge: this.config.session.maxAgeMs,
105-
httpOnly: true,
106-
sameSite: "lax",
107-
secure: true,
103+
response.cookie(SessionHandlerProvider.getJWTCookieName(this.config), token, {
104+
maxAge: this.config.auth.session.cookie.maxAge,
105+
httpOnly: this.config.auth.session.cookie.httpOnly,
106+
sameSite: this.config.auth.session.cookie.sameSite,
107+
secure: this.config.auth.session.cookie.secure,
108108
});
109109

110110
reportJWTCookieIssued();

components/server/src/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export type Config = Omit<
5555
session: {
5656
lifetimeSeconds: number;
5757
issuer: string;
58+
cookie: CookieConfig;
5859
};
5960
};
6061
};
@@ -258,10 +259,19 @@ export interface ConfigSerialized {
258259
session: {
259260
lifetimeSeconds: number;
260261
issuer: string;
262+
cookie: CookieConfig;
261263
};
262264
};
263265
}
264266

267+
export interface CookieConfig {
268+
name: string;
269+
maxAge: number;
270+
sameSite: boolean | "lax" | "strict" | "none";
271+
secure: boolean;
272+
httpOnly: boolean;
273+
}
274+
265275
export interface AuthPKIConfig {
266276
signing: KeyPair;
267277
validating?: KeyPair[];

components/server/src/session-handler.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const MySQLStore = mysqlstore(session);
1515
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1616
import { Config as DBConfig } from "@gitpod/gitpod-db/lib/config";
1717
import { Config } from "./config";
18-
import { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url";
1918

2019
@injectable()
2120
export class SessionHandlerProvider {
@@ -66,12 +65,8 @@ export class SessionHandlerProvider {
6665
return `${derived}v2_`;
6766
}
6867

69-
static getJWTCookieName(hostURL: GitpodHostUrl) {
70-
const derived = hostURL
71-
.toString()
72-
.replace(/https?/, "")
73-
.replace(/[\W_]+/g, "_");
74-
return `${derived}jwt_`;
68+
static getJWTCookieName(config: Config) {
69+
return config.auth.session.cookie.name;
7570
}
7671

7772
public clearSessionCookie(res: express.Response, config: Config): void {
@@ -82,7 +77,7 @@ export class SessionHandlerProvider {
8277
delete options.maxAge;
8378
res.clearCookie(name, options);
8479

85-
res.clearCookie(SessionHandlerProvider.getJWTCookieName(this.config.hostUrl));
80+
res.clearCookie(SessionHandlerProvider.getJWTCookieName(this.config));
8681
}
8782

8883
protected createStore(): any | undefined {

install/installer/pkg/components/auth/config.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package auth
66

77
import (
88
"fmt"
9+
"regexp"
910
"time"
1011

1112
"github.com/gitpod-io/gitpod/installer/pkg/common"
@@ -21,17 +22,41 @@ type Config struct {
2122

2223
type SessionConfig struct {
2324
// How long shoud the session be valid for?
24-
LifetimeSeconds int64 `json:"lifetimeSeconds"`
25-
Issuer string `json:"issuer"`
25+
LifetimeSeconds int64 `json:"lifetimeSeconds"`
26+
Issuer string `json:"issuer"`
27+
Cookie CookieConfig `json:"cookie"`
28+
}
29+
30+
type CookieConfig struct {
31+
Name string `json:"name"`
32+
MaxAge int64 `json:"maxAge"`
33+
SameSite string `json:"sameSite"`
34+
Secure bool `json:"secure"`
35+
HTTPOnly bool `json:"httpOnly"`
2636
}
2737

2838
func GetConfig(ctx *common.RenderContext) ([]corev1.Volume, []corev1.VolumeMount, Config) {
2939
volumes, mounts, pki := getPKI()
40+
lifetime := int64((7 * 24 * time.Hour).Seconds())
3041
return volumes, mounts, Config{
3142
PKI: pki,
3243
Session: SessionConfig{
33-
LifetimeSeconds: int64((7 * 24 * time.Hour).Seconds()),
44+
LifetimeSeconds: lifetime,
3445
Issuer: fmt.Sprintf("https://%s", ctx.Config.Domain),
46+
Cookie: CookieConfig{
47+
// Caution: changing these have security implications for the application. Make sure you understand what you're doing.
48+
Name: cookieNameFromDomain(ctx.Config.Domain),
49+
MaxAge: lifetime,
50+
SameSite: "lax",
51+
Secure: true,
52+
HTTPOnly: true,
53+
},
3554
},
3655
}
3756
}
57+
58+
func cookieNameFromDomain(domain string) string {
59+
// replace all non-word characters with underscores
60+
derived := regexp.MustCompile(`[\W_]+`).ReplaceAllString(domain, "_")
61+
return "_" + derived + "_jwt_"
62+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package auth
6+
7+
import "testing"
8+
9+
func TestCookieNameFromDomain(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
domain string
13+
expectedOutcome string
14+
}{
15+
{
16+
name: "Simple Domain",
17+
domain: "example.com",
18+
expectedOutcome: "_example_com_jwt_",
19+
},
20+
{
21+
name: "Domain with Underscore",
22+
domain: "example_test.com",
23+
expectedOutcome: "_example_test_com_jwt_",
24+
},
25+
{
26+
name: "Domain with Hyphen",
27+
domain: "example-test.com",
28+
expectedOutcome: "_example_test_com_jwt_",
29+
},
30+
{
31+
name: "Domain with Special Characters",
32+
domain: "example&test.com",
33+
expectedOutcome: "_example_test_com_jwt_",
34+
},
35+
{
36+
name: "Subdomain",
37+
domain: "subdomain.example.com",
38+
expectedOutcome: "_subdomain_example_com_jwt_",
39+
},
40+
{
41+
name: "Subdomain with Hyphen",
42+
domain: "sub-domain.example.com",
43+
expectedOutcome: "_sub_domain_example_com_jwt_",
44+
},
45+
{
46+
name: "Subdomain with Underscore",
47+
domain: "sub_domain.example.com",
48+
expectedOutcome: "_sub_domain_example_com_jwt_",
49+
},
50+
{
51+
name: "Subdomain with Special Characters",
52+
domain: "sub&domain.example.com",
53+
expectedOutcome: "_sub_domain_example_com_jwt_",
54+
},
55+
}
56+
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
actual := cookieNameFromDomain(tt.domain)
60+
if actual != tt.expectedOutcome {
61+
t.Errorf("expected %q, got %q", tt.expectedOutcome, actual)
62+
}
63+
})
64+
}
65+
}

install/installer/pkg/components/public-api-server/configmap.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ func configmap(ctx *common.RenderContext) ([]runtime.Object, error) {
7474
Session: config.SessionConfig{
7575
LifetimeSeconds: authCfg.Session.LifetimeSeconds,
7676
Issuer: authCfg.Session.Issuer,
77+
Cookie: config.CookieConfig{
78+
Name: authCfg.Session.Cookie.Name,
79+
MaxAge: authCfg.Session.Cookie.MaxAge,
80+
SameSite: authCfg.Session.Cookie.SameSite,
81+
Secure: authCfg.Session.Cookie.Secure,
82+
HTTPOnly: authCfg.Session.Cookie.HTTPOnly,
83+
},
7784
},
7885
},
7986
Server: &baseserver.Configuration{

install/installer/pkg/components/public-api-server/configmap_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ func TestConfigMap(t *testing.T) {
6969
Session: config.SessionConfig{
7070
LifetimeSeconds: int64((24 * 7 * time.Hour).Seconds()),
7171
Issuer: "https://test.domain.everything.awesome.is",
72+
Cookie: config.CookieConfig{
73+
Name: "_test_domain_everything_awesome_is_jwt_",
74+
MaxAge: int64((24 * 7 * time.Hour).Seconds()),
75+
SameSite: "lax",
76+
Secure: true,
77+
HTTPOnly: true,
78+
},
7279
},
7380
},
7481
Server: &baseserver.Configuration{

install/installer/pkg/components/server/configmap_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ func TestConfigMap(t *testing.T) {
6666
Session: auth.SessionConfig{
6767
LifetimeSeconds: int64((7 * 24 * time.Hour).Seconds()),
6868
Issuer: "https://awesome.domain",
69+
Cookie: auth.CookieConfig{
70+
Name: "_awesome_domain_jwt_",
71+
MaxAge: int64((7 * 24 * time.Hour).Seconds()),
72+
SameSite: "lax",
73+
Secure: true,
74+
HTTPOnly: true,
75+
},
6976
},
7077
},
7178
}

0 commit comments

Comments
 (0)