Compare commits

...

13 Commits

Author SHA1 Message Date
Colin Adler 2d37eb42e7 Merge pull request from GHSA-7cc2-r658-7xpf
This fixes a vulnerability with the `CODER_OIDC_EMAIL_DOMAIN` option,
where users with a superset of the allowed email domain would be allowed
to login. For example, given `CODER_OIDC_EMAIL_DOMAIN=google.com`, a
user would be permitted entry if their email domain was
`colin-google.com`.

(cherry picked from commit 4439a920e4)
2024-03-04 18:46:34 +00:00
Colin Adler e7033b34dc chore: add patch notes for v2.8.4 2024-03-04 18:46:22 +00:00
Ben 01c6266e3e docs: add v2.8.3 changelog 2024-02-15 17:20:43 +00:00
Spike Curtis f9011dcba2 feat: expose DERP server debug metrics (#12135) (#12151)
Adds some debug endpoints for looking into the DERP server.

The `api/v2/debug/derp/traffic` endpoint requires the `ss` utility to be present in order to function.  I have *not* added the `iproute2` package to our base image as it adds 11MB, so this endpoint won't be useful by default.  However, in a debugging situation, we could exec into the container and then `apk add iproute2`, or build a special debug image.

The `api/v2/debug/expvar` handler contains DERP metrics as well as commandline and memstats.

Example:

```
{
"alert_failed": 0,
"alert_generated": 0,
"cmdline": ["/Users/spike/repos/coder/build/coder_darwin_arm64","--global-config","/Users/spike/repos/coder/.coderv2","server","--http-address","0.0.0.0:3000","--swagger-enable","--access-url","http://127.0.0.1:3000","--dangerous-allow-cors-requests=true"],
"derp": {"accepts": 1, "average_queue_duration_ms": 0, "bytes_received": 0, "bytes_sent": 0, "counter_packets_dropped_reason": {"gone_disconnected": 0, "gone_not_here": 0, "queue_head": 0, "queue_tail": 0, "unknown_dest": 0, "unknown_dest_on_fwd": 0, "write_error": 0}, "counter_packets_dropped_type": {"disco": 0, "other": 0}, "counter_packets_received_kind": {"disco": 0, "other": 0}, "counter_tcp_rtt": {}, "counter_total_dup_client_conns": 0, "gauge_clients_local": 1, "gauge_clients_remote": 0, "gauge_clients_total": 1, "gauge_current_connections": 1, "gauge_current_dup_client_conns": 0, "gauge_current_dup_client_keys": 0, "gauge_current_file_descriptors": 0, "gauge_current_home_connections": 1, "gauge_memstats_sys0": 20874504, "gauge_watchers": 0, "got_ping": 0, "home_moves_in": 0, "home_moves_out": 0, "multiforwarder_created": 0, "multiforwarder_deleted": 0, "packet_forwarder_delete_other_value": 0, "packets_dropped": 0, "packets_forwarded_in": 0, "packets_forwarded_out": 0, "packets_received": 0, "packets_sent": 0, "peer_gone_disconnected_frames": 0, "peer_gone_not_here_frames": 0, "sent_pong": 0, "unknown_frames": 0, "version": "1.47.0-dev20240214-t64db8c604"},
"memstats": {"Alloc":286506256,"TotalAlloc":297594632,"Sys":310621512,"Lookups":0,"Mallocs":304204,"Frees":171570,"HeapAlloc":286506256,"HeapSys":294060032,"HeapIdle":3694592,"HeapInuse":290365440,"HeapReleased":3620864,"HeapObjects":132634,"StackInuse":3735552,"StackSys":3735552,"MSpanInuse":347256,"MSpanSys":358512,"MCacheInuse":9600,"MCacheSys":15600,"BuckHashSys":1469877,"GCSys":9434896,"OtherSys":1547043,"NextGC":551867656,"LastGC":1707892877408883000,"PauseTotalNs":1247000,"PauseNs":[200333,229375,239875,209542,106958,203792,57125,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1707892876217481000,1707892876219726000,1707892876222273000,1707892876226151000,1707892876234815000,1707892877398146000,1707892877408883000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":7,"NumForcedGC":0,"GCCPUFraction":0.0022425810335762954,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":14396,"Frees":9143},{"Size":16,"Mallocs":89090,"Frees":50507},{"Size":24,"Mallocs":40839,"Frees":24456},{"Size":32,"Mallocs":22404,"Frees":12379},{"Size":48,"Mallocs":51174,"Frees":23718},{"Size":64,"Mallocs":15406,"Frees":3501},{"Size":80,"Mallocs":6688,"Frees":2352},{"Size":96,"Mallocs":2567,"Frees":374},{"Size":112,"Mallocs":19371,"Frees":16883},{"Size":128,"Mallocs":2873,"Frees":1061},{"Size":144,"Mallocs":5600,"Frees":2742},{"Size":160,"Mallocs":2159,"Frees":622},{"Size":176,"Mallocs":454,"Frees":86},{"Size":192,"Mallocs":227,"Frees":128},{"Size":208,"Mallocs":1407,"Frees":732},{"Size":224,"Mallocs":1365,"Frees":1090},{"Size":240,"Mallocs":82,"Frees":48},{"Size":256,"Mallocs":310,"Frees":162},{"Size":288,"Mallocs":1945,"Frees":562},{"Size":320,"Mallocs":1200,"Frees":458},{"Size":352,"Mallocs":133,"Frees":33},{"Size":384,"Mallocs":582,"Frees":51},{"Size":416,"Mallocs":747,"Frees":200},{"Size":448,"Mallocs":113,"Frees":22},{"Size":480,"Mallocs":34,"Frees":21},{"Size":512,"Mallocs":951,"Frees":91},{"Size":576,"Mallocs":364,"Frees":122},{"Size":640,"Mallocs":532,"Frees":270},{"Size":704,"Mallocs":93,"Frees":39},{"Size":768,"Mallocs":83,"Frees":35},{"Size":896,"Mallocs":308,"Frees":175},{"Size":1024,"Mallocs":226,"Frees":122},{"Size":1152,"Mallocs":198,"Frees":100},{"Size":1280,"Mallocs":314,"Frees":171},{"Size":1408,"Mallocs":77,"Frees":47},{"Size":1536,"Mallocs":80,"Frees":54},{"Size":1792,"Mallocs":199,"Frees":107},{"Size":2048,"Mallocs":112,"Frees":48},{"Size":2304,"Mallocs":71,"Frees":32},{"Size":2688,"Mallocs":206,"Frees":81},{"Size":3072,"Mallocs":39,"Frees":15},{"Size":3200,"Mallocs":16,"Frees":7},{"Size":3456,"Mallocs":44,"Frees":29},{"Size":4096,"Mallocs":192,"Frees":83},{"Size":4864,"Mallocs":44,"Frees":25},{"Size":5376,"Mallocs":105,"Frees":43},{"Size":6144,"Mallocs":25,"Frees":5},{"Size":6528,"Mallocs":22,"Frees":7},{"Size":6784,"Mallocs":3,"Frees":0},{"Size":6912,"Mallocs":4,"Frees":2},{"Size":8192,"Mallocs":59,"Frees":10},{"Size":9472,"Mallocs":31,"Frees":12},{"Size":9728,"Mallocs":5,"Frees":2},{"Size":10240,"Mallocs":5,"Frees":0},{"Size":10880,"Mallocs":27,"Frees":11},{"Size":12288,"Mallocs":4,"Frees":1},{"Size":13568,"Mallocs":4,"Frees":2},{"Size":14336,"Mallocs":9,"Frees":2},{"Size":16384,"Mallocs":10,"Frees":2},{"Size":18432,"Mallocs":4,"Frees":2}]},
"warning_failed": 0,
"warning_generated": 0
}
```

If we find the DERP metrics useful we could consider how to include them in Prometheus scrapes based on the tailnet `varz` package.  That's for a later PR if at all.
2024-02-15 09:10:29 +04:00
Spike Curtis ae1be27ba6 fix: set node callback each time we reinit the coordinator in servertailnet (#12140) (#12150)
I think this will resolve #12136 but lets get a proper test at the system level before closing.

Before this change, we only register the node callback at start of day for the server tailnet.  If the coordinator changes, like we know happens when we are licensed for the PGCoordinator, we close the connection to the old coord, and open a new one to the new coord.

The callback is designed to direct the updates to the new coordinator, but there is nothing that specifically triggers it to fire after we connect to the new coordinator.

If we have STUN, then period re-STUNs will generally get it to fire eventually, but without STUN it we could go indefinitely without a callback.

This PR changes the servertailnet to re-register the callback each time we reconnect to the coordinator.  Registering a callback (even if it's the same callback) triggers an immediate call with our node information, so the new coordinator will have it.
2024-02-15 08:58:04 +04:00
Colin Adler c4a01a42ce chore(docs): add v2.8.2 changelog 2024-02-12 05:33:05 +00:00
Dean Sheather c0aeb2fc2e fix: copy app ID in healthcheck (#12087)
(cherry picked from commit 429144da22)
2024-02-12 05:30:34 +00:00
Bruno Quaresma 908d236a19 fix(site): enable submit when auto start and stop are both disabled (#12055) 2024-02-07 18:28:33 +00:00
Spike Curtis f519db88fb fix: allow startup scripts larger than 32k (#12060)
Fixes #12057 and adds a regression test.
2024-02-07 18:28:14 +00:00
Ben e996e8b7e8 fix typo 2024-02-07 00:10:37 +00:00
Ben da60671b33 formatting + add TF version 2024-02-07 00:08:19 +00:00
Ben 963a1404c0 fmt 2024-02-06 23:41:58 +00:00
Ben 002110228c docs: add v2.8.0 changelog 2024-02-06 23:39:03 +00:00
22 changed files with 536 additions and 33 deletions
+82
View File
@@ -397,6 +397,66 @@ const docTemplate = `{
}
}
},
"/debug/derp/traffic": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Debug"
],
"summary": "Debug DERP traffic",
"operationId": "debug-derp-traffic",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/derp.BytesSentRecv"
}
}
}
},
"x-apidocgen": {
"skip": true
}
}
},
"/debug/expvar": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Debug"
],
"summary": "Debug expvar",
"operationId": "debug-expvar",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
}
},
"x-apidocgen": {
"skip": true
}
}
},
"/debug/health": {
"get": {
"security": [
@@ -13019,6 +13079,25 @@ const docTemplate = `{
}
}
},
"derp.BytesSentRecv": {
"type": "object",
"properties": {
"key": {
"description": "Key is the public key of the client which sent/received these bytes.",
"allOf": [
{
"$ref": "#/definitions/key.NodePublic"
}
]
},
"recv": {
"type": "integer"
},
"sent": {
"type": "integer"
}
}
},
"derp.ServerInfoMessage": {
"type": "object",
"properties": {
@@ -13524,6 +13603,9 @@ const docTemplate = `{
}
}
},
"key.NodePublic": {
"type": "object"
},
"netcheck.Report": {
"type": "object",
"properties": {
+74
View File
@@ -337,6 +337,58 @@
}
}
},
"/debug/derp/traffic": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Debug"],
"summary": "Debug DERP traffic",
"operationId": "debug-derp-traffic",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/derp.BytesSentRecv"
}
}
}
},
"x-apidocgen": {
"skip": true
}
}
},
"/debug/expvar": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Debug"],
"summary": "Debug expvar",
"operationId": "debug-expvar",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": true
}
}
},
"x-apidocgen": {
"skip": true
}
}
},
"/debug/health": {
"get": {
"security": [
@@ -11852,6 +11904,25 @@
}
}
},
"derp.BytesSentRecv": {
"type": "object",
"properties": {
"key": {
"description": "Key is the public key of the client which sent/received these bytes.",
"allOf": [
{
"$ref": "#/definitions/key.NodePublic"
}
]
},
"recv": {
"type": "integer"
},
"sent": {
"type": "integer"
}
}
},
"derp.ServerInfoMessage": {
"type": "object",
"properties": {
@@ -12321,6 +12392,9 @@
}
}
},
"key.NodePublic": {
"type": "object"
},
"netcheck.Report": {
"type": "object",
"properties": {
+17
View File
@@ -5,6 +5,7 @@ import (
"crypto/tls"
"crypto/x509"
"database/sql"
"expvar"
"flag"
"fmt"
"io"
@@ -84,6 +85,8 @@ func init() {
globalHTTPSwaggerHandler = httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json"))
}
var expDERPOnce = sync.Once{}
// Options are requires parameters for Coder to start.
type Options struct {
AccessURL *url.URL
@@ -559,6 +562,16 @@ func New(options *Options) *API {
derpHandler := derphttp.Handler(api.DERPServer)
derpHandler, api.derpCloseFunc = tailnet.WithWebsocketSupport(api.DERPServer, derpHandler)
// Register DERP on expvar HTTP handler, which we serve below in the router, c.f. expvar.Handler()
// These are the metrics the DERP server exposes.
// TODO: export via prometheus
expDERPOnce.Do(func() {
// We need to do this via a global Once because expvar registry is global and panics if we
// register multiple times. In production there is only one Coderd and one DERP server per
// process, but in testing, we create multiple of both, so the Once protects us from
// panicking.
expvar.Publish("derp", api.DERPServer.ExpVar())
})
cors := httpmw.Cors(options.DeploymentValues.Dangerous.AllowAllCors.Value())
prometheusMW := httpmw.Prometheus(options.PrometheusRegistry)
@@ -1028,6 +1041,10 @@ func New(options *Options) *API {
r.Use(httpmw.ExtractUserParam(options.Database))
r.Get("/debug-link", api.userDebugOIDC)
})
r.Route("/derp", func(r chi.Router) {
r.Get("/traffic", options.DERPServer.ServeDebugTraffic)
})
r.Method("GET", "/expvar", expvar.Handler()) // contains DERP metrics as well as cmdline and memstats
})
})
+21 -1
View File
@@ -276,7 +276,7 @@ func validateHealthSettings(settings codersdk.HealthSettings) error {
}
// For some reason the swagger docs need to be attached to a function.
//
// @Summary Debug Info Websocket Test
// @ID debug-info-websocket-test
// @Security CoderSessionToken
@@ -287,6 +287,26 @@ func validateHealthSettings(settings codersdk.HealthSettings) error {
// @x-apidocgen {"skip": true}
func _debugws(http.ResponseWriter, *http.Request) {} //nolint:unused
// @Summary Debug DERP traffic
// @ID debug-derp-traffic
// @Security CoderSessionToken
// @Produce json
// @Success 200 {array} derp.BytesSentRecv
// @Tags Debug
// @Router /debug/derp/traffic [get]
// @x-apidocgen {"skip": true}
func _debugDERPTraffic(http.ResponseWriter, *http.Request) {} //nolint:unused
// @Summary Debug expvar
// @ID debug-expvar
// @Security CoderSessionToken
// @Produce json
// @Tags Debug
// @Success 200 {object} map[string]any
// @Router /debug/expvar [get]
// @x-apidocgen {"skip": true}
func _debugExpVar(http.ResponseWriter, *http.Request) {} //nolint:unused
func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []codersdk.HealthSection {
dismissedHealthchecks := []codersdk.HealthSection{}
settingsJSON, err := db.GetHealthSettings(ctx)
+17 -22
View File
@@ -136,28 +136,8 @@ func NewServerTailnet(
return nil, xerrors.Errorf("get initial multi agent: %w", err)
}
tn.agentConn.Store(&agentConn)
pn, err := tailnet.NodeToProto(conn.Node())
if err != nil {
tn.logger.Critical(context.Background(), "failed to convert node", slog.Error(err))
} else {
err = tn.getAgentConn().UpdateSelf(pn)
if err != nil {
tn.logger.Warn(context.Background(), "server tailnet update self", slog.Error(err))
}
}
conn.SetNodeCallback(func(node *tailnet.Node) {
pn, err := tailnet.NodeToProto(node)
if err != nil {
tn.logger.Critical(context.Background(), "failed to convert node", slog.Error(err))
return
}
err = tn.getAgentConn().UpdateSelf(pn)
if err != nil {
tn.logger.Warn(context.Background(), "broadcast server node to agents", slog.Error(err))
}
})
// registering the callback also triggers send of the initial node
tn.coordinatee.SetNodeCallback(tn.nodeCallback)
// This is set to allow local DERP traffic to be proxied through memory
// instead of needing to hit the external access URL. Don't use the ctx
@@ -183,6 +163,18 @@ func NewServerTailnet(
return tn, nil
}
func (s *ServerTailnet) nodeCallback(node *tailnet.Node) {
pn, err := tailnet.NodeToProto(node)
if err != nil {
s.logger.Critical(context.Background(), "failed to convert node", slog.Error(err))
return
}
err = s.getAgentConn().UpdateSelf(pn)
if err != nil {
s.logger.Warn(context.Background(), "broadcast server node to agents", slog.Error(err))
}
}
func (s *ServerTailnet) Describe(descs chan<- *prometheus.Desc) {
s.connsPerAgent.Describe(descs)
s.totalConns.Describe(descs)
@@ -285,6 +277,9 @@ func (s *ServerTailnet) reinitCoordinator() {
continue
}
s.agentConn.Store(&agentConn)
// reset the Node callback, which triggers the conn to send the node immediately, and also
// register for updates
s.coordinatee.SetNodeCallback(s.nodeCallback)
// Resubscribe to all of the agents we're tracking.
for agentID := range s.agentConnectionTimes {
+2
View File
@@ -48,6 +48,7 @@ func TestServerTailnet_Reconnect(t *testing.T) {
agentConnectionTimes: make(map[uuid.UUID]time.Time),
}
// reinit the Coordinator once, to load mMultiAgent0
mCoord.EXPECT().SetNodeCallback(gomock.Any()).Times(1)
uut.reinitCoordinator()
mMultiAgent0.EXPECT().NextUpdate(gomock.Any()).
@@ -57,6 +58,7 @@ func TestServerTailnet_Reconnect(t *testing.T) {
Times(1).
Return(true) // this triggers reconnect
setLost := mCoord.EXPECT().SetAllPeersLost().Times(1).After(closed0)
mCoord.EXPECT().SetNodeCallback(gomock.Any()).Times(1).After(closed0)
mMultiAgent1.EXPECT().NextUpdate(gomock.Any()).
Times(1).
After(setLost).
+10 -2
View File
@@ -928,15 +928,23 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
if len(api.OIDCConfig.EmailDomain) > 0 {
ok = false
emailSp := strings.Split(email, "@")
if len(emailSp) == 1 {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Your email %q is not in domains %q!", email, api.OIDCConfig.EmailDomain),
})
return
}
userEmailDomain := emailSp[len(emailSp)-1]
for _, domain := range api.OIDCConfig.EmailDomain {
if strings.HasSuffix(strings.ToLower(email), strings.ToLower(domain)) {
if strings.EqualFold(userEmailDomain, domain) {
ok = true
break
}
}
if !ok {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("Your email %q is not in domains %q !", email, api.OIDCConfig.EmailDomain),
Message: fmt.Sprintf("Your email %q is not in domains %q!", email, api.OIDCConfig.EmailDomain),
})
return
}
+11
View File
@@ -757,6 +757,17 @@ func TestUserOIDC(t *testing.T) {
"kwc.io",
},
StatusCode: http.StatusOK,
}, {
Name: "EmailDomainSubset",
IDTokenClaims: jwt.MapClaims{
"email": "colin@gmail.com",
"email_verified": true,
},
AllowSignups: true,
EmailDomain: []string{
"mail.com",
},
StatusCode: http.StatusForbidden,
}, {
Name: "EmptyClaims",
IDTokenClaims: jwt.MapClaims{},
+50
View File
@@ -0,0 +1,50 @@
package coderd_test
import (
"testing"
"github.com/stretchr/testify/require"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbfake"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
)
func TestAgentAPI_LargeManifest(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, store := coderdtest.NewWithDatabase(t, nil)
adminUser := coderdtest.CreateFirstUser(t, client)
n := 512000
longScript := make([]byte, n)
for i := range longScript {
longScript[i] = 'q'
}
r := dbfake.WorkspaceBuild(t, store, database.Workspace{
OrganizationID: adminUser.OrganizationID,
OwnerID: adminUser.UserID,
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
agents[0].Scripts = []*proto.Script{
{
Script: string(longScript),
},
}
return agents
}).Do()
ac := agentsdk.New(client.URL)
ac.SetSessionToken(r.AgentToken)
conn, err := ac.ConnectRPC(ctx)
defer func() {
_ = conn.Close()
}()
require.NoError(t, err)
agentAPI := agentproto.NewDRPCAgentClient(conn)
manifest, err := agentAPI.GetManifest(ctx, &agentproto.GetManifestRequest{})
require.NoError(t, err)
require.Len(t, manifest.Scripts, 1)
require.Len(t, manifest.Scripts[0].Script, n)
}
+3
View File
@@ -688,6 +688,9 @@ func (c *wsNetConn) Close() error {
// during read or write will cancel the context, but not close the
// conn. Close should be called to release context resources.
func websocketNetConn(ctx context.Context, conn *websocket.Conn, msgType websocket.MessageType) (context.Context, net.Conn) {
// Set the read limit to 4 MiB -- about the limit for protobufs. This needs to be larger than
// the default because some of our protocols can include large messages like startup scripts.
conn.SetReadLimit(1 << 22)
ctx, cancel := context.WithCancel(ctx)
nc := websocket.NetConn(ctx, conn, msgType)
return ctx, &wsNetConn{
+4 -1
View File
@@ -286,8 +286,11 @@ func ProtoFromAppHealthsRequest(req PostAppHealthsRequest) (*proto.BatchUpdateAp
if !ok {
return nil, xerrors.Errorf("unknown app health: %s", h)
}
var idCopy uuid.UUID
copy(idCopy[:], id[:])
pReq.Updates = append(pReq.Updates, &proto.BatchUpdateAppHealthRequest_HealthUpdate{
Id: id[:],
Id: idCopy[:],
Health: proto.AppHealth(hp),
})
}
+28
View File
@@ -7419,6 +7419,24 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| `count` | integer | false | | |
| `workspaces` | array of [codersdk.Workspace](#codersdkworkspace) | false | | |
## derp.BytesSentRecv
```json
{
"key": {},
"recv": 0,
"sent": 0
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------ | -------------------------------- | -------- | ------------ | -------------------------------------------------------------------- |
| `key` | [key.NodePublic](#keynodepublic) | false | | Key is the public key of the client which sent/received these bytes. |
| `recv` | integer | false | | |
| `sent` | integer | false | | |
## derp.ServerInfoMessage
```json
@@ -8532,6 +8550,16 @@ If the schedule is empty, the user will be updated to use the default schedule.|
| `warnings` | array of [health.Message](#healthmessage) | false | | |
| `workspace_proxies` | [codersdk.RegionsResponse-codersdk_WorkspaceProxy](#codersdkregionsresponse-codersdk_workspaceproxy) | false | | |
## key.NodePublic
```json
{}
```
### Properties
_None_
## netcheck.Report
```json
Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+107
View File
@@ -0,0 +1,107 @@
## Changelog
### Features
- Coder will now autofill parameters in the "create workspace" page based on the previous value (#11731) (@ammario)
![Parameter autofill](./images/parameter-autofill.png)
- Change default Terraform version to v1.6 and relax max version constraint (#12027) (@johnstcn)
- Show workspace name suggestions below the name field (#12001) (@aslilac)
- Add favorite/unfavorite commands (@johnstcn)
- Add .zip support for Coder templates (#11839) (#12032) (@mtojek)
- Add support for `--wait` and scripts that block login (#10473) (@mafredri)
- Users can mark workspaces as favorite to show up at the top of their list (#11875) (#11793) (#11878) (#11791) (@johnstcn)
![Favorite workspaces](./images/favorite_workspace.png)
- Add Prometheus metrics to servertailnet (#11988) (@coadler)
- Add support for concurrent scenarios (#11753) (@mafredri)
- Display user avatar on workspace page (#11893) (@BrunoQuaresma)
- Do not show popover on update deadline (#11921) (@BrunoQuaresma)
- Simplify "Create Template" form by removing advanced settings (#11918) (@BrunoQuaresma)
- Show deprecation message on template page (#11996) (@BrunoQuaresma)
- Check agent API version on connection (#11696)
- Add "updated" search param to workspaces (#11714)
- Add option to speedtest to dump a pcap of network traffic (#11848) (@coadler)
- Add logging to client tailnet yamux (#11908) (@spikecurtis)
- Add customizable upgrade message on client/server version mismatch (#11587) (@sreya)
- Add logging to pgcoord subscribe/unsubscribe (#11952) (@spikecurtis)
- Add logging to pgPubsub (#11953) (@spikecurtis)
- Add logging to agent yamux session (#11912) (@spikecurtis)
- Move agent v2 API connection monitoring to yamux layer (#11910) (@spikecurtis)
- Add statsReporter for reporting stats on agent v2 API (#11920) (@spikecurtis)
- Add metrics to PGPubsub (#11971) (@spikecurtis)
- Add custom error message on signups disabled page (#11959) (@mtojek)
### Bug fixes
- Make ServerTailnet set peers lost when it reconnects to the coordinator (#11682)
- Properly auto-update workspace on SSH if template policy is set (#11773)
- Allow template name length of 32 in template push and create (#11915) (@mafredri)
- Alter return signature of convertWorkspace, add check for requesterID (#11796) (@johnstcn)
- Fix limit in `GetUserWorkspaceBuildParameters` (#11954) (@mafredri)
- Fix test flake in TestHeartbeat (#11808) (@johnstcn)
- Do not cache context cancellation errors (#11840) (@johnstcn)
- Fix startup script on workspace creation (#11958) (@matifali)
- Fix startup script looping (#11972) (@code-asher)
- Add ID to default columns in licenses list output (#11823) (@johnstcn)
- Handle query canceled error in sendBeat() (#11794) (@johnstcn)
- Fix proxy settings link (#11817) (@BrunoQuaresma)
- Disable autostart and autostop according to template settings (#11809) (@Kira-Pilot)
- Fix capitalized username (#11891) (@BrunoQuaresma)
- Fix parameters' request upon template variables update (#11898) (@BrunoQuaresma)
- Fix text overflow on batch ws deletion (#11981) (@BrunoQuaresma)
- Fix parameter input icon shrink (#11995) (@BrunoQuaresma)
- Use TSMP ping for reachability, not latency (#11749)
- Display error when fetching OAuth2 provider apps (#11713)
- Fix code-server path based forwarding, defer to code-server (#11759)
- Use correct logger for lifecycle_executor (#11763)
- Disable keepalives in workspaceapps transport (#11789) (@coadler)
- Accept agent RPC connection without version query parameter (#11790) (@spikecurtis)
- Stop spamming DERP map updates for equivalent maps (#11792) (@spikecurtis)
- Check update permission to start workspace (#11798) (@mtojek)
- Always attempt external auth refresh when fetching (#11762) (@Emyrk)
- Stop running tests that exec sh scripts in parallel (#11834) (@spikecurtis)
- Fix type error from theme update (#11844) (@aslilac)
- Use new context after t.Parallel in TestOAuthAppSecrets (@spikecurtis)
- Always attempt external auth refresh when fetching (#11762) (#11830) (@Emyrk)
- Wait for new template version before promoting (#11874) (@spikecurtis)
- Respect wait flag on ping (#11896) (@f0ssel)
- Fix cliui prompt styling (#11899) (@aslilac)
- Fix prevent agent_test.go from failing on error logs (#11909) (@spikecurtis)
- Add timeout to listening ports request (#11935) (@coadler)
- Only delete expired agents on success (#11940) (@coadler)
- Close MultiAgentConn when coordinator closes (#11941) (@spikecurtis)
- Strip timezone information from a date in dau response (#11962) (@Emyrk)
- Improve click UX and styling for Auth Token page (#11863) (@Parkreiner)
- Rewrite url to agent ip in single tailnet (#11810) (@coadler)
- Avoid race in TestPGPubsub_Metrics by using Eventually (#11973) (@spikecurtis)
- Always return a clean http client for promoauth (#11963) (@Emyrk)
- Change build status colors (#11985) (@aslilac)
- Only display xray results if vulns > 0 (#11989) (@sreya)
- Use dark background in terminal, even when a light theme is selected (#12004) (@aslilac)
- Fix graceful disconnect in DialWorkspaceAgent (#11993) (@spikecurtis)
- Stop logging error on query canceled (#12017) (@spikecurtis)
- Only display quota if it is higher than 0 (#11979) (@BrunoQuaresma)
### Documentation
- Using coder modules in offline deployments (#11788) (@matifali)
- Simplify JFrog integration docs (#11787) (@matifali)
- Add guide for azure federation (#11864) (@ericpaulsen)
- Fix example template README 404s and semantics (#11903) (@ericpaulsen)
- Update remote docker host docs (#11919) (@matifali)
- Add FAQ for gateway reconnects (#12007) (@ericpaulsen)
### Code refactoring
- Apply cosmetic changes and remove ExternalAuth from settings page (#11756)
- Minor improvements create workspace form (#11771)
- Verify external auth before displaying workspace form (#11777) (@BrunoQuaresma)
Compare: [`v2.7.2...v2.7.3`](https://github.com/coder/coder/compare/v2.7.2...v2.7.3)
## Container image
- `docker pull ghcr.io/coder/coder:v2.7.3`
## Install/upgrade
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
+15
View File
@@ -0,0 +1,15 @@
## Changelog
### Bug fixes
- Fixed an issue where workspace apps may never be marked as healthy.
Compare: [`v2.8.1...v2.8.2`](https://github.com/coder/coder/compare/v2.8.1...v2.8.2)
## Container image
- `docker pull ghcr.io/coder/coder:v2.8.2`
## Install/upgrade
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
+15
View File
@@ -0,0 +1,15 @@
## Changelog
### Bug fixes
- Fixed an issue where some workspace apps were inaccessible.
Compare: [`v2.8.2...v2.8.3`](https://github.com/coder/coder/compare/v2.8.1...v2.8.2)
## Container image
- `docker pull ghcr.io/coder/coder:v2.8.3`
## Install/upgrade
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
+20
View File
@@ -0,0 +1,20 @@
## Changelog
All users are recommended to upgrade to a version that patches
[GHSA-7cc2-r658-7xpf](https://github.com/coder/coder/security/advisories/GHSA-7cc2-r658-7xpf)
as soon as possible if they are using OIDC authentication with the
`CODER_OIDC_EMAIL_DOMAIN` setting.
### Security
- Fixes [GHSA-7cc2-r658-7xpf](https://github.com/coder/coder/security/advisories/GHSA-7cc2-r658-7xpf)
Compare: [`v2.8.3...v2.8.4`](https://github.com/coder/coder/compare/v2.8.3...v2.8.4)
## Container image
- `docker pull ghcr.io/coder/coder:v2.8.4`
## Install/upgrade
Refer to our docs to [install](https://coder.com/docs/v2/latest/install) or [upgrade](https://coder.com/docs/v2/latest/admin/upgrade) Coder, or use a release asset below.
+13 -3
View File
@@ -1,4 +1,9 @@
import type { PropsWithChildren, FC } from "react";
import {
type PropsWithChildren,
type FC,
forwardRef,
HTMLAttributes,
} from "react";
import Tooltip from "@mui/material/Tooltip";
import { type Interpolation, type Theme } from "@emotion/react";
import { Stack } from "components/Stack/Stack";
@@ -74,9 +79,14 @@ export const NotReachableBadge: FC = () => {
);
};
export const DisabledBadge: FC = () => {
export const DisabledBadge: FC = forwardRef<
HTMLSpanElement,
HTMLAttributes<HTMLSpanElement>
>((props, ref) => {
return (
<span
{...props}
ref={ref}
css={[
styles.badge,
(theme) => ({
@@ -89,7 +99,7 @@ export const DisabledBadge: FC = () => {
Disabled
</span>
);
};
});
export const EnterpriseBadge: FC = () => {
return (
@@ -105,3 +105,13 @@ export const Loading: Story = {
isLoading: true,
},
};
export const AutoStopAndStartOff: Story = {
args: {
initialValues: {
...defaultInitialValues,
autostartEnabled: false,
autostopEnabled: false,
},
},
};
@@ -379,3 +379,37 @@ describe("templateInheritance", () => {
expect(ttlInput).toBeDisabled();
});
});
test("form should be enabled when both auto stop and auto start features are disabled, given that the template permits these actions", async () => {
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
render(
<WorkspaceScheduleForm
{...defaultFormProps}
initialValues={{
...defaultFormProps.initialValues,
autostopEnabled: false,
autostartEnabled: false,
}}
/>,
);
const submitButton = await screen.findByRole("button", { name: "Submit" });
expect(submitButton).toBeEnabled();
});
test("form should be disabled when both auto stop and auto start features are disabled at template level", async () => {
jest.spyOn(API, "getTemplateByName").mockResolvedValue(MockTemplate);
render(
<WorkspaceScheduleForm
{...defaultFormProps}
allowTemplateAutoStart={false}
allowTemplateAutoStop={false}
initialValues={{
...defaultFormProps.initialValues,
}}
/>,
);
const submitButton = await screen.findByRole("button", { name: "Submit" });
expect(submitButton).toBeDisabled();
});
@@ -441,10 +441,9 @@ export const WorkspaceScheduleForm: FC<WorkspaceScheduleFormProps> = ({
<FormFooter
onCancel={onCancel}
isLoading={isLoading}
submitDisabled={
(!allowTemplateAutoStart && !allowTemplateAutoStop) ||
(!form.values.autostartEnabled && !form.values.autostopEnabled)
}
// If both options, autostart and autostop, are disabled at the template
// level, the form is disabled.
submitDisabled={!allowTemplateAutoStart && !allowTemplateAutoStop}
/>
</HorizontalForm>
);