mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
chore: add workspace proxies to the backend (#7032)
Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -18,10 +19,13 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/util/slice"
|
||||
)
|
||||
|
||||
var validProxyByHostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9.-]+$`)
|
||||
|
||||
// FakeDatabase is helpful for knowing if the underlying db is an in memory fake
|
||||
// database. This is only in the databasefake package, so will only be used
|
||||
// by unit tests.
|
||||
@ -5093,6 +5097,40 @@ func (q *fakeQuerier) GetWorkspaceProxyByID(_ context.Context, id uuid.UUID) (da
|
||||
return database.WorkspaceProxy{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceProxyByHostname(_ context.Context, hostname string) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
// Return zero rows if this is called with a non-sanitized hostname. The SQL
|
||||
// version of this query does the same thing.
|
||||
if !validProxyByHostnameRegex.MatchString(hostname) {
|
||||
return database.WorkspaceProxy{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
// This regex matches the SQL version.
|
||||
accessURLRegex := regexp.MustCompile(`[^:]*://` + regexp.QuoteMeta(hostname) + `([:/]?.)*`)
|
||||
|
||||
for _, proxy := range q.workspaceProxies {
|
||||
if proxy.Deleted {
|
||||
continue
|
||||
}
|
||||
if accessURLRegex.MatchString(proxy.Url) {
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
// Compile the app hostname regex. This is slow sadly.
|
||||
wildcardRegexp, err := httpapi.CompileHostnamePattern(proxy.WildcardHostname)
|
||||
if err != nil {
|
||||
return database.WorkspaceProxy{}, xerrors.Errorf("compile hostname pattern %q for proxy %q (%s): %w", proxy.WildcardHostname, proxy.Name, proxy.ID.String(), err)
|
||||
}
|
||||
if _, ok := httpapi.ExecuteHostnamePattern(wildcardRegexp, hostname); ok {
|
||||
return proxy, nil
|
||||
}
|
||||
}
|
||||
|
||||
return database.WorkspaceProxy{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertWorkspaceProxy(_ context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@ -5104,14 +5142,16 @@ func (q *fakeQuerier) InsertWorkspaceProxy(_ context.Context, arg database.Inser
|
||||
}
|
||||
|
||||
p := database.WorkspaceProxy{
|
||||
ID: arg.ID,
|
||||
Name: arg.Name,
|
||||
Icon: arg.Icon,
|
||||
Url: arg.Url,
|
||||
WildcardHostname: arg.WildcardHostname,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
Deleted: false,
|
||||
ID: arg.ID,
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
Icon: arg.Icon,
|
||||
Url: arg.Url,
|
||||
WildcardHostname: arg.WildcardHostname,
|
||||
TokenHashedSecret: arg.TokenHashedSecret,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
Deleted: false,
|
||||
}
|
||||
q.workspaceProxies = append(q.workspaceProxies, p)
|
||||
return p, nil
|
||||
|
@ -129,6 +129,96 @@ func TestUserOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyByHostname(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := dbfake.New()
|
||||
|
||||
// Insert a bunch of different proxies.
|
||||
proxies := []struct {
|
||||
name string
|
||||
accessURL string
|
||||
wildcardHostname string
|
||||
}{
|
||||
{
|
||||
name: "one",
|
||||
accessURL: "https://one.coder.com",
|
||||
wildcardHostname: "*.wildcard.one.coder.com",
|
||||
},
|
||||
{
|
||||
name: "two",
|
||||
accessURL: "https://two.coder.com",
|
||||
wildcardHostname: "*--suffix.two.coder.com",
|
||||
},
|
||||
}
|
||||
for _, p := range proxies {
|
||||
dbgen.WorkspaceProxy(t, db, database.WorkspaceProxy{
|
||||
Name: p.name,
|
||||
Url: p.accessURL,
|
||||
WildcardHostname: p.wildcardHostname,
|
||||
})
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
testHostname string
|
||||
matchProxyName string
|
||||
}{
|
||||
{
|
||||
name: "NoMatch",
|
||||
testHostname: "test.com",
|
||||
matchProxyName: "",
|
||||
},
|
||||
{
|
||||
name: "MatchAccessURL",
|
||||
testHostname: "one.coder.com",
|
||||
matchProxyName: "one",
|
||||
},
|
||||
{
|
||||
name: "MatchWildcard",
|
||||
testHostname: "something.wildcard.one.coder.com",
|
||||
matchProxyName: "one",
|
||||
},
|
||||
{
|
||||
name: "MatchSuffix",
|
||||
testHostname: "something--suffix.two.coder.com",
|
||||
matchProxyName: "two",
|
||||
},
|
||||
{
|
||||
name: "ValidateHostname/1",
|
||||
testHostname: ".*ne.coder.com",
|
||||
matchProxyName: "",
|
||||
},
|
||||
{
|
||||
name: "ValidateHostname/2",
|
||||
testHostname: "https://one.coder.com",
|
||||
matchProxyName: "",
|
||||
},
|
||||
{
|
||||
name: "ValidateHostname/3",
|
||||
testHostname: "one.coder.com:8080/hello",
|
||||
matchProxyName: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
proxy, err := db.GetWorkspaceProxyByHostname(context.Background(), c.testHostname)
|
||||
if c.matchProxyName == "" {
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
require.Empty(t, proxy)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, proxy)
|
||||
require.Equal(t, c.matchProxyName, proxy.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func methods(rt reflect.Type) map[string]bool {
|
||||
methods := make(map[string]bool)
|
||||
for i := 0; i < rt.NumMethod(); i++ {
|
||||
|
Reference in New Issue
Block a user