Skip to content

chore: add workspace proxies to the backend #7032

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Apr 17, 2023
Merged
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ca5b50c
feat: Implement start of external workspace proxies
Emyrk Apr 5, 2023
5fc7832
Add more init code
Emyrk Apr 5, 2023
391fe74
feat: add proxysdk and proxy tokeng
deansheather Apr 6, 2023
23d0a4c
Comments and import cleanup
Emyrk Apr 6, 2023
7cce9a2
Move to wsproxy, make unit test work, update audit log resources
Emyrk Apr 6, 2023
2aebe77
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 6, 2023
020b4b5
Add proxy token provider
Emyrk Apr 6, 2023
dc5af55
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 6, 2023
c5225ae
Begin writing unit test for external proxy
Emyrk Apr 6, 2023
d6a1217
Add option validation
Emyrk Apr 7, 2023
1e163d9
Fix access url passing
Emyrk Apr 7, 2023
e86a518
Healthz and buildinfo endpoints
Emyrk Apr 7, 2023
20b44c6
do stuff
deansheather Apr 11, 2023
68c3bb1
Linting
Emyrk Apr 11, 2023
ec04552
Check workspace proxy hostnames for subdomain apps
Emyrk Apr 11, 2023
07323e5
Path based redirects redirect to dashboardurl
Emyrk Apr 11, 2023
ffa8b00
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 11, 2023
a96a73b
Just commit something
Emyrk Apr 11, 2023
2d7e242
use query instead of proxycache
deansheather Apr 12, 2023
e80e7e0
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 12, 2023
be25c51
Make gen
Emyrk Apr 12, 2023
208eaf1
MAke gen
Emyrk Apr 12, 2023
6cfb62c
Linting
Emyrk Apr 12, 2023
a112e29
Bump migration
Emyrk Apr 13, 2023
d6edd29
Smuggling for path apps on proxies
deansheather Apr 13, 2023
5db3d25
Reuse system rbac subject
Emyrk Apr 13, 2023
7e4ed87
Add TODO
Emyrk Apr 13, 2023
7140420
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 13, 2023
fb30e1a
Give moons exec perms
Emyrk Apr 13, 2023
22aadf1
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 13, 2023
a66ffd7
Merge remote-tracking branch 'origin/main' into dreamteam/external_proxy
Emyrk Apr 14, 2023
a483f3e
Fix merge mistake
Emyrk Apr 14, 2023
50fa1ca
Renames from PR feedback
Emyrk Apr 14, 2023
b7f3b86
Update enterprise/audit/table.go
Emyrk Apr 17, 2023
bb032c3
Renames and formatting
Emyrk Apr 17, 2023
6ab0dea
Make gen
Emyrk Apr 17, 2023
12c6f8d
Fix compile
Emyrk Apr 17, 2023
224fa2f
Add comments to sql columns
Emyrk Apr 17, 2023
06fb88b
ExternalProxy -> WorkspaceProxy
Emyrk Apr 17, 2023
82d10d9
Remove Actor function
Emyrk Apr 17, 2023
dc884eb
comments
deansheather Apr 17, 2023
dbbd2ba
comments
deansheather Apr 17, 2023
1322f99
Use correct MW
Emyrk Apr 17, 2023
784fb68
Make gen/fmt/lint
Emyrk Apr 17, 2023
b72ef2f
Group vs route to fix swagger
Emyrk Apr 17, 2023
a4f205e
comments
deansheather Apr 17, 2023
8508138
comments
deansheather Apr 17, 2023
cfe484c
comments
deansheather Apr 17, 2023
d4d9bf9
tests for RequireAPIKeyOrWorkspaceProxyAuth
deansheather Apr 17, 2023
fdbd31e
tests for ExtractWorkspaceProxy
deansheather Apr 17, 2023
efef018
Merge branch 'main' into dreamteam/external_proxy
deansheather Apr 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Implement start of external workspace proxies
  • Loading branch information
Emyrk committed Apr 5, 2023
commit ca5b50c785000d24f5397a5f48711d8b78c20195
161 changes: 161 additions & 0 deletions enterprise/externalproxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package externalproxy

import (
"net/http"
"net/url"
"regexp"
"time"

"github.com/coder/coder/buildinfo"

"github.com/prometheus/client_golang/prometheus"

"github.com/coder/coder/coderd/tracing"
"go.opentelemetry.io/otel/trace"

"github.com/go-chi/chi/v5"

"github.com/coder/coder/coderd/wsconncache"

"github.com/coder/coder/coderd/httpmw"

"cdr.dev/slog"
"github.com/coder/coder/coderd/workspaceapps"
)

type Options struct {
Logger slog.Logger

// PrimaryAccessURL is the URL of the primary coderd instance.
// This also serves as the DashboardURL.
PrimaryAccessURL *url.URL
// AccessURL is the URL of the WorkspaceProxy. This is the url to communicate
// with this server.
AccessURL *url.URL

// TODO: @emyrk We use these two fields in many places with this comment.
// Maybe we should make some shared options struct?
// AppHostname should be the wildcard hostname to use for workspace
// applications INCLUDING the asterisk, (optional) suffix and leading dot.
// It will use the same scheme and port number as the access URL.
// E.g. "*.apps.coder.com" or "*-apps.coder.com".
AppHostname string
// AppHostnameRegex contains the regex version of options.AppHostname as
// generated by httpapi.CompileHostnamePattern(). It MUST be set if
// options.AppHostname is set.
AppHostnameRegex *regexp.Regexp

RealIPConfig *httpmw.RealIPConfig
// TODO: @emyrk this key needs to be provided via a file or something?
// Maybe we should curl it from the primary over some secure connection?
AppSecurityKey workspaceapps.SecurityKey

Tracing trace.TracerProvider
PrometheusRegistry *prometheus.Registry

APIRateLimit int
SecureAuthCookie bool
}

// Server is an external workspace proxy server. This server can communicate
// directly with a workspace. It requires a primary coderd to establish a said
// connection.
type Server struct {
PrimaryAccessURL *url.URL
AppServer *workspaceapps.Server

// Logging/Metrics
Logger slog.Logger
TracerProvider trace.TracerProvider
PrometheusRegistry *prometheus.Registry

Handler chi.Router

// TODO: Missing:
// - derpserver

Options *Options
}

func New(opts *Options) *Server {
if opts.PrometheusRegistry == nil {
opts.PrometheusRegistry = prometheus.NewRegistry()
}

r := chi.NewRouter()
s := &Server{
Options: opts,
PrimaryAccessURL: opts.PrimaryAccessURL,
AppServer: &workspaceapps.Server{
Logger: opts.Logger.Named("workspaceapps"),
DashboardURL: opts.PrimaryAccessURL,
AccessURL: opts.AccessURL,
Hostname: opts.AppHostname,
HostnameRegex: opts.AppHostnameRegex,
// TODO: @emyrk We should reduce the options passed in here.
DeploymentValues: nil,
RealIPConfig: opts.RealIPConfig,
// TODO: @emyrk we need to implement this for external token providers.
SignedTokenProvider: nil,
// TODO: @emyrk we need to implement a dialer
WorkspaceConnCache: wsconncache.New(nil, 0),
AppSecurityKey: opts.AppSecurityKey,
},
Logger: opts.Logger.Named("workspace-proxy"),
TracerProvider: opts.Tracing,
PrometheusRegistry: opts.PrometheusRegistry,
Handler: r,
}

// Routes
apiRateLimiter := httpmw.RateLimit(opts.APIRateLimit, time.Minute)
// Persistant middlewares to all routes
r.Use(
// TODO: @emyrk Should we standardize these in some other package?
httpmw.Recover(s.Logger),
tracing.StatusWriterMiddleware,
tracing.Middleware(s.TracerProvider),
httpmw.AttachRequestID,
httpmw.ExtractRealIP(s.Options.RealIPConfig),
httpmw.Logger(s.Logger),
httpmw.Prometheus(s.PrometheusRegistry),

// SubdomainAppMW is a middleware that handles all requests to the
// subdomain based workspace apps.
s.AppServer.SubdomainAppMW(apiRateLimiter),
// Build-Version is helpful for debugging.
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Coder-Build-Version", buildinfo.Version())
next.ServeHTTP(w, r)
})
},
// This header stops a browser from trying to MIME-sniff the content type and
// forces it to stick with the declared content-type. This is the only valid
// value for this header.
// See: https://github.com/coder/security/issues/12
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Content-Type-Options", "nosniff")
next.ServeHTTP(w, r)
})
},
// TODO: @emyrk we might not need this? But good to have if it does
// not break anything.
httpmw.CSRF(s.Options.SecureAuthCookie),
)

// Attach workspace apps routes.
r.Group(func(r chi.Router) {
r.Use(apiRateLimiter)
s.AppServer.Attach(r)
})

// TODO: @emyrk Buildinfo and healthz routes.

return s
}

func (s *Server) Close() error {
return s.AppServer.Close()
}