Files
coder/codersdk/deploymentconfig.go
Dean Sheather 0374af23b2 fix(security)!: path-based app sharing changes (#5772)
This commit disables path-based app sharing by default. It is possible
for a workspace app on a path (not a subdomain) to make API requests to
the Coder API. When accessing your own workspace, this is not much of a
problem. When accessing a shared workspace app, the workspace owner
could include malicious javascript in the page that makes requests to
the Coder API on behalf of the visitor.

This vulnerability does not affect subdomain apps.

- Disables path-based app sharing by default. Previous behavior can be
  restored using the `--dangerous-allow-path-app-sharing` flag which is
  not recommended.

- Disables users with the site "owner" role from accessing path-based
  apps from workspaces they do not own. Previous behavior can be
  restored using the `--dangerous-allow-path-app-site-owner-access` flag
  which is not recommended.

- Adds a flag `--disable-path-apps` which can be used by
  security-conscious admins to disable all path-based apps across the
  entire deployment. This check is enforced at app-access time, not at
  template-ingest time.
2023-01-18 22:56:14 +00:00

243 lines
13 KiB
Go

package codersdk
import (
"context"
"encoding/json"
"net/http"
"time"
"golang.org/x/xerrors"
)
// DeploymentConfig is the central configuration for the coder server.
type DeploymentConfig struct {
AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"`
WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"`
HTTPAddress *DeploymentConfigField[string] `json:"http_address" typescript:",notnull"`
AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"`
DERP *DERP `json:"derp" typescript:",notnull"`
GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"`
Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"`
Pprof *PprofConfig `json:"pprof" typescript:",notnull"`
ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"`
ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"`
CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"`
InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"`
PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"`
OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"`
OIDC *OIDCConfig `json:"oidc" typescript:",notnull"`
Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"`
TLS *TLSConfig `json:"tls" typescript:",notnull"`
Trace *TraceConfig `json:"trace" typescript:",notnull"`
SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"`
SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"`
MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"`
AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"`
AgentFallbackTroubleshootingURL *DeploymentConfigField[string] `json:"agent_fallback_troubleshooting_url" typescript:",notnull"`
AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"`
BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"`
SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"`
Provisioner *ProvisionerConfig `json:"provisioner" typescript:",notnull"`
RateLimit *RateLimitConfig `json:"rate_limit" typescript:",notnull"`
Experiments *DeploymentConfigField[[]string] `json:"experiments" typescript:",notnull"`
UpdateCheck *DeploymentConfigField[bool] `json:"update_check" typescript:",notnull"`
MaxTokenLifetime *DeploymentConfigField[time.Duration] `json:"max_token_lifetime" typescript:",notnull"`
Swagger *SwaggerConfig `json:"swagger" typescript:",notnull"`
Logging *LoggingConfig `json:"logging" typescript:",notnull"`
Dangerous *DangerousConfig `json:"dangerous" typescript:",notnull"`
DisablePathApps *DeploymentConfigField[bool] `json:"disable_path_apps" typescript:",notnull"`
// DEPRECATED: Use HTTPAddress or TLS.Address instead.
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
// DEPRECATED: Use Experiments instead.
Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
}
type DERP struct {
Server *DERPServerConfig `json:"server" typescript:",notnull"`
Config *DERPConfig `json:"config" typescript:",notnull"`
}
type DERPServerConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
RegionID *DeploymentConfigField[int] `json:"region_id" typescript:",notnull"`
RegionCode *DeploymentConfigField[string] `json:"region_code" typescript:",notnull"`
RegionName *DeploymentConfigField[string] `json:"region_name" typescript:",notnull"`
STUNAddresses *DeploymentConfigField[[]string] `json:"stun_addresses" typescript:",notnull"`
RelayURL *DeploymentConfigField[string] `json:"relay_url" typescript:",notnull"`
}
type DERPConfig struct {
URL *DeploymentConfigField[string] `json:"url" typescript:",notnull"`
Path *DeploymentConfigField[string] `json:"path" typescript:",notnull"`
}
type PrometheusConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
}
type PprofConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
}
type OAuth2Config struct {
Github *OAuth2GithubConfig `json:"github" typescript:",notnull"`
}
type OAuth2GithubConfig struct {
ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"`
ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"`
AllowedOrgs *DeploymentConfigField[[]string] `json:"allowed_orgs" typescript:",notnull"`
AllowedTeams *DeploymentConfigField[[]string] `json:"allowed_teams" typescript:",notnull"`
AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"`
AllowEveryone *DeploymentConfigField[bool] `json:"allow_everyone" typescript:",notnull"`
EnterpriseBaseURL *DeploymentConfigField[string] `json:"enterprise_base_url" typescript:",notnull"`
}
type OIDCConfig struct {
AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"`
ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"`
ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"`
EmailDomain *DeploymentConfigField[[]string] `json:"email_domain" typescript:",notnull"`
IssuerURL *DeploymentConfigField[string] `json:"issuer_url" typescript:",notnull"`
Scopes *DeploymentConfigField[[]string] `json:"scopes" typescript:",notnull"`
IgnoreEmailVerified *DeploymentConfigField[bool] `json:"ignore_email_verified" typescript:",notnull"`
UsernameField *DeploymentConfigField[string] `json:"username_field" typescript:",notnull"`
}
type TelemetryConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
Trace *DeploymentConfigField[bool] `json:"trace" typescript:",notnull"`
URL *DeploymentConfigField[string] `json:"url" typescript:",notnull"`
}
type TLSConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
RedirectHTTP *DeploymentConfigField[bool] `json:"redirect_http" typescript:",notnull"`
CertFiles *DeploymentConfigField[[]string] `json:"cert_file" typescript:",notnull"`
ClientAuth *DeploymentConfigField[string] `json:"client_auth" typescript:",notnull"`
ClientCAFile *DeploymentConfigField[string] `json:"client_ca_file" typescript:",notnull"`
KeyFiles *DeploymentConfigField[[]string] `json:"key_file" typescript:",notnull"`
MinVersion *DeploymentConfigField[string] `json:"min_version" typescript:",notnull"`
ClientCertFile *DeploymentConfigField[string] `json:"client_cert_file" typescript:",notnull"`
ClientKeyFile *DeploymentConfigField[string] `json:"client_key_file" typescript:",notnull"`
}
type TraceConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
HoneycombAPIKey *DeploymentConfigField[string] `json:"honeycomb_api_key" typescript:",notnull"`
CaptureLogs *DeploymentConfigField[bool] `json:"capture_logs" typescript:",notnull"`
}
type GitAuthConfig struct {
ID string `json:"id"`
Type string `json:"type"`
ClientID string `json:"client_id"`
ClientSecret string `json:"-" yaml:"client_secret"`
AuthURL string `json:"auth_url"`
TokenURL string `json:"token_url"`
ValidateURL string `json:"validate_url"`
Regex string `json:"regex"`
NoRefresh bool `json:"no_refresh"`
Scopes []string `json:"scopes"`
}
type ProvisionerConfig struct {
Daemons *DeploymentConfigField[int] `json:"daemons" typescript:",notnull"`
DaemonPollInterval *DeploymentConfigField[time.Duration] `json:"daemon_poll_interval" typescript:",notnull"`
DaemonPollJitter *DeploymentConfigField[time.Duration] `json:"daemon_poll_jitter" typescript:",notnull"`
ForceCancelInterval *DeploymentConfigField[time.Duration] `json:"force_cancel_interval" typescript:",notnull"`
}
type RateLimitConfig struct {
DisableAll *DeploymentConfigField[bool] `json:"disable_all" typescript:",notnull"`
API *DeploymentConfigField[int] `json:"api" typescript:",notnull"`
}
type SwaggerConfig struct {
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
}
type LoggingConfig struct {
Human *DeploymentConfigField[string] `json:"human" typescript:",notnull"`
JSON *DeploymentConfigField[string] `json:"json" typescript:",notnull"`
Stackdriver *DeploymentConfigField[string] `json:"stackdriver" typescript:",notnull"`
}
type DangerousConfig struct {
AllowPathAppSharing *DeploymentConfigField[bool] `json:"allow_path_app_sharing" typescript:",notnull"`
AllowPathAppSiteOwnerAccess *DeploymentConfigField[bool] `json:"allow_path_app_site_owner_access" typescript:",notnull"`
}
type Flaggable interface {
string | time.Duration | bool | int | []string | []GitAuthConfig
}
type DeploymentConfigField[T Flaggable] struct {
Name string `json:"name"`
Usage string `json:"usage"`
Flag string `json:"flag"`
// EnvOverride will override the automatically generated environment
// variable name. Useful if you're moving values around but need to keep
// backwards compatibility with old environment variable names.
//
// NOTE: this is not supported for array flags.
EnvOverride string `json:"-"`
Shorthand string `json:"shorthand"`
Enterprise bool `json:"enterprise"`
Hidden bool `json:"hidden"`
Secret bool `json:"secret"`
Default T `json:"default"`
Value T `json:"value"`
}
// MarshalJSON removes the Value field from the JSON output of any fields marked Secret.
// nolint:revive
func (f *DeploymentConfigField[T]) MarshalJSON() ([]byte, error) {
copy := struct {
Name string `json:"name"`
Usage string `json:"usage"`
Flag string `json:"flag"`
Shorthand string `json:"shorthand"`
Enterprise bool `json:"enterprise"`
Hidden bool `json:"hidden"`
Secret bool `json:"secret"`
Default T `json:"default"`
Value T `json:"value"`
}{
Name: f.Name,
Usage: f.Usage,
Flag: f.Flag,
Shorthand: f.Shorthand,
Enterprise: f.Enterprise,
Hidden: f.Hidden,
Secret: f.Secret,
}
if !f.Secret {
copy.Default = f.Default
copy.Value = f.Value
}
return json.Marshal(copy)
}
// DeploymentConfig returns the deployment config for the coder server.
func (c *Client) DeploymentConfig(ctx context.Context) (DeploymentConfig, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/config/deployment", nil)
if err != nil {
return DeploymentConfig{}, xerrors.Errorf("execute request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return DeploymentConfig{}, readBodyAsError(res)
}
var df DeploymentConfig
return df, json.NewDecoder(res.Body).Decode(&df)
}