Skip to content

Commit 79af7b9

Browse files
Copilotmatifali
andcommitted
Implement hostname length validation for Coder workspace apps
Co-authored-by: matifali <10648092+matifali@users.noreply.github.com>
1 parent f306379 commit 79af7b9

File tree

2 files changed

+150
-2
lines changed

2 files changed

+150
-2
lines changed

coderd/database/db2sdk/db2sdk.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,23 @@ func AppSubdomain(dbApp database.WorkspaceApp, agentName, workspaceName, ownerNa
539539
}.String()
540540
}
541541

542+
// validateAppHostnameLength checks if any segment of the app hostname exceeds the DNS label limit of 63 characters.
543+
// Returns true if the hostname is valid, false if any segment is too long.
544+
func validateAppHostnameLength(subdomainName string) bool {
545+
if subdomainName == "" {
546+
return true
547+
}
548+
549+
// Split the hostname into segments by '--' (the format is app--agent--workspace--user)
550+
segments := strings.Split(subdomainName, "--")
551+
for _, segment := range segments {
552+
if len(segment) > 63 {
553+
return false
554+
}
555+
}
556+
return true
557+
}
558+
542559
func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) []codersdk.WorkspaceApp {
543560
sort.Slice(dbApps, func(i, j int) bool {
544561
if dbApps[i].DisplayOrder != dbApps[j].DisplayOrder {
@@ -558,6 +575,15 @@ func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus
558575
apps := make([]codersdk.WorkspaceApp, 0)
559576
for _, dbApp := range dbApps {
560577
statuses := statusesByAppID[dbApp.ID]
578+
subdomainName := AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName)
579+
appHealth := codersdk.WorkspaceAppHealth(dbApp.Health)
580+
581+
// Check if this is a subdomain app with hostname length issues
582+
if dbApp.Subdomain && subdomainName != "" && !validateAppHostnameLength(subdomainName) {
583+
// Override health to unhealthy if hostname exceeds DNS limits
584+
appHealth = codersdk.WorkspaceAppHealthUnhealthy
585+
}
586+
561587
apps = append(apps, codersdk.WorkspaceApp{
562588
ID: dbApp.ID,
563589
URL: dbApp.Url.String,
@@ -567,14 +593,14 @@ func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus
567593
Command: dbApp.Command.String,
568594
Icon: dbApp.Icon,
569595
Subdomain: dbApp.Subdomain,
570-
SubdomainName: AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName),
596+
SubdomainName: subdomainName,
571597
SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel),
572598
Healthcheck: codersdk.Healthcheck{
573599
URL: dbApp.HealthcheckUrl,
574600
Interval: dbApp.HealthcheckInterval,
575601
Threshold: dbApp.HealthcheckThreshold,
576602
},
577-
Health: codersdk.WorkspaceAppHealth(dbApp.Health),
603+
Health: appHealth,
578604
Group: dbApp.DisplayGroup.String,
579605
Hidden: dbApp.Hidden,
580606
OpenIn: codersdk.WorkspaceAppOpenIn(dbApp.OpenIn),
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package db2sdk
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/uuid"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/coder/coder/v2/coderd/database"
10+
"github.com/coder/coder/v2/codersdk"
11+
)
12+
13+
func TestValidateAppHostnameLength(t *testing.T) {
14+
t.Parallel()
15+
16+
tests := []struct {
17+
name string
18+
subdomainName string
19+
expected bool
20+
}{
21+
{
22+
name: "empty hostname",
23+
subdomainName: "",
24+
expected: true,
25+
},
26+
{
27+
name: "valid short hostname",
28+
subdomainName: "app--agent--workspace--user",
29+
expected: true,
30+
},
31+
{
32+
name: "valid hostname with max length segment",
33+
subdomainName: "a12345678901234567890123456789012345678901234567890123456789012--agent--workspace--user", // 63 chars in first segment
34+
expected: true,
35+
},
36+
{
37+
name: "invalid hostname with long app name",
38+
subdomainName: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail--agent--workspace--user", // 78 chars in first segment
39+
expected: false,
40+
},
41+
{
42+
name: "invalid hostname with long agent name",
43+
subdomainName: "app--toolongagentnamethatexceedsthednslimitof63charactersforsureandshouldfail--workspace--user", // 72 chars in agent segment
44+
expected: false,
45+
},
46+
{
47+
name: "invalid hostname with long workspace name",
48+
subdomainName: "app--agent--toolongworkspacenamethatexceedsthednslimitof63charactersforsureandshouldfail--user", // 77 chars in workspace segment
49+
expected: false,
50+
},
51+
{
52+
name: "invalid hostname with long username",
53+
subdomainName: "app--agent--workspace--toolongusernamethatexceedsthednslimitof63charactersforsureandshouldfail", // 72 chars in username segment
54+
expected: false,
55+
},
56+
}
57+
58+
for _, tt := range tests {
59+
t.Run(tt.name, func(t *testing.T) {
60+
result := validateAppHostnameLength(tt.subdomainName)
61+
require.Equal(t, tt.expected, result)
62+
})
63+
}
64+
}
65+
66+
func TestAppsWithHostnameLengthValidation(t *testing.T) {
67+
t.Parallel()
68+
69+
agent := database.WorkspaceAgent{
70+
ID: uuid.New(),
71+
Name: "agent",
72+
}
73+
workspace := database.Workspace{
74+
ID: uuid.New(),
75+
Name: "workspace",
76+
}
77+
ownerName := "user"
78+
79+
tests := []struct {
80+
name string
81+
appSlug string
82+
subdomain bool
83+
expectedHealth codersdk.WorkspaceAppHealth
84+
}{
85+
{
86+
name: "non-subdomain app should not be affected",
87+
appSlug: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail",
88+
subdomain: false,
89+
expectedHealth: codersdk.WorkspaceAppHealthHealthy,
90+
},
91+
{
92+
name: "short subdomain app should remain healthy",
93+
appSlug: "app",
94+
subdomain: true,
95+
expectedHealth: codersdk.WorkspaceAppHealthHealthy,
96+
},
97+
{
98+
name: "long subdomain app should become unhealthy",
99+
appSlug: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail",
100+
subdomain: true,
101+
expectedHealth: codersdk.WorkspaceAppHealthUnhealthy,
102+
},
103+
}
104+
105+
for _, tt := range tests {
106+
t.Run(tt.name, func(t *testing.T) {
107+
dbApps := []database.WorkspaceApp{
108+
{
109+
ID: uuid.New(),
110+
Slug: tt.appSlug,
111+
DisplayName: "Test App",
112+
Subdomain: tt.subdomain,
113+
Health: database.WorkspaceAppHealthHealthy, // Start as healthy
114+
},
115+
}
116+
117+
apps := Apps(dbApps, []database.WorkspaceAppStatus{}, agent, ownerName, workspace)
118+
require.Len(t, apps, 1)
119+
require.Equal(t, tt.expectedHealth, apps[0].Health)
120+
})
121+
}
122+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy