From 0dc7ca368f34d22dcfbf1f75501da6561e25c084 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Thu, 21 Nov 2024 16:29:40 +0000
Subject: [PATCH 01/17] Working implementation

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/database/dbmem/dbmem.go                |   1 +
 coderd/database/dump.sql                      |   8 +-
 ...00277_workspace_app_cors_behavior.down.sql |   4 +
 .../000277_workspace_app_cors_behavior.up.sql |  10 +
 coderd/database/models.go                     |  61 +-
 coderd/database/queries.sql.go                |  18 +-
 coderd/database/queries/workspaceapps.sql     |   3 +-
 coderd/database/sqlc.yaml                     |   1 +
 coderd/httpmw/cors.go                         |   6 +
 coderd/httpmw/cors_test.go                    |   3 +-
 .../provisionerdserver/provisionerdserver.go  |  10 +
 coderd/workspaceapps/apptest/apptest.go       | 173 ++++-
 coderd/workspaceapps/apptest/setup.go         |  99 ++-
 coderd/workspaceapps/cors/cors.go             |  24 +
 coderd/workspaceapps/db.go                    |   4 +
 coderd/workspaceapps/provider.go              |   1 +
 coderd/workspaceapps/proxy.go                 |  77 +-
 coderd/workspaceapps/request.go               |   8 +
 coderd/workspaceapps/token.go                 |  10 +-
 provisioner/terraform/resources.go            |  32 +-
 provisionersdk/proto/provisioner.pb.go        | 715 ++++++++++--------
 provisionersdk/proto/provisioner.proto        |   6 +
 provisionersdk/proto/provisioner_drpc.pb.go   |   2 +-
 site/e2e/provisionerGenerated.ts              |  10 +
 24 files changed, 891 insertions(+), 395 deletions(-)
 create mode 100644 coderd/database/migrations/000277_workspace_app_cors_behavior.down.sql
 create mode 100644 coderd/database/migrations/000277_workspace_app_cors_behavior.up.sql
 create mode 100644 coderd/workspaceapps/cors/cors.go

diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go
index 765573b311a84..50d8376d6db42 100644
--- a/coderd/database/dbmem/dbmem.go
+++ b/coderd/database/dbmem/dbmem.go
@@ -8174,6 +8174,7 @@ func (q *FakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW
 		Health:               arg.Health,
 		Hidden:               arg.Hidden,
 		DisplayOrder:         arg.DisplayOrder,
+		CorsBehavior:         arg.CorsBehavior,
 	}
 	q.workspaceApps = append(q.workspaceApps, workspaceApp)
 	return workspaceApp, nil
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index eba9b7cf106d3..0d0a613d1f187 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -10,6 +10,11 @@ CREATE TYPE api_key_scope AS ENUM (
     'application_connect'
 );
 
+CREATE TYPE app_cors_behavior AS ENUM (
+    'simple',
+    'passthru'
+);
+
 CREATE TYPE app_sharing_level AS ENUM (
     'owner',
     'authenticated',
@@ -1580,7 +1585,8 @@ CREATE TABLE workspace_apps (
     slug text NOT NULL,
     external boolean DEFAULT false NOT NULL,
     display_order integer DEFAULT 0 NOT NULL,
-    hidden boolean DEFAULT false NOT NULL
+    hidden boolean DEFAULT false NOT NULL,
+    cors_behavior app_cors_behavior DEFAULT 'simple'::app_cors_behavior NOT NULL
 );
 
 COMMENT ON COLUMN workspace_apps.display_order IS 'Specifies the order in which to display agent app in user interfaces.';
diff --git a/coderd/database/migrations/000277_workspace_app_cors_behavior.down.sql b/coderd/database/migrations/000277_workspace_app_cors_behavior.down.sql
new file mode 100644
index 0000000000000..5f6e26306594e
--- /dev/null
+++ b/coderd/database/migrations/000277_workspace_app_cors_behavior.down.sql
@@ -0,0 +1,4 @@
+ALTER TABLE workspace_apps
+    DROP COLUMN IF EXISTS cors_behavior;
+
+DROP TYPE IF EXISTS app_cors_behavior;
\ No newline at end of file
diff --git a/coderd/database/migrations/000277_workspace_app_cors_behavior.up.sql b/coderd/database/migrations/000277_workspace_app_cors_behavior.up.sql
new file mode 100644
index 0000000000000..aab91bf4024e7
--- /dev/null
+++ b/coderd/database/migrations/000277_workspace_app_cors_behavior.up.sql
@@ -0,0 +1,10 @@
+CREATE TYPE app_cors_behavior AS ENUM (
+    'simple',
+    'passthru'
+);
+
+-- https://www.postgresql.org/docs/16/sql-altertable.html
+-- When a column is added with ADD COLUMN and a non-volatile DEFAULT is specified, the default is evaluated at the time
+-- of the statement and the result stored in the table's metadata. That value will be used for the column for all existing rows.
+ALTER TABLE workspace_apps
+    ADD COLUMN cors_behavior app_cors_behavior NOT NULL DEFAULT 'simple'::app_cors_behavior;
\ No newline at end of file
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 6b99245079950..28232ef1cfbbb 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -74,6 +74,64 @@ func AllAPIKeyScopeValues() []APIKeyScope {
 	}
 }
 
+type AppCORSBehavior string
+
+const (
+	AppCorsBehaviorSimple   AppCORSBehavior = "simple"
+	AppCorsBehaviorPassthru AppCORSBehavior = "passthru"
+)
+
+func (e *AppCORSBehavior) Scan(src interface{}) error {
+	switch s := src.(type) {
+	case []byte:
+		*e = AppCORSBehavior(s)
+	case string:
+		*e = AppCORSBehavior(s)
+	default:
+		return fmt.Errorf("unsupported scan type for AppCORSBehavior: %T", src)
+	}
+	return nil
+}
+
+type NullAppCORSBehavior struct {
+	AppCORSBehavior AppCORSBehavior `json:"app_cors_behavior"`
+	Valid           bool            `json:"valid"` // Valid is true if AppCORSBehavior is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (ns *NullAppCORSBehavior) Scan(value interface{}) error {
+	if value == nil {
+		ns.AppCORSBehavior, ns.Valid = "", false
+		return nil
+	}
+	ns.Valid = true
+	return ns.AppCORSBehavior.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (ns NullAppCORSBehavior) Value() (driver.Value, error) {
+	if !ns.Valid {
+		return nil, nil
+	}
+	return string(ns.AppCORSBehavior), nil
+}
+
+func (e AppCORSBehavior) Valid() bool {
+	switch e {
+	case AppCorsBehaviorSimple,
+		AppCorsBehaviorPassthru:
+		return true
+	}
+	return false
+}
+
+func AllAppCORSBehaviorValues() []AppCORSBehavior {
+	return []AppCORSBehavior{
+		AppCorsBehaviorSimple,
+		AppCorsBehaviorPassthru,
+	}
+}
+
 type AppSharingLevel string
 
 const (
@@ -3082,7 +3140,8 @@ type WorkspaceApp struct {
 	// Specifies the order in which to display agent app in user interfaces.
 	DisplayOrder int32 `db:"display_order" json:"display_order"`
 	// Determines if the app is not shown in user interfaces.
-	Hidden bool `db:"hidden" json:"hidden"`
+	Hidden       bool            `db:"hidden" json:"hidden"`
+	CorsBehavior AppCORSBehavior `db:"cors_behavior" json:"cors_behavior"`
 }
 
 // A record of workspace app usage statistics
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 33a3ce12a444d..bb4165230548a 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -13070,7 +13070,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentStats(ctx context.Context, arg InsertWo
 }
 
 const getWorkspaceAppByAgentIDAndSlug = `-- name: GetWorkspaceAppByAgentIDAndSlug :one
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden FROM workspace_apps WHERE agent_id = $1 AND slug = $2
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE agent_id = $1 AND slug = $2
 `
 
 type GetWorkspaceAppByAgentIDAndSlugParams struct {
@@ -13099,12 +13099,13 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg Ge
 		&i.External,
 		&i.DisplayOrder,
 		&i.Hidden,
+		&i.CorsBehavior,
 	)
 	return i, err
 }
 
 const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
 `
 
 func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) {
@@ -13134,6 +13135,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
+			&i.CorsBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -13149,7 +13151,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
 }
 
 const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
 `
 
 func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) {
@@ -13179,6 +13181,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
+			&i.CorsBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -13194,7 +13197,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
 }
 
 const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
 `
 
 func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) {
@@ -13224,6 +13227,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
+			&i.CorsBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -13252,6 +13256,7 @@ INSERT INTO
         external,
         subdomain,
         sharing_level,
+        cors_behavior,
         healthcheck_url,
         healthcheck_interval,
         healthcheck_threshold,
@@ -13260,7 +13265,7 @@ INSERT INTO
         hidden
     )
 VALUES
-    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden
+    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior
 `
 
 type InsertWorkspaceAppParams struct {
@@ -13275,6 +13280,7 @@ type InsertWorkspaceAppParams struct {
 	External             bool               `db:"external" json:"external"`
 	Subdomain            bool               `db:"subdomain" json:"subdomain"`
 	SharingLevel         AppSharingLevel    `db:"sharing_level" json:"sharing_level"`
+	CorsBehavior         AppCORSBehavior    `db:"cors_behavior" json:"cors_behavior"`
 	HealthcheckUrl       string             `db:"healthcheck_url" json:"healthcheck_url"`
 	HealthcheckInterval  int32              `db:"healthcheck_interval" json:"healthcheck_interval"`
 	HealthcheckThreshold int32              `db:"healthcheck_threshold" json:"healthcheck_threshold"`
@@ -13296,6 +13302,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
 		arg.External,
 		arg.Subdomain,
 		arg.SharingLevel,
+		arg.CorsBehavior,
 		arg.HealthcheckUrl,
 		arg.HealthcheckInterval,
 		arg.HealthcheckThreshold,
@@ -13322,6 +13329,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
 		&i.External,
 		&i.DisplayOrder,
 		&i.Hidden,
+		&i.CorsBehavior,
 	)
 	return i, err
 }
diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql
index 9ae1367093efd..393427c1ccc68 100644
--- a/coderd/database/queries/workspaceapps.sql
+++ b/coderd/database/queries/workspaceapps.sql
@@ -24,6 +24,7 @@ INSERT INTO
         external,
         subdomain,
         sharing_level,
+        cors_behavior,
         healthcheck_url,
         healthcheck_interval,
         healthcheck_threshold,
@@ -32,7 +33,7 @@ INSERT INTO
         hidden
     )
 VALUES
-    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING *;
+    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING *;
 
 -- name: UpdateWorkspaceAppHealthByID :exec
 UPDATE
diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml
index fac159f71ebe3..105fceade4f1c 100644
--- a/coderd/database/sqlc.yaml
+++ b/coderd/database/sqlc.yaml
@@ -146,6 +146,7 @@ sql:
           login_type_oauth2_provider_app: LoginTypeOAuth2ProviderApp
           crypto_key_feature_workspace_apps_api_key: CryptoKeyFeatureWorkspaceAppsAPIKey
           crypto_key_feature_oidc_convert: CryptoKeyFeatureOIDCConvert
+          app_cors_behavior: AppCORSBehavior
 rules:
   - name: do-not-use-public-schema-in-queries
     message: "do not use public schema in queries"
diff --git a/coderd/httpmw/cors.go b/coderd/httpmw/cors.go
index dd69c714379a4..8d6b946617378 100644
--- a/coderd/httpmw/cors.go
+++ b/coderd/httpmw/cors.go
@@ -8,6 +8,7 @@ import (
 	"github.com/go-chi/cors"
 
 	"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
+	ws_cors "github.com/coder/coder/v2/coderd/workspaceapps/cors"
 )
 
 const (
@@ -47,6 +48,11 @@ func Cors(allowAll bool, origins ...string) func(next http.Handler) http.Handler
 func WorkspaceAppCors(regex *regexp.Regexp, app appurl.ApplicationURL) func(next http.Handler) http.Handler {
 	return cors.Handler(cors.Options{
 		AllowOriginFunc: func(r *http.Request, rawOrigin string) bool {
+			// If passthru behavior is set, disable our simplified CORS handling.
+			if ws_cors.HasBehavior(r.Context(), ws_cors.AppCORSBehaviorPassthru) {
+				return true
+			}
+
 			origin, err := url.Parse(rawOrigin)
 			if rawOrigin == "" || origin.Host == "" || err != nil {
 				return false
diff --git a/coderd/httpmw/cors_test.go b/coderd/httpmw/cors_test.go
index 57111799ff292..5dc746ccf7edd 100644
--- a/coderd/httpmw/cors_test.go
+++ b/coderd/httpmw/cors_test.go
@@ -105,7 +105,8 @@ func TestWorkspaceAppCors(t *testing.T) {
 					r.Header.Set("Access-Control-Request-Method", method)
 				}
 
-				handler := httpmw.WorkspaceAppCors(regex, test.app)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+				// TODO: signed token provider
+				handler := httpmw.WorkspaceAppCors(nil, regex, test.app)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 					rw.WriteHeader(http.StatusNoContent)
 				}))
 
diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go
index 71847b0562d0b..734dd3279e520 100644
--- a/coderd/provisionerdserver/provisionerdserver.go
+++ b/coderd/provisionerdserver/provisionerdserver.go
@@ -1988,6 +1988,15 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
 				sharingLevel = database.AppSharingLevelPublic
 			}
 
+			// TODO: consider backwards-compat where proto might not contain this field
+			var corsBehavior database.AppCORSBehavior
+			switch app.CorsBehavior {
+			case sdkproto.AppCORSBehavior_PASSTHRU:
+				corsBehavior = database.AppCorsBehaviorPassthru
+			default:
+				corsBehavior = database.AppCorsBehaviorSimple
+			}
+
 			dbApp, err := db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{
 				ID:          uuid.New(),
 				CreatedAt:   dbtime.Now(),
@@ -2006,6 +2015,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
 				External:             app.External,
 				Subdomain:            app.Subdomain,
 				SharingLevel:         sharingLevel,
+				CorsBehavior:         corsBehavior,
 				HealthcheckUrl:       app.Healthcheck.Url,
 				HealthcheckInterval:  app.Healthcheck.Interval,
 				HealthcheckThreshold: app.Healthcheck.Threshold,
diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go
index c6e251806230d..f8448d8daad52 100644
--- a/coderd/workspaceapps/apptest/apptest.go
+++ b/coderd/workspaceapps/apptest/apptest.go
@@ -472,6 +472,53 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 		})
 	})
 
+	t.Run("CORS", func(t *testing.T) {
+		t.Parallel()
+
+		t.Run("AuthenticatedPassthruProtected", func(t *testing.T) {
+			t.Parallel()
+
+			ctx := testutil.Context(t, testutil.WaitLong)
+
+			appDetails := setupProxyTest(t, nil)
+
+			// Given: an unauthenticated client
+			client := appDetails.AppClient(t)
+			client.SetSessionToken("")
+
+			// When: a request is made to an authenticated app with passthru CORS behavior
+			resp, err := requestWithRetries(ctx, t, client, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.AuthenticatedCORSPassthru).String(), nil)
+			require.NoError(t, err)
+			defer resp.Body.Close()
+
+			// Then: the request is redirected to the primary access URL because even though CORS is passthru,
+			// the request must still be authenticated first
+			require.Equal(t, http.StatusSeeOther, resp.StatusCode)
+			gotLocation, err := resp.Location()
+			require.NoError(t, err)
+			require.Equal(t, appDetails.SDKClient.URL.Host, gotLocation.Host)
+			require.Equal(t, "/api/v2/applications/auth-redirect", gotLocation.Path)
+		})
+
+		t.Run("AuthenticatedPassthruOK", func(t *testing.T) {
+			t.Parallel()
+
+			ctx := testutil.Context(t, testutil.WaitLong)
+
+			appDetails := setupProxyTest(t, nil)
+
+			userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
+			userAppClient := appDetails.AppClient(t)
+			userAppClient.SetSessionToken(userClient.SessionToken())
+
+			// Given: an authenticated app with passthru CORS behavior
+			resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.AuthenticatedCORSPassthru).String(), nil)
+			require.NoError(t, err)
+			defer resp.Body.Close()
+			require.Equal(t, http.StatusOK, resp.StatusCode)
+		})
+	})
+
 	t.Run("WorkspaceApplicationAuth", func(t *testing.T) {
 		t.Parallel()
 
@@ -1399,10 +1446,14 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			agnt = workspaceBuild.Resources[0].Agents[0]
 			found := map[string]codersdk.WorkspaceAppSharingLevel{}
 			expected := map[string]codersdk.WorkspaceAppSharingLevel{
-				proxyTestAppNameFake:          codersdk.WorkspaceAppSharingLevelOwner,
-				proxyTestAppNameOwner:         codersdk.WorkspaceAppSharingLevelOwner,
-				proxyTestAppNameAuthenticated: codersdk.WorkspaceAppSharingLevelAuthenticated,
-				proxyTestAppNamePublic:        codersdk.WorkspaceAppSharingLevelPublic,
+				proxyTestAppNameFake:                      codersdk.WorkspaceAppSharingLevelOwner,
+				proxyTestAppNameOwner:                     codersdk.WorkspaceAppSharingLevelOwner,
+				proxyTestAppNameAuthenticated:             codersdk.WorkspaceAppSharingLevelAuthenticated,
+				proxyTestAppNamePublic:                    codersdk.WorkspaceAppSharingLevelPublic,
+				proxyTestAppNameAuthenticatedCORSPassthru: codersdk.WorkspaceAppSharingLevelAuthenticated,
+				proxyTestAppNamePublicCORSPassthru:        codersdk.WorkspaceAppSharingLevelPublic,
+				proxyTestAppNameAuthenticatedCORSDefault:  codersdk.WorkspaceAppSharingLevelAuthenticated,
+				proxyTestAppNamePublicCORSDefault:         codersdk.WorkspaceAppSharingLevelPublic,
 			}
 			for _, app := range agnt.Apps {
 				found[app.DisplayName] = app.SharingLevel
@@ -1559,6 +1610,12 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 				// Unauthenticated user should not have any access.
 				verifyAccess(t, appDetails, isPathApp, user.Username, workspace.Name, agnt.Name, proxyTestAppNameAuthenticated, clientWithNoAuth, false, true)
+
+				// Unauthenticated user should not have any access, regardless of CORS behavior (using passthru).
+				verifyAccess(t, appDetails, isPathApp, user.Username, workspace.Name, agnt.Name, proxyTestAppNameAuthenticatedCORSPassthru, clientWithNoAuth, false, true)
+
+				// Unauthenticated user should not have any access, regardless of CORS behavior (using default).
+				verifyAccess(t, appDetails, isPathApp, user.Username, workspace.Name, agnt.Name, proxyTestAppNameAuthenticatedCORSDefault, clientWithNoAuth, false, true)
 			})
 
 			t.Run("LevelPublic", func(t *testing.T) {
@@ -1576,6 +1633,12 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 				// Unauthenticated user should be able to access the workspace.
 				verifyAccess(t, appDetails, isPathApp, user.Username, workspace.Name, agnt.Name, proxyTestAppNamePublic, clientWithNoAuth, allowedUnlessSharingDisabled, !allowedUnlessSharingDisabled)
+
+				// Unauthenticated user should have access, regardless of CORS behavior (using passthru).
+				verifyAccess(t, appDetails, isPathApp, user.Username, workspace.Name, agnt.Name, proxyTestAppNamePublicCORSPassthru, clientWithNoAuth, allowedUnlessSharingDisabled, !allowedUnlessSharingDisabled)
+
+				// Unauthenticated user should have access, regardless of CORS behavior (using default).
+				verifyAccess(t, appDetails, isPathApp, user.Username, workspace.Name, agnt.Name, proxyTestAppNamePublicCORSDefault, clientWithNoAuth, allowedUnlessSharingDisabled, !allowedUnlessSharingDisabled)
 			})
 		}
 
@@ -1778,6 +1841,95 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 		require.Equal(t, []string{"baz"}, resp.Header.Values("X-Foobar"))
 	})
 
+	// See above test for original implementation.
+	t.Run("CORSHeadersConditionalStrip", func(t *testing.T) {
+		t.Parallel()
+
+		// Set a bunch of headers which may or may not be stripped, depending on the CORS behavior.
+		// See coderd/workspaceapps/proxy.go (proxyWorkspaceApp).
+		headers := http.Header{
+			"X-Foobar":                         []string{"baz"},
+			"Access-Control-Allow-Origin":      []string{"http://localhost"},
+			"access-control-allow-origin":      []string{"http://localhost"},
+			"Access-Control-Allow-Credentials": []string{"true"},
+			"Access-Control-Allow-Methods":     []string{"PUT"},
+			"Access-Control-Allow-Headers":     []string{"X-Foobar"},
+			"Vary": []string{
+				"Origin",
+				"origin",
+				"Access-Control-Request-Headers",
+				"access-Control-request-Headers",
+				"Access-Control-Request-Methods",
+				"ACCESS-CONTROL-REQUEST-METHODS",
+				"X-Foobar",
+			},
+		}
+
+		appDetails := setupProxyTest(t, &DeploymentOptions{
+			headers: headers,
+		})
+
+		tests := []struct {
+			name        string
+			app         App
+			shouldStrip bool
+		}{
+			{
+				// Uses an app which does not set CORS behavior, which *should* be equivalent to default.
+				name:        "NormalStrip",
+				app:         appDetails.Apps.Owner,
+				shouldStrip: true,
+			},
+			{
+				// Explicitly uses the default CORS behavior.
+				name:        "DefaultStrip",
+				app:         appDetails.Apps.PublicCORSDefault,
+				shouldStrip: true,
+			},
+			{
+				// Explicitly does not strip CORS headers.
+				name:        "PassthruNoStrip",
+				app:         appDetails.Apps.PublicCORSPassthru,
+				shouldStrip: false,
+			},
+		}
+
+		for _, tc := range tests {
+			t.Run(tc.name, func(t *testing.T) {
+				t.Parallel()
+
+				ctx := testutil.Context(t, testutil.WaitLong)
+
+				// Given: a particular app
+				appURL := appDetails.SubdomainAppURL(tc.app)
+
+				// When: querying the app
+				resp, err := requestWithRetries(ctx, t, appDetails.AppClient(t), http.MethodGet, appURL.String(), nil)
+				require.NoError(t, err)
+				defer resp.Body.Close()
+
+				require.Equal(t, http.StatusOK, resp.StatusCode)
+
+				// Then: the CORS headers should be conditionally stripped or not, depending on the CORS behavior.
+				if tc.shouldStrip {
+					require.Empty(t, resp.Header.Values("Access-Control-Allow-Origin"))
+					require.Empty(t, resp.Header.Values("Access-Control-Allow-Credentials"))
+					require.Empty(t, resp.Header.Values("Access-Control-Allow-Methods"))
+					require.Empty(t, resp.Header.Values("Access-Control-Allow-Headers"))
+				} else {
+					for k, v := range headers {
+						// We dedupe the values because some headers have been set multiple times.
+						headerVal := dedupe(resp.Header.Values(k))
+						assert.ElementsMatchf(t, headerVal, v, "header %q does not contain %q", k, v)
+					}
+				}
+
+				// This header is not a CORS-related header, so it should always be set.
+				require.Equal(t, []string{"baz"}, resp.Header.Values("X-Foobar"))
+			})
+		}
+	})
+
 	t.Run("ReportStats", func(t *testing.T) {
 		t.Parallel()
 
@@ -1999,3 +2151,16 @@ func findCookie(cookies []*http.Cookie, name string) *http.Cookie {
 	}
 	return nil
 }
+
+func dedupe[T comparable](elements []T) []T {
+	found := map[T]bool{}
+	result := []T{}
+
+	for _, v := range elements {
+		if !found[v] {
+			found[v] = true
+			result = append(result, v)
+		}
+	}
+	return result
+}
diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go
index 06544446fe6e2..06ff54eec04fc 100644
--- a/coderd/workspaceapps/apptest/setup.go
+++ b/coderd/workspaceapps/apptest/setup.go
@@ -22,6 +22,7 @@ import (
 	"github.com/coder/coder/v2/coderd/coderdtest"
 	"github.com/coder/coder/v2/coderd/workspaceapps"
 	"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
+	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 	"github.com/coder/coder/v2/codersdk/agentsdk"
 	"github.com/coder/coder/v2/cryptorand"
@@ -31,13 +32,17 @@ import (
 )
 
 const (
-	proxyTestAgentName            = "agent-name"
-	proxyTestAppNameFake          = "test-app-fake"
-	proxyTestAppNameOwner         = "test-app-owner"
-	proxyTestAppNameAuthenticated = "test-app-authenticated"
-	proxyTestAppNamePublic        = "test-app-public"
-	proxyTestAppQuery             = "query=true"
-	proxyTestAppBody              = "hello world from apps test"
+	proxyTestAgentName                        = "agent-name"
+	proxyTestAppNameFake                      = "test-app-fake"
+	proxyTestAppNameOwner                     = "test-app-owner"
+	proxyTestAppNameAuthenticated             = "test-app-authenticated"
+	proxyTestAppNamePublic                    = "test-app-public"
+	proxyTestAppNameAuthenticatedCORSPassthru = "test-app-authenticated-cors-passthru"
+	proxyTestAppNamePublicCORSPassthru        = "test-app-public-cors-passthru"
+	proxyTestAppNameAuthenticatedCORSDefault  = "test-app-authenticated-cors-default"
+	proxyTestAppNamePublicCORSDefault         = "test-app-public-cors-default"
+	proxyTestAppQuery                         = "query=true"
+	proxyTestAppBody                          = "hello world from apps test"
 
 	proxyTestSubdomainRaw = "*.test.coder.com"
 	proxyTestSubdomain    = "test.coder.com"
@@ -93,6 +98,10 @@ type App struct {
 	// Prefix should have ---.
 	Prefix string
 	Query  string
+	Path   string
+
+	// Control the behavior of CORS handling.
+	CORSBehavior cors.AppCORSBehavior
 }
 
 // Details are the full test details returned from setupProxyTestWithFactory.
@@ -109,12 +118,16 @@ type Details struct {
 	AppPort   uint16
 
 	Apps struct {
-		Fake          App
-		Owner         App
-		Authenticated App
-		Public        App
-		Port          App
-		PortHTTPS     App
+		Fake                      App
+		Owner                     App
+		Authenticated             App
+		Public                    App
+		Port                      App
+		PortHTTPS                 App
+		PublicCORSPassthru        App
+		AuthenticatedCORSPassthru App
+		PublicCORSDefault         App
+		AuthenticatedCORSDefault  App
 	}
 }
 
@@ -252,6 +265,36 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
 		AgentName:     agnt.Name,
 		AppSlugOrPort: strconv.Itoa(int(opts.port)) + "s",
 	}
+	details.Apps.PublicCORSPassthru = App{
+		Username:      me.Username,
+		WorkspaceName: workspace.Name,
+		AgentName:     agnt.Name,
+		AppSlugOrPort: proxyTestAppNamePublicCORSPassthru,
+		CORSBehavior:  cors.AppCORSBehaviorPassthru,
+		Query:         proxyTestAppQuery,
+	}
+	details.Apps.AuthenticatedCORSPassthru = App{
+		Username:      me.Username,
+		WorkspaceName: workspace.Name,
+		AgentName:     agnt.Name,
+		AppSlugOrPort: proxyTestAppNameAuthenticatedCORSPassthru,
+		CORSBehavior:  cors.AppCORSBehaviorPassthru,
+		Query:         proxyTestAppQuery,
+	}
+	details.Apps.PublicCORSDefault = App{
+		Username:      me.Username,
+		WorkspaceName: workspace.Name,
+		AgentName:     agnt.Name,
+		AppSlugOrPort: proxyTestAppNamePublicCORSDefault,
+		Query:         proxyTestAppQuery,
+	}
+	details.Apps.AuthenticatedCORSDefault = App{
+		Username:      me.Username,
+		WorkspaceName: workspace.Name,
+		AgentName:     agnt.Name,
+		AppSlugOrPort: proxyTestAppNameAuthenticatedCORSDefault,
+		Query:         proxyTestAppQuery,
+	}
 
 	return details
 }
@@ -361,6 +404,36 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
 			Url:          appURL,
 			Subdomain:    true,
 		},
+		{
+			Slug:         proxyTestAppNamePublicCORSPassthru,
+			DisplayName:  proxyTestAppNamePublicCORSPassthru,
+			SharingLevel: proto.AppSharingLevel_PUBLIC,
+			Url:          appURL,
+			Subdomain:    true,
+			CorsBehavior: proto.AppCORSBehavior_PASSTHRU,
+		},
+		{
+			Slug:         proxyTestAppNameAuthenticatedCORSPassthru,
+			DisplayName:  proxyTestAppNameAuthenticatedCORSPassthru,
+			SharingLevel: proto.AppSharingLevel_AUTHENTICATED,
+			Url:          appURL,
+			Subdomain:    true,
+			CorsBehavior: proto.AppCORSBehavior_PASSTHRU,
+		},
+		{
+			Slug:         proxyTestAppNamePublicCORSDefault,
+			DisplayName:  proxyTestAppNamePublicCORSDefault,
+			SharingLevel: proto.AppSharingLevel_PUBLIC,
+			Url:          appURL,
+			Subdomain:    true,
+		},
+		{
+			Slug:         proxyTestAppNameAuthenticatedCORSDefault,
+			DisplayName:  proxyTestAppNameAuthenticatedCORSDefault,
+			SharingLevel: proto.AppSharingLevel_AUTHENTICATED,
+			Url:          appURL,
+			Subdomain:    true,
+		},
 	}
 	version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
 		Parse:         echo.ParseComplete,
diff --git a/coderd/workspaceapps/cors/cors.go b/coderd/workspaceapps/cors/cors.go
new file mode 100644
index 0000000000000..0ee34cf390c5a
--- /dev/null
+++ b/coderd/workspaceapps/cors/cors.go
@@ -0,0 +1,24 @@
+package cors
+
+import "context"
+
+type AppCORSBehavior string
+
+const (
+	AppCORSBehaviorSimple   AppCORSBehavior = "simple"
+	AppCORSBehaviorPassthru AppCORSBehavior = "passthru"
+)
+
+type contextKeyBehavior struct{}
+
+// WithBehavior sets the CORS behavior for the given context.
+func WithBehavior(ctx context.Context, behavior AppCORSBehavior) context.Context {
+	return context.WithValue(ctx, contextKeyBehavior{}, behavior)
+}
+
+// HasBehavior returns true if the given context has the specified CORS behavior.
+func HasBehavior(ctx context.Context, behavior AppCORSBehavior) bool {
+	val := ctx.Value(contextKeyBehavior{})
+	b, ok := val.(AppCORSBehavior)
+	return ok && b == behavior
+}
diff --git a/coderd/workspaceapps/db.go b/coderd/workspaceapps/db.go
index 1aa4dfe91bdd0..1a16a680dfb4d 100644
--- a/coderd/workspaceapps/db.go
+++ b/coderd/workspaceapps/db.go
@@ -16,6 +16,7 @@ import (
 	"github.com/go-jose/go-jose/v4/jwt"
 
 	"cdr.dev/slog"
+
 	"github.com/coder/coder/v2/coderd/cryptokeys"
 	"github.com/coder/coder/v2/coderd/database"
 	"github.com/coder/coder/v2/coderd/database/dbauthz"
@@ -24,6 +25,7 @@ import (
 	"github.com/coder/coder/v2/coderd/jwtutils"
 	"github.com/coder/coder/v2/coderd/rbac"
 	"github.com/coder/coder/v2/coderd/rbac/policy"
+	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 )
 
@@ -123,12 +125,14 @@ func (p *DBTokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r *
 		WriteWorkspaceApp500(p.Logger, p.DashboardURL, rw, r, &appReq, err, "get app details from database")
 		return nil, "", false
 	}
+
 	token.UserID = dbReq.User.ID
 	token.WorkspaceID = dbReq.Workspace.ID
 	token.AgentID = dbReq.Agent.ID
 	if dbReq.AppURL != nil {
 		token.AppURL = dbReq.AppURL.String()
 	}
+	token.CORSBehavior = cors.AppCORSBehavior(dbReq.AppCORSBehavior)
 
 	// Verify the user has access to the app.
 	authed, warnings, err := p.authorizeRequest(r.Context(), authz, dbReq)
diff --git a/coderd/workspaceapps/provider.go b/coderd/workspaceapps/provider.go
index 1887036e35cbf..b2ea018c9c89b 100644
--- a/coderd/workspaceapps/provider.go
+++ b/coderd/workspaceapps/provider.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"cdr.dev/slog"
+
 	"github.com/coder/coder/v2/codersdk"
 )
 
diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go
index a9c60357a009d..979a05d53f13c 100644
--- a/coderd/workspaceapps/proxy.go
+++ b/coderd/workspaceapps/proxy.go
@@ -20,6 +20,7 @@ import (
 	"nhooyr.io/websocket"
 
 	"cdr.dev/slog"
+
 	"github.com/coder/coder/v2/agent/agentssh"
 	"github.com/coder/coder/v2/coderd/cryptokeys"
 	"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -29,6 +30,7 @@ import (
 	"github.com/coder/coder/v2/coderd/tracing"
 	"github.com/coder/coder/v2/coderd/util/slice"
 	"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
+	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 	"github.com/coder/coder/v2/codersdk/workspacesdk"
 	"github.com/coder/coder/v2/site"
@@ -395,41 +397,55 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
 				return
 			}
 
-			// Use the passed in app middlewares before checking authentication and
-			// passing to the proxy app.
-			mws := chi.Middlewares(append(middlewares, httpmw.WorkspaceAppCors(s.HostnameRegex, app)))
-			mws.Handler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-				if !s.handleAPIKeySmuggling(rw, r, AccessMethodSubdomain) {
-					return
-				}
+			if !s.handleAPIKeySmuggling(rw, r, AccessMethodSubdomain) {
+				return
+			}
 
-				token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
-					Logger:              s.Logger,
-					SignedTokenProvider: s.SignedTokenProvider,
-					DashboardURL:        s.DashboardURL,
-					PathAppBaseURL:      s.AccessURL,
-					AppHostname:         s.Hostname,
-					AppRequest: Request{
-						AccessMethod:      AccessMethodSubdomain,
-						BasePath:          "/",
-						Prefix:            app.Prefix,
-						UsernameOrID:      app.Username,
-						WorkspaceNameOrID: app.WorkspaceName,
-						AgentNameOrID:     app.AgentName,
-						AppSlugOrPort:     app.AppSlugOrPort,
-					},
-					AppPath:  r.URL.Path,
-					AppQuery: r.URL.RawQuery,
-				})
-				if !ok {
-					return
-				}
+			// Generate a signed token for the request.
+			token, ok := ResolveRequest(rw, r, ResolveRequestOptions{
+				Logger:              s.Logger,
+				SignedTokenProvider: s.SignedTokenProvider,
+				DashboardURL:        s.DashboardURL,
+				PathAppBaseURL:      s.AccessURL,
+				AppHostname:         s.Hostname,
+				AppRequest: Request{
+					AccessMethod:      AccessMethodSubdomain,
+					BasePath:          "/",
+					Prefix:            app.Prefix,
+					UsernameOrID:      app.Username,
+					WorkspaceNameOrID: app.WorkspaceName,
+					AgentNameOrID:     app.AgentName,
+					AppSlugOrPort:     app.AppSlugOrPort,
+				},
+				AppPath:  r.URL.Path,
+				AppQuery: r.URL.RawQuery,
+			})
+			if !ok {
+				return
+			}
+
+			// Use the passed in app middlewares and CORS middleware with the token
+			mws := chi.Middlewares(append(middlewares, s.injectCORSBehavior(token), httpmw.WorkspaceAppCors(s.HostnameRegex, app)))
+			mws.Handler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 				s.proxyWorkspaceApp(rw, r, *token, r.URL.Path, app)
 			})).ServeHTTP(rw, r.WithContext(ctx))
 		})
 	}
 }
 
+func (s *Server) injectCORSBehavior(token *SignedToken) func(http.Handler) http.Handler {
+	return func(next http.Handler) http.Handler {
+		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			var behavior cors.AppCORSBehavior
+			if token != nil {
+				behavior = token.CORSBehavior
+			}
+
+			next.ServeHTTP(rw, r.WithContext(cors.WithBehavior(r.Context(), behavior)))
+		})
+	}
+}
+
 // parseHostname will return if a given request is attempting to access a
 // workspace app via a subdomain. If it is, the hostname of the request is parsed
 // into an appurl.ApplicationURL and true is returned. If the request is not
@@ -560,6 +576,11 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT
 	proxy := s.AgentProvider.ReverseProxy(appURL, s.DashboardURL, appToken.AgentID, app, s.Hostname)
 
 	proxy.ModifyResponse = func(r *http.Response) error {
+		// If passthru behavior is set, disable our CORS header stripping.
+		if cors.HasBehavior(r.Request.Context(), cors.AppCORSBehaviorPassthru) {
+			return nil
+		}
+
 		r.Header.Del(httpmw.AccessControlAllowOriginHeader)
 		r.Header.Del(httpmw.AccessControlAllowCredentialsHeader)
 		r.Header.Del(httpmw.AccessControlAllowMethodsHeader)
diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go
index 0833ab731fe67..8304bf8bd9772 100644
--- a/coderd/workspaceapps/request.go
+++ b/coderd/workspaceapps/request.go
@@ -202,6 +202,8 @@ type databaseRequest struct {
 	// AppSharingLevel is the sharing level of the app. This is forced to be set
 	// to AppSharingLevelOwner if the access method is terminal.
 	AppSharingLevel database.AppSharingLevel
+	// AppCORSBehavior defines the behavior of the CORS middleware.
+	AppCORSBehavior database.AppCORSBehavior
 }
 
 // getDatabase does queries to get the owner user, workspace and agent
@@ -290,12 +292,16 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
 		agentNameOrID   = r.AgentNameOrID
 		appURL          string
 		appSharingLevel database.AppSharingLevel
+		appCORSBehavior database.AppCORSBehavior
 		// First check if it's a port-based URL with an optional "s" suffix for HTTPS.
 		potentialPortStr      = strings.TrimSuffix(r.AppSlugOrPort, "s")
 		portUint, portUintErr = strconv.ParseUint(potentialPortStr, 10, 16)
 	)
 	//nolint:nestif
 	if portUintErr == nil {
+		// TODO: handle this branch
+		appCORSBehavior = database.AppCorsBehaviorSimple
+
 		protocol := "http"
 		if strings.HasSuffix(r.AppSlugOrPort, "s") {
 			protocol = "https"
@@ -366,6 +372,7 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
 					appSharingLevel = database.AppSharingLevelOwner
 				}
 				appURL = app.Url.String
+				appCORSBehavior = app.CorsBehavior
 				break
 			}
 		}
@@ -412,6 +419,7 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
 		Agent:           agent,
 		AppURL:          appURLParsed,
 		AppSharingLevel: appSharingLevel,
+		AppCORSBehavior: appCORSBehavior,
 	}, nil
 }
 
diff --git a/coderd/workspaceapps/token.go b/coderd/workspaceapps/token.go
index dcd8c5a0e5c34..72b8db2bf8129 100644
--- a/coderd/workspaceapps/token.go
+++ b/coderd/workspaceapps/token.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/coder/coder/v2/coderd/cryptokeys"
 	"github.com/coder/coder/v2/coderd/jwtutils"
+	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 )
 
@@ -22,10 +23,11 @@ type SignedToken struct {
 	// Request details.
 	Request `json:"request"`
 
-	UserID      uuid.UUID `json:"user_id"`
-	WorkspaceID uuid.UUID `json:"workspace_id"`
-	AgentID     uuid.UUID `json:"agent_id"`
-	AppURL      string    `json:"app_url"`
+	UserID       uuid.UUID            `json:"user_id"`
+	WorkspaceID  uuid.UUID            `json:"workspace_id"`
+	AgentID      uuid.UUID            `json:"agent_id"`
+	AppURL       string               `json:"app_url"`
+	CORSBehavior cors.AppCORSBehavior `json:"cors_behavior"`
 }
 
 // MatchesRequest returns true if the token matches the request. Any token that
diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go
index 0ff1660eaf807..1d7b26c4fe423 100644
--- a/provisioner/terraform/resources.go
+++ b/provisioner/terraform/resources.go
@@ -74,16 +74,17 @@ type agentAppAttributes struct {
 	Slug        string `mapstructure:"slug"`
 	DisplayName string `mapstructure:"display_name"`
 	// Name is deprecated in favor of DisplayName.
-	Name        string                     `mapstructure:"name"`
-	Icon        string                     `mapstructure:"icon"`
-	URL         string                     `mapstructure:"url"`
-	External    bool                       `mapstructure:"external"`
-	Command     string                     `mapstructure:"command"`
-	Share       string                     `mapstructure:"share"`
-	Subdomain   bool                       `mapstructure:"subdomain"`
-	Healthcheck []appHealthcheckAttributes `mapstructure:"healthcheck"`
-	Order       int64                      `mapstructure:"order"`
-	Hidden      bool                       `mapstructure:"hidden"`
+	Name         string                     `mapstructure:"name"`
+	Icon         string                     `mapstructure:"icon"`
+	URL          string                     `mapstructure:"url"`
+	External     bool                       `mapstructure:"external"`
+	Command      string                     `mapstructure:"command"`
+	Share        string                     `mapstructure:"share"`
+	CORSBehavior string                     `mapstructure:"cors_behavior"`
+	Subdomain    bool                       `mapstructure:"subdomain"`
+	Healthcheck  []appHealthcheckAttributes `mapstructure:"healthcheck"`
+	Order        int64                      `mapstructure:"order"`
+	Hidden       bool                       `mapstructure:"hidden"`
 }
 
 type agentEnvAttributes struct {
@@ -432,6 +433,16 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
 				sharingLevel = proto.AppSharingLevel_PUBLIC
 			}
 
+			var corsBehavior proto.AppCORSBehavior
+			switch strings.ToLower(attrs.CORSBehavior) {
+			case "simple":
+				corsBehavior = proto.AppCORSBehavior_SIMPLE
+			case "passthru":
+				corsBehavior = proto.AppCORSBehavior_PASSTHRU
+			default:
+				return nil, xerrors.Errorf("invalid app CORS behavior %q", attrs.CORSBehavior)
+			}
+
 			for _, agents := range resourceAgents {
 				for _, agent := range agents {
 					// Find agents with the matching ID and associate them!
@@ -449,6 +460,7 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
 						Icon:         attrs.Icon,
 						Subdomain:    attrs.Subdomain,
 						SharingLevel: sharingLevel,
+						CorsBehavior: corsBehavior,
 						Healthcheck:  healthcheck,
 						Order:        attrs.Order,
 						Hidden:       attrs.Hidden,
diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go
index 026939d17120e..70922e445b343 100644
--- a/provisionersdk/proto/provisioner.pb.go
+++ b/provisionersdk/proto/provisioner.pb.go
@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.30.0
-// 	protoc        v4.23.3
+// 	protoc        v5.28.2
 // source: provisionersdk/proto/provisioner.proto
 
 package proto
@@ -126,6 +126,52 @@ func (AppSharingLevel) EnumDescriptor() ([]byte, []int) {
 	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{1}
 }
 
+type AppCORSBehavior int32
+
+const (
+	AppCORSBehavior_SIMPLE   AppCORSBehavior = 0
+	AppCORSBehavior_PASSTHRU AppCORSBehavior = 1
+)
+
+// Enum value maps for AppCORSBehavior.
+var (
+	AppCORSBehavior_name = map[int32]string{
+		0: "SIMPLE",
+		1: "PASSTHRU",
+	}
+	AppCORSBehavior_value = map[string]int32{
+		"SIMPLE":   0,
+		"PASSTHRU": 1,
+	}
+)
+
+func (x AppCORSBehavior) Enum() *AppCORSBehavior {
+	p := new(AppCORSBehavior)
+	*p = x
+	return p
+}
+
+func (x AppCORSBehavior) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (AppCORSBehavior) Descriptor() protoreflect.EnumDescriptor {
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[2].Descriptor()
+}
+
+func (AppCORSBehavior) Type() protoreflect.EnumType {
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[2]
+}
+
+func (x AppCORSBehavior) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use AppCORSBehavior.Descriptor instead.
+func (AppCORSBehavior) EnumDescriptor() ([]byte, []int) {
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2}
+}
+
 // WorkspaceTransition is the desired outcome of a build
 type WorkspaceTransition int32
 
@@ -160,11 +206,11 @@ func (x WorkspaceTransition) String() string {
 }
 
 func (WorkspaceTransition) Descriptor() protoreflect.EnumDescriptor {
-	return file_provisionersdk_proto_provisioner_proto_enumTypes[2].Descriptor()
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor()
 }
 
 func (WorkspaceTransition) Type() protoreflect.EnumType {
-	return &file_provisionersdk_proto_provisioner_proto_enumTypes[2]
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[3]
 }
 
 func (x WorkspaceTransition) Number() protoreflect.EnumNumber {
@@ -173,7 +219,7 @@ func (x WorkspaceTransition) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use WorkspaceTransition.Descriptor instead.
 func (WorkspaceTransition) EnumDescriptor() ([]byte, []int) {
-	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2}
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3}
 }
 
 type TimingState int32
@@ -209,11 +255,11 @@ func (x TimingState) String() string {
 }
 
 func (TimingState) Descriptor() protoreflect.EnumDescriptor {
-	return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor()
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[4].Descriptor()
 }
 
 func (TimingState) Type() protoreflect.EnumType {
-	return &file_provisionersdk_proto_provisioner_proto_enumTypes[3]
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[4]
 }
 
 func (x TimingState) Number() protoreflect.EnumNumber {
@@ -222,7 +268,7 @@ func (x TimingState) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use TimingState.Descriptor instead.
 func (TimingState) EnumDescriptor() ([]byte, []int) {
-	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3}
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{4}
 }
 
 // Empty indicates a successful request/response.
@@ -1394,6 +1440,7 @@ type App struct {
 	Subdomain    bool            `protobuf:"varint,6,opt,name=subdomain,proto3" json:"subdomain,omitempty"`
 	Healthcheck  *Healthcheck    `protobuf:"bytes,7,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"`
 	SharingLevel AppSharingLevel `protobuf:"varint,8,opt,name=sharing_level,json=sharingLevel,proto3,enum=provisioner.AppSharingLevel" json:"sharing_level,omitempty"`
+	CorsBehavior AppCORSBehavior `protobuf:"varint,12,opt,name=cors_behavior,json=corsBehavior,proto3,enum=provisioner.AppCORSBehavior" json:"cors_behavior,omitempty"`
 	External     bool            `protobuf:"varint,9,opt,name=external,proto3" json:"external,omitempty"`
 	Order        int64           `protobuf:"varint,10,opt,name=order,proto3" json:"order,omitempty"`
 	Hidden       bool            `protobuf:"varint,11,opt,name=hidden,proto3" json:"hidden,omitempty"`
@@ -1487,6 +1534,13 @@ func (x *App) GetSharingLevel() AppSharingLevel {
 	return AppSharingLevel_OWNER
 }
 
+func (x *App) GetCorsBehavior() AppCORSBehavior {
+	if x != nil {
+		return x.CorsBehavior
+	}
+	return AppCORSBehavior_SIMPLE
+}
+
 func (x *App) GetExternal() bool {
 	if x != nil {
 		return x.External
@@ -3116,7 +3170,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
 	0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
 	0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x19,
 	0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x22, 0xe3, 0x02, 0x0a, 0x03, 0x41, 0x70,
+	0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x22, 0xa6, 0x03, 0x0a, 0x03, 0x41, 0x70,
 	0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
 	0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
 	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73,
@@ -3134,192 +3188,169 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
 	0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76,
 	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69,
 	0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67,
-	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
-	0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
-	0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03,
-	0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65,
-	0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x22,
-	0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10,
-	0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c,
-	0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09,
-	0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x92, 0x03, 0x0a, 0x08, 0x52,
-	0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74,
-	0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
-	0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67,
-	0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d,
-	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
-	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d,
-	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69,
-	0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12,
-	0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65,
-	0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f,
-	0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43,
-	0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61,
-	0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
-	0x50, 0x61, 0x74, 0x68, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
-	0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73,
-	0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e,
-	0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c,
-	0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22,
-	0x4c, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b,
-	0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xac, 0x07,
-	0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f,
-	0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63,
-	0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73,
-	0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
-	0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61,
-	0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
-	0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e,
-	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e,
-	0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c,
-	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12,
-	0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e,
-	0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72,
-	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a,
-	0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72,
-	0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69,
-	0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
-	0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
-	0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
-	0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f,
-	0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73,
-	0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63,
-	0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73,
-	0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e,
-	0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f,
-	0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12,
-	0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e,
-	0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d,
-	0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f,
-	0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65,
-	0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73,
-	0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70,
-	0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53,
-	0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73,
-	0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f,
-	0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65,
-	0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62,
-	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12,
-	0x3b, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e,
-	0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77,
-	0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8a, 0x01, 0x0a,
-	0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c,
-	0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69,
-	0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
-	0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12,
-	0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
-	0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
-	0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72,
-	0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61,
-	0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
-	0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
-	0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61,
-	0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
-	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70,
-	0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65,
-	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12,
-	0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
-	0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73,
-	0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
-	0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b,
-	0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d,
-	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a,
-	0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e,
-	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x73, 0x5f, 0x62, 0x65,
+	0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x43, 0x4f,
+	0x52, 0x53, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x52, 0x0c, 0x63, 0x6f, 0x72, 0x73,
+	0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65,
+	0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65,
+	0x72, 0x6e, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20,
+	0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69,
+	0x64, 0x64, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64,
+	0x65, 0x6e, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63,
+	0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12,
+	0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x92, 0x03,
+	0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79,
+	0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+	0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a,
+	0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
+	0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69,
+	0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63,
+	0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61,
+	0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79,
+	0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69,
+	0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
+	0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
 	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
-	0xb5, 0x02, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
-	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
-	0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d,
-	0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
-	0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
-	0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
-	0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61,
-	0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56,
-	0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61,
-	0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17,
-	0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72,
-	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e,
-	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65,
-	0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
-	0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72,
-	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xd6, 0x02, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e,
-	0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
-	0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33,
-	0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
-	0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
-	0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
-	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
-	0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65,
-	0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12,
-	0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73,
+	0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
+	0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f,
+	0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75,
+	0x6c, 0x6c, 0x22, 0x4c, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10,
+	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+	0x22, 0xac, 0x07, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a,
+	0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f,
+	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69,
+	0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+	0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12,
+	0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
+	0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,
+	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64,
+	0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77,
+	0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45,
+	0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
+	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d,
+	0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d,
+	0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
+	0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63,
+	0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f,
+	0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41,
+	0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65,
+	0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
+	0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65,
+	0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64,
+	0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
+	0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
+	0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72,
+	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
+	0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0e,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f,
+	0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x1e, 0x77, 0x6f,
+	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73,
+	0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e,
+	0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x44,
+	0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65,
+	0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65,
+	0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74,
+	0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
+	0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64,
+	0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f,
+	0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65,
+	0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
+	0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22,
+	0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65,
+	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72,
+	0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d,
+	0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69,
+	0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65,
+	0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c,
+	0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a,
+	0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
+	0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54,
+	0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52,
+	0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c,
+	0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f,
+	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+	0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57,
+	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73,
+	0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67,
+	0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
+	0x38, 0x01, 0x22, 0xb5, 0x02, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
+	0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74,
+	0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61,
+	0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
+	0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
+	0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d,
+	0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61,
+	0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+	0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
+	0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12,
+	0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68,
 	0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45,
+	0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45,
 	0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69,
-	0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74,
-	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
-	0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67,
-	0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
-	0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
-	0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
-	0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
-	0x61, 0x74, 0x61, 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d,
-	0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
-	0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
-	0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03,
+	0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74,
+	0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xd6, 0x02, 0x0a, 0x0c, 0x50,
+	0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
+	0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02,
 	0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
 	0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73,
 	0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
-	0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
 	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72,
 	0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
 	0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61,
-	0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20,
+	0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20,
 	0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
 	0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72,
 	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15,
@@ -3327,62 +3358,92 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
 	0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73,
 	0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
 	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d,
-	0x69, 0x6e, 0x67, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12,
-	0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
-	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72,
-	0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
-	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12,
-	0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
-	0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73,
-	0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67,
-	0x65, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54,
-	0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
-	0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d,
-	0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a,
-	0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70,
-	0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65,
-	0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61,
-	0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e,
-	0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70,
-	0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70,
-	0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
-	0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70,
-	0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24,
-	0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72,
-	0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52,
-	0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48,
-	0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
-	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
-	0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70,
-	0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
-	0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70,
-	0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a,
-	0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65,
-	0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
-	0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10,
-	0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45,
-	0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61,
-	0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e,
-	0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49,
-	0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49,
-	0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18,
+	0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+	0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+	0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79,
+	0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
+	0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09,
+	0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72,
+	0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68,
+	0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d,
+	0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
+	0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74,
+	0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69,
+	0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07,
+	0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69,
+	0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73,
+	0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65,
+	0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73,
+	0x74, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+	0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73,
+	0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
+	0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61,
+	0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70,
+	0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+	0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52,
+	0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04,
+	0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
+	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67,
+	0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
+	0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70,
+	0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70,
+	0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05,
+	0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72,
+	0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43,
+	0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79,
+	0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c,
+	0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12,
+	0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
+	0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09,
+	0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70,
+	0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05,
+	0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45,
+	0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55,
+	0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x2b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x43, 0x4f, 0x52,
+	0x53, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x49, 0x4d,
+	0x50, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x41, 0x53, 0x53, 0x54, 0x48, 0x52,
+	0x55, 0x10, 0x01, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
 	0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54,
 	0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12,
 	0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x0b,
@@ -3412,98 +3473,100 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte {
 	return file_provisionersdk_proto_provisioner_proto_rawDescData
 }
 
-var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
+var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
 var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 34)
 var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{
 	(LogLevel)(0),                        // 0: provisioner.LogLevel
 	(AppSharingLevel)(0),                 // 1: provisioner.AppSharingLevel
-	(WorkspaceTransition)(0),             // 2: provisioner.WorkspaceTransition
-	(TimingState)(0),                     // 3: provisioner.TimingState
-	(*Empty)(nil),                        // 4: provisioner.Empty
-	(*TemplateVariable)(nil),             // 5: provisioner.TemplateVariable
-	(*RichParameterOption)(nil),          // 6: provisioner.RichParameterOption
-	(*RichParameter)(nil),                // 7: provisioner.RichParameter
-	(*RichParameterValue)(nil),           // 8: provisioner.RichParameterValue
-	(*VariableValue)(nil),                // 9: provisioner.VariableValue
-	(*Log)(nil),                          // 10: provisioner.Log
-	(*InstanceIdentityAuth)(nil),         // 11: provisioner.InstanceIdentityAuth
-	(*ExternalAuthProviderResource)(nil), // 12: provisioner.ExternalAuthProviderResource
-	(*ExternalAuthProvider)(nil),         // 13: provisioner.ExternalAuthProvider
-	(*Agent)(nil),                        // 14: provisioner.Agent
-	(*DisplayApps)(nil),                  // 15: provisioner.DisplayApps
-	(*Env)(nil),                          // 16: provisioner.Env
-	(*Script)(nil),                       // 17: provisioner.Script
-	(*App)(nil),                          // 18: provisioner.App
-	(*Healthcheck)(nil),                  // 19: provisioner.Healthcheck
-	(*Resource)(nil),                     // 20: provisioner.Resource
-	(*Module)(nil),                       // 21: provisioner.Module
-	(*Metadata)(nil),                     // 22: provisioner.Metadata
-	(*Config)(nil),                       // 23: provisioner.Config
-	(*ParseRequest)(nil),                 // 24: provisioner.ParseRequest
-	(*ParseComplete)(nil),                // 25: provisioner.ParseComplete
-	(*PlanRequest)(nil),                  // 26: provisioner.PlanRequest
-	(*PlanComplete)(nil),                 // 27: provisioner.PlanComplete
-	(*ApplyRequest)(nil),                 // 28: provisioner.ApplyRequest
-	(*ApplyComplete)(nil),                // 29: provisioner.ApplyComplete
-	(*Timing)(nil),                       // 30: provisioner.Timing
-	(*CancelRequest)(nil),                // 31: provisioner.CancelRequest
-	(*Request)(nil),                      // 32: provisioner.Request
-	(*Response)(nil),                     // 33: provisioner.Response
-	(*Agent_Metadata)(nil),               // 34: provisioner.Agent.Metadata
-	nil,                                  // 35: provisioner.Agent.EnvEntry
-	(*Resource_Metadata)(nil),            // 36: provisioner.Resource.Metadata
-	nil,                                  // 37: provisioner.ParseComplete.WorkspaceTagsEntry
-	(*timestamppb.Timestamp)(nil),        // 38: google.protobuf.Timestamp
+	(AppCORSBehavior)(0),                 // 2: provisioner.AppCORSBehavior
+	(WorkspaceTransition)(0),             // 3: provisioner.WorkspaceTransition
+	(TimingState)(0),                     // 4: provisioner.TimingState
+	(*Empty)(nil),                        // 5: provisioner.Empty
+	(*TemplateVariable)(nil),             // 6: provisioner.TemplateVariable
+	(*RichParameterOption)(nil),          // 7: provisioner.RichParameterOption
+	(*RichParameter)(nil),                // 8: provisioner.RichParameter
+	(*RichParameterValue)(nil),           // 9: provisioner.RichParameterValue
+	(*VariableValue)(nil),                // 10: provisioner.VariableValue
+	(*Log)(nil),                          // 11: provisioner.Log
+	(*InstanceIdentityAuth)(nil),         // 12: provisioner.InstanceIdentityAuth
+	(*ExternalAuthProviderResource)(nil), // 13: provisioner.ExternalAuthProviderResource
+	(*ExternalAuthProvider)(nil),         // 14: provisioner.ExternalAuthProvider
+	(*Agent)(nil),                        // 15: provisioner.Agent
+	(*DisplayApps)(nil),                  // 16: provisioner.DisplayApps
+	(*Env)(nil),                          // 17: provisioner.Env
+	(*Script)(nil),                       // 18: provisioner.Script
+	(*App)(nil),                          // 19: provisioner.App
+	(*Healthcheck)(nil),                  // 20: provisioner.Healthcheck
+	(*Resource)(nil),                     // 21: provisioner.Resource
+	(*Module)(nil),                       // 22: provisioner.Module
+	(*Metadata)(nil),                     // 23: provisioner.Metadata
+	(*Config)(nil),                       // 24: provisioner.Config
+	(*ParseRequest)(nil),                 // 25: provisioner.ParseRequest
+	(*ParseComplete)(nil),                // 26: provisioner.ParseComplete
+	(*PlanRequest)(nil),                  // 27: provisioner.PlanRequest
+	(*PlanComplete)(nil),                 // 28: provisioner.PlanComplete
+	(*ApplyRequest)(nil),                 // 29: provisioner.ApplyRequest
+	(*ApplyComplete)(nil),                // 30: provisioner.ApplyComplete
+	(*Timing)(nil),                       // 31: provisioner.Timing
+	(*CancelRequest)(nil),                // 32: provisioner.CancelRequest
+	(*Request)(nil),                      // 33: provisioner.Request
+	(*Response)(nil),                     // 34: provisioner.Response
+	(*Agent_Metadata)(nil),               // 35: provisioner.Agent.Metadata
+	nil,                                  // 36: provisioner.Agent.EnvEntry
+	(*Resource_Metadata)(nil),            // 37: provisioner.Resource.Metadata
+	nil,                                  // 38: provisioner.ParseComplete.WorkspaceTagsEntry
+	(*timestamppb.Timestamp)(nil),        // 39: google.protobuf.Timestamp
 }
 var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{
-	6,  // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption
+	7,  // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption
 	0,  // 1: provisioner.Log.level:type_name -> provisioner.LogLevel
-	35, // 2: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
-	18, // 3: provisioner.Agent.apps:type_name -> provisioner.App
-	34, // 4: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata
-	15, // 5: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps
-	17, // 6: provisioner.Agent.scripts:type_name -> provisioner.Script
-	16, // 7: provisioner.Agent.extra_envs:type_name -> provisioner.Env
-	19, // 8: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
+	36, // 2: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
+	19, // 3: provisioner.Agent.apps:type_name -> provisioner.App
+	35, // 4: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata
+	16, // 5: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps
+	18, // 6: provisioner.Agent.scripts:type_name -> provisioner.Script
+	17, // 7: provisioner.Agent.extra_envs:type_name -> provisioner.Env
+	20, // 8: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
 	1,  // 9: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel
-	14, // 10: provisioner.Resource.agents:type_name -> provisioner.Agent
-	36, // 11: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
-	2,  // 12: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
-	5,  // 13: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable
-	37, // 14: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry
-	22, // 15: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata
-	8,  // 16: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue
-	9,  // 17: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue
-	13, // 18: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider
-	20, // 19: provisioner.PlanComplete.resources:type_name -> provisioner.Resource
-	7,  // 20: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter
-	12, // 21: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
-	30, // 22: provisioner.PlanComplete.timings:type_name -> provisioner.Timing
-	21, // 23: provisioner.PlanComplete.modules:type_name -> provisioner.Module
-	22, // 24: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata
-	20, // 25: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource
-	7,  // 26: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter
-	12, // 27: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
-	30, // 28: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing
-	38, // 29: provisioner.Timing.start:type_name -> google.protobuf.Timestamp
-	38, // 30: provisioner.Timing.end:type_name -> google.protobuf.Timestamp
-	3,  // 31: provisioner.Timing.state:type_name -> provisioner.TimingState
-	23, // 32: provisioner.Request.config:type_name -> provisioner.Config
-	24, // 33: provisioner.Request.parse:type_name -> provisioner.ParseRequest
-	26, // 34: provisioner.Request.plan:type_name -> provisioner.PlanRequest
-	28, // 35: provisioner.Request.apply:type_name -> provisioner.ApplyRequest
-	31, // 36: provisioner.Request.cancel:type_name -> provisioner.CancelRequest
-	10, // 37: provisioner.Response.log:type_name -> provisioner.Log
-	25, // 38: provisioner.Response.parse:type_name -> provisioner.ParseComplete
-	27, // 39: provisioner.Response.plan:type_name -> provisioner.PlanComplete
-	29, // 40: provisioner.Response.apply:type_name -> provisioner.ApplyComplete
-	32, // 41: provisioner.Provisioner.Session:input_type -> provisioner.Request
-	33, // 42: provisioner.Provisioner.Session:output_type -> provisioner.Response
-	42, // [42:43] is the sub-list for method output_type
-	41, // [41:42] is the sub-list for method input_type
-	41, // [41:41] is the sub-list for extension type_name
-	41, // [41:41] is the sub-list for extension extendee
-	0,  // [0:41] is the sub-list for field type_name
+	2,  // 10: provisioner.App.cors_behavior:type_name -> provisioner.AppCORSBehavior
+	15, // 11: provisioner.Resource.agents:type_name -> provisioner.Agent
+	37, // 12: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
+	3,  // 13: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
+	6,  // 14: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable
+	38, // 15: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry
+	23, // 16: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata
+	9,  // 17: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue
+	10, // 18: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue
+	14, // 19: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider
+	21, // 20: provisioner.PlanComplete.resources:type_name -> provisioner.Resource
+	8,  // 21: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter
+	13, // 22: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
+	31, // 23: provisioner.PlanComplete.timings:type_name -> provisioner.Timing
+	22, // 24: provisioner.PlanComplete.modules:type_name -> provisioner.Module
+	23, // 25: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata
+	21, // 26: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource
+	8,  // 27: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter
+	13, // 28: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
+	31, // 29: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing
+	39, // 30: provisioner.Timing.start:type_name -> google.protobuf.Timestamp
+	39, // 31: provisioner.Timing.end:type_name -> google.protobuf.Timestamp
+	4,  // 32: provisioner.Timing.state:type_name -> provisioner.TimingState
+	24, // 33: provisioner.Request.config:type_name -> provisioner.Config
+	25, // 34: provisioner.Request.parse:type_name -> provisioner.ParseRequest
+	27, // 35: provisioner.Request.plan:type_name -> provisioner.PlanRequest
+	29, // 36: provisioner.Request.apply:type_name -> provisioner.ApplyRequest
+	32, // 37: provisioner.Request.cancel:type_name -> provisioner.CancelRequest
+	11, // 38: provisioner.Response.log:type_name -> provisioner.Log
+	26, // 39: provisioner.Response.parse:type_name -> provisioner.ParseComplete
+	28, // 40: provisioner.Response.plan:type_name -> provisioner.PlanComplete
+	30, // 41: provisioner.Response.apply:type_name -> provisioner.ApplyComplete
+	33, // 42: provisioner.Provisioner.Session:input_type -> provisioner.Request
+	34, // 43: provisioner.Provisioner.Session:output_type -> provisioner.Response
+	43, // [43:44] is the sub-list for method output_type
+	42, // [42:43] is the sub-list for method input_type
+	42, // [42:42] is the sub-list for extension type_name
+	42, // [42:42] is the sub-list for extension extendee
+	0,  // [0:42] is the sub-list for field type_name
 }
 
 func init() { file_provisionersdk_proto_provisioner_proto_init() }
@@ -3920,7 +3983,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc,
-			NumEnums:      4,
+			NumEnums:      5,
 			NumMessages:   34,
 			NumExtensions: 0,
 			NumServices:   1,
diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto
index 1e1de886c7d0a..f913434731875 100644
--- a/provisionersdk/proto/provisioner.proto
+++ b/provisionersdk/proto/provisioner.proto
@@ -137,6 +137,11 @@ enum AppSharingLevel {
     PUBLIC = 2;
 }
 
+enum AppCORSBehavior {
+    SIMPLE = 0;
+    PASSTHRU = 1;
+}
+
 message DisplayApps {
     bool vscode = 1;
     bool vscode_insiders = 2;
@@ -175,6 +180,7 @@ message App {
     bool subdomain = 6;
     Healthcheck healthcheck = 7;
     AppSharingLevel sharing_level = 8;
+    AppCORSBehavior cors_behavior = 12;
     bool external = 9;
     int64 order = 10;
     bool hidden = 11;
diff --git a/provisionersdk/proto/provisioner_drpc.pb.go b/provisionersdk/proto/provisioner_drpc.pb.go
index de310e779dcaa..c9c54002439c2 100644
--- a/provisionersdk/proto/provisioner_drpc.pb.go
+++ b/provisionersdk/proto/provisioner_drpc.pb.go
@@ -1,5 +1,5 @@
 // Code generated by protoc-gen-go-drpc. DO NOT EDIT.
-// protoc-gen-go-drpc version: v0.0.33
+// protoc-gen-go-drpc version: (devel)
 // source: provisionersdk/proto/provisioner.proto
 
 package proto
diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts
index 9f238b0e47212..db7419bdefd52 100644
--- a/site/e2e/provisionerGenerated.ts
+++ b/site/e2e/provisionerGenerated.ts
@@ -22,6 +22,12 @@ export enum AppSharingLevel {
 	UNRECOGNIZED = -1,
 }
 
+export enum AppCORSBehavior {
+	SIMPLE = 0,
+	PASSTHRU = 1,
+	UNRECOGNIZED = -1,
+}
+
 /** WorkspaceTransition is the desired outcome of a build */
 export enum WorkspaceTransition {
 	START = 0,
@@ -193,6 +199,7 @@ export interface App {
 	subdomain: boolean;
 	healthcheck: Healthcheck | undefined;
 	sharingLevel: AppSharingLevel;
+	corsBehavior: AppCORSBehavior;
 	external: boolean;
 	order: number;
 	hidden: boolean;
@@ -746,6 +753,9 @@ export const App = {
 		if (message.sharingLevel !== 0) {
 			writer.uint32(64).int32(message.sharingLevel);
 		}
+		if (message.corsBehavior !== 0) {
+			writer.uint32(96).int32(message.corsBehavior);
+		}
 		if (message.external === true) {
 			writer.uint32(72).bool(message.external);
 		}

From 3c0e33aa997d53c40e8138bb8504892d248beb63 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Tue, 26 Nov 2024 09:41:43 +0000
Subject: [PATCH 02/17] Rename CorsBehavior to CORSBehavior

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/database/dbmem/dbmem.go                  |  2 +-
 coderd/database/models.go                       |  2 +-
 coderd/database/queries.sql.go                  | 14 +++++++-------
 coderd/database/sqlc.yaml                       |  1 +
 coderd/provisionerdserver/provisionerdserver.go |  2 +-
 coderd/workspaceapps/request.go                 |  2 +-
 6 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go
index 50d8376d6db42..cfab07333a6c2 100644
--- a/coderd/database/dbmem/dbmem.go
+++ b/coderd/database/dbmem/dbmem.go
@@ -8174,7 +8174,7 @@ func (q *FakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW
 		Health:               arg.Health,
 		Hidden:               arg.Hidden,
 		DisplayOrder:         arg.DisplayOrder,
-		CorsBehavior:         arg.CorsBehavior,
+		CORSBehavior:         arg.CORSBehavior,
 	}
 	q.workspaceApps = append(q.workspaceApps, workspaceApp)
 	return workspaceApp, nil
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 28232ef1cfbbb..821116d0da6cc 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -3141,7 +3141,7 @@ type WorkspaceApp struct {
 	DisplayOrder int32 `db:"display_order" json:"display_order"`
 	// Determines if the app is not shown in user interfaces.
 	Hidden       bool            `db:"hidden" json:"hidden"`
-	CorsBehavior AppCORSBehavior `db:"cors_behavior" json:"cors_behavior"`
+	CORSBehavior AppCORSBehavior `db:"cors_behavior" json:"cors_behavior"`
 }
 
 // A record of workspace app usage statistics
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index bb4165230548a..7fee1e0d2ebd2 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -13099,7 +13099,7 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg Ge
 		&i.External,
 		&i.DisplayOrder,
 		&i.Hidden,
-		&i.CorsBehavior,
+		&i.CORSBehavior,
 	)
 	return i, err
 }
@@ -13135,7 +13135,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
-			&i.CorsBehavior,
+			&i.CORSBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -13181,7 +13181,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
-			&i.CorsBehavior,
+			&i.CORSBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -13227,7 +13227,7 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
-			&i.CorsBehavior,
+			&i.CORSBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -13280,7 +13280,7 @@ type InsertWorkspaceAppParams struct {
 	External             bool               `db:"external" json:"external"`
 	Subdomain            bool               `db:"subdomain" json:"subdomain"`
 	SharingLevel         AppSharingLevel    `db:"sharing_level" json:"sharing_level"`
-	CorsBehavior         AppCORSBehavior    `db:"cors_behavior" json:"cors_behavior"`
+	CORSBehavior         AppCORSBehavior    `db:"cors_behavior" json:"cors_behavior"`
 	HealthcheckUrl       string             `db:"healthcheck_url" json:"healthcheck_url"`
 	HealthcheckInterval  int32              `db:"healthcheck_interval" json:"healthcheck_interval"`
 	HealthcheckThreshold int32              `db:"healthcheck_threshold" json:"healthcheck_threshold"`
@@ -13302,7 +13302,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
 		arg.External,
 		arg.Subdomain,
 		arg.SharingLevel,
-		arg.CorsBehavior,
+		arg.CORSBehavior,
 		arg.HealthcheckUrl,
 		arg.HealthcheckInterval,
 		arg.HealthcheckThreshold,
@@ -13329,7 +13329,7 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
 		&i.External,
 		&i.DisplayOrder,
 		&i.Hidden,
-		&i.CorsBehavior,
+		&i.CORSBehavior,
 	)
 	return i, err
 }
diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml
index 105fceade4f1c..1753da4cbd0ee 100644
--- a/coderd/database/sqlc.yaml
+++ b/coderd/database/sqlc.yaml
@@ -147,6 +147,7 @@ sql:
           crypto_key_feature_workspace_apps_api_key: CryptoKeyFeatureWorkspaceAppsAPIKey
           crypto_key_feature_oidc_convert: CryptoKeyFeatureOIDCConvert
           app_cors_behavior: AppCORSBehavior
+          cors_behavior: CORSBehavior
 rules:
   - name: do-not-use-public-schema-in-queries
     message: "do not use public schema in queries"
diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go
index 734dd3279e520..d057ff623dba5 100644
--- a/coderd/provisionerdserver/provisionerdserver.go
+++ b/coderd/provisionerdserver/provisionerdserver.go
@@ -2015,7 +2015,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
 				External:             app.External,
 				Subdomain:            app.Subdomain,
 				SharingLevel:         sharingLevel,
-				CorsBehavior:         corsBehavior,
+				CORSBehavior:         corsBehavior,
 				HealthcheckUrl:       app.Healthcheck.Url,
 				HealthcheckInterval:  app.Healthcheck.Interval,
 				HealthcheckThreshold: app.Healthcheck.Threshold,
diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go
index 8304bf8bd9772..b700e7d4a54cf 100644
--- a/coderd/workspaceapps/request.go
+++ b/coderd/workspaceapps/request.go
@@ -372,7 +372,7 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
 					appSharingLevel = database.AppSharingLevelOwner
 				}
 				appURL = app.Url.String
-				appCORSBehavior = app.CorsBehavior
+				appCORSBehavior = app.CORSBehavior
 				break
 			}
 		}

From a4d3c8d29fe3ebff6dc04edfeb04e8e39ed0a519 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Wed, 27 Nov 2024 07:37:57 +0000
Subject: [PATCH 03/17] Hard & simplify CORORS handling logic

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/httpmw/cors.go         |  6 ------
 coderd/workspaceapps/proxy.go | 26 ++++++++++++++++++++++----
 2 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/coderd/httpmw/cors.go b/coderd/httpmw/cors.go
index 8d6b946617378..dd69c714379a4 100644
--- a/coderd/httpmw/cors.go
+++ b/coderd/httpmw/cors.go
@@ -8,7 +8,6 @@ import (
 	"github.com/go-chi/cors"
 
 	"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
-	ws_cors "github.com/coder/coder/v2/coderd/workspaceapps/cors"
 )
 
 const (
@@ -48,11 +47,6 @@ func Cors(allowAll bool, origins ...string) func(next http.Handler) http.Handler
 func WorkspaceAppCors(regex *regexp.Regexp, app appurl.ApplicationURL) func(next http.Handler) http.Handler {
 	return cors.Handler(cors.Options{
 		AllowOriginFunc: func(r *http.Request, rawOrigin string) bool {
-			// If passthru behavior is set, disable our simplified CORS handling.
-			if ws_cors.HasBehavior(r.Context(), ws_cors.AppCORSBehaviorPassthru) {
-				return true
-			}
-
 			origin, err := url.Parse(rawOrigin)
 			if rawOrigin == "" || origin.Host == "" || err != nil {
 				return false
diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go
index 979a05d53f13c..da6a075957837 100644
--- a/coderd/workspaceapps/proxy.go
+++ b/coderd/workspaceapps/proxy.go
@@ -424,8 +424,8 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
 				return
 			}
 
-			// Use the passed in app middlewares and CORS middleware with the token
-			mws := chi.Middlewares(append(middlewares, s.injectCORSBehavior(token), httpmw.WorkspaceAppCors(s.HostnameRegex, app)))
+			// Proxy the request (possibly with the CORS middleware).
+			mws := chi.Middlewares(append(middlewares, s.determineCORSBehavior(token, app)))
 			mws.Handler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 				s.proxyWorkspaceApp(rw, r, *token, r.URL.Path, app)
 			})).ServeHTTP(rw, r.WithContext(ctx))
@@ -433,15 +433,33 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
 	}
 }
 
-func (s *Server) injectCORSBehavior(token *SignedToken) func(http.Handler) http.Handler {
+// determineCORSBehavior examines the given token and conditionally applies
+// CORS middleware if the token specifies that behavior.
+func (s *Server) determineCORSBehavior(token *SignedToken, app appurl.ApplicationURL) func(http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
+		// Create the CORS middleware handler upfront.
+		corsHandler := httpmw.WorkspaceAppCors(s.HostnameRegex, app)(next)
+
 		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 			var behavior cors.AppCORSBehavior
 			if token != nil {
 				behavior = token.CORSBehavior
 			}
 
-			next.ServeHTTP(rw, r.WithContext(cors.WithBehavior(r.Context(), behavior)))
+			// Add behavior to context regardless of which handler we use,
+			// since we will use this later on to determine if we should strip
+			// CORS headers in the response.
+			r = r.WithContext(cors.WithBehavior(r.Context(), behavior))
+
+			switch behavior {
+			case cors.AppCORSBehaviorPassthru:
+				// Bypass the CORS middleware.
+				next.ServeHTTP(rw, r)
+				return
+			default:
+				// Apply the CORS middleware.
+				corsHandler.ServeHTTP(rw, r)
+			}
 		})
 	}
 }

From 873c3e45f5162374474f6a13af6f0e854a995e83 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Wed, 27 Nov 2024 09:08:34 +0000
Subject: [PATCH 04/17] Minor fixes

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/httpmw/cors_test.go                    |  3 +-
 .../provisionerdserver/provisionerdserver.go  |  1 -
 coderd/workspaceapps/apptest/apptest.go       | 88 ++++++++++++++++---
 coderd/workspaceapps/request.go               |  2 +-
 provisioner/terraform/resources.go            |  5 +-
 5 files changed, 78 insertions(+), 21 deletions(-)

diff --git a/coderd/httpmw/cors_test.go b/coderd/httpmw/cors_test.go
index 5dc746ccf7edd..57111799ff292 100644
--- a/coderd/httpmw/cors_test.go
+++ b/coderd/httpmw/cors_test.go
@@ -105,8 +105,7 @@ func TestWorkspaceAppCors(t *testing.T) {
 					r.Header.Set("Access-Control-Request-Method", method)
 				}
 
-				// TODO: signed token provider
-				handler := httpmw.WorkspaceAppCors(nil, regex, test.app)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+				handler := httpmw.WorkspaceAppCors(regex, test.app)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 					rw.WriteHeader(http.StatusNoContent)
 				}))
 
diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go
index d057ff623dba5..657742ca14fae 100644
--- a/coderd/provisionerdserver/provisionerdserver.go
+++ b/coderd/provisionerdserver/provisionerdserver.go
@@ -1988,7 +1988,6 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
 				sharingLevel = database.AppSharingLevelPublic
 			}
 
-			// TODO: consider backwards-compat where proto might not contain this field
 			var corsBehavior database.AppCORSBehavior
 			switch app.CorsBehavior {
 			case sdkproto.AppCORSBehavior_PASSTHRU:
diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go
index f8448d8daad52..d2918e571a98e 100644
--- a/coderd/workspaceapps/apptest/apptest.go
+++ b/coderd/workspaceapps/apptest/apptest.go
@@ -475,12 +475,20 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 	t.Run("CORS", func(t *testing.T) {
 		t.Parallel()
 
-		t.Run("AuthenticatedPassthruProtected", func(t *testing.T) {
+		// Set up test headers that should be returned by the app
+		testHeaders := http.Header{
+			"Access-Control-Allow-Origin":  []string{"*"},
+			"Access-Control-Allow-Methods": []string{"GET, POST, OPTIONS"},
+		}
+
+		t.Run("UnauthenticatedPassthruRejected", func(t *testing.T) {
 			t.Parallel()
 
 			ctx := testutil.Context(t, testutil.WaitLong)
 
-			appDetails := setupProxyTest(t, nil)
+			appDetails := setupProxyTest(t, &DeploymentOptions{
+				headers: testHeaders,
+			})
 
 			// Given: an unauthenticated client
 			client := appDetails.AppClient(t)
@@ -491,7 +499,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			require.NoError(t, err)
 			defer resp.Body.Close()
 
-			// Then: the request is redirected to the primary access URL because even though CORS is passthru,
+			// Then: the request is redirected to login because even though CORS is passthru,
 			// the request must still be authenticated first
 			require.Equal(t, http.StatusSeeOther, resp.StatusCode)
 			gotLocation, err := resp.Location()
@@ -505,7 +513,9 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 			ctx := testutil.Context(t, testutil.WaitLong)
 
-			appDetails := setupProxyTest(t, nil)
+			appDetails := setupProxyTest(t, &DeploymentOptions{
+				headers: testHeaders,
+			})
 
 			userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
 			userAppClient := appDetails.AppClient(t)
@@ -516,6 +526,65 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			require.NoError(t, err)
 			defer resp.Body.Close()
 			require.Equal(t, http.StatusOK, resp.StatusCode)
+
+			// Check CORS headers are passed through
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Credentials"), resp.Header.Get("Access-Control-Allow-Credentials"))
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
+		})
+
+		t.Run("UnauthenticatedPublicPassthruOK", func(t *testing.T) {
+			t.Parallel()
+
+			ctx := testutil.Context(t, testutil.WaitLong)
+
+			appDetails := setupProxyTest(t, &DeploymentOptions{
+				headers: testHeaders,
+			})
+
+			// Given: an unauthenticated client
+			client := appDetails.AppClient(t)
+			client.SetSessionToken("")
+
+			// When: a request is made to a public app with passthru CORS behavior
+			resp, err := requestWithRetries(ctx, t, client, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.PublicCORSPassthru).String(), nil)
+			require.NoError(t, err)
+			defer resp.Body.Close()
+
+			// Then: the request succeeds because the app is public
+			require.Equal(t, http.StatusOK, resp.StatusCode)
+
+			// Check CORS headers are passed through
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Credentials"), resp.Header.Get("Access-Control-Allow-Credentials"))
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
+		})
+
+		t.Run("AuthenticatedPublicPassthruOK", func(t *testing.T) {
+			t.Parallel()
+
+			ctx := testutil.Context(t, testutil.WaitLong)
+
+			appDetails := setupProxyTest(t, &DeploymentOptions{
+				headers: testHeaders,
+			})
+
+			userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
+			userAppClient := appDetails.AppClient(t)
+			userAppClient.SetSessionToken(userClient.SessionToken())
+
+			// Given: an authenticated client accessing a public app with passthru CORS behavior
+			resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.PublicCORSPassthru).String(), nil)
+			require.NoError(t, err)
+			defer resp.Body.Close()
+
+			// Then: the request succeeds because the app is public
+			require.Equal(t, http.StatusOK, resp.StatusCode)
+
+			// Check CORS headers are passed through
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Credentials"), resp.Header.Get("Access-Control-Allow-Credentials"))
+			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
 		})
 	})
 
@@ -1842,7 +1911,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 	})
 
 	// See above test for original implementation.
-	t.Run("CORSHeadersConditionalStrip", func(t *testing.T) {
+	t.Run("CORSHeadersConditionallyStripped", func(t *testing.T) {
 		t.Parallel()
 
 		// Set a bunch of headers which may or may not be stripped, depending on the CORS behavior.
@@ -1854,15 +1923,6 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			"Access-Control-Allow-Credentials": []string{"true"},
 			"Access-Control-Allow-Methods":     []string{"PUT"},
 			"Access-Control-Allow-Headers":     []string{"X-Foobar"},
-			"Vary": []string{
-				"Origin",
-				"origin",
-				"Access-Control-Request-Headers",
-				"access-Control-request-Headers",
-				"Access-Control-Request-Methods",
-				"ACCESS-CONTROL-REQUEST-METHODS",
-				"X-Foobar",
-			},
 		}
 
 		appDetails := setupProxyTest(t, &DeploymentOptions{
diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go
index b700e7d4a54cf..ce99d4ccdbcf8 100644
--- a/coderd/workspaceapps/request.go
+++ b/coderd/workspaceapps/request.go
@@ -299,7 +299,7 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
 	)
 	//nolint:nestif
 	if portUintErr == nil {
-		// TODO: handle this branch
+		// TODO: handle CORS passthru for port sharing use-case.
 		appCORSBehavior = database.AppCorsBehaviorSimple
 
 		protocol := "http"
diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go
index 1d7b26c4fe423..2585f381943b3 100644
--- a/provisioner/terraform/resources.go
+++ b/provisioner/terraform/resources.go
@@ -435,12 +435,11 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
 
 			var corsBehavior proto.AppCORSBehavior
 			switch strings.ToLower(attrs.CORSBehavior) {
-			case "simple":
-				corsBehavior = proto.AppCORSBehavior_SIMPLE
 			case "passthru":
 				corsBehavior = proto.AppCORSBehavior_PASSTHRU
 			default:
-				return nil, xerrors.Errorf("invalid app CORS behavior %q", attrs.CORSBehavior)
+				corsBehavior = proto.AppCORSBehavior_SIMPLE
+				logger.Debug(ctx, "CORS behavior not set, defaulting to 'simple'")
 			}
 
 			for _, agents := range resourceAgents {

From 65f984f87cc33c7900cf4e820968ab37b4998db0 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Wed, 27 Nov 2024 11:40:21 +0000
Subject: [PATCH 05/17] Appeasing the linter

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/database/dbauthz/dbauthz_test.go     |  1 +
 coderd/database/dbgen/dbgen.go              |  1 +
 coderd/database/dbmem/dbmem.go              |  4 ++++
 coderd/workspaceapps/apptest/setup.go       | 11 ++++++-----
 coderd/workspaceapps/db_test.go             | 11 ++++++-----
 provisioner/terraform/resources.go          |  2 +-
 provisionersdk/proto/provisioner.pb.go      |  2 +-
 provisionersdk/proto/provisioner_drpc.pb.go |  2 +-
 8 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go
index 638829ae24ae5..8762a22cbd883 100644
--- a/coderd/database/dbauthz/dbauthz_test.go
+++ b/coderd/database/dbauthz/dbauthz_test.go
@@ -2567,6 +2567,7 @@ func (s *MethodTestSuite) TestSystemFunctions() {
 			ID:           uuid.New(),
 			Health:       database.WorkspaceAppHealthDisabled,
 			SharingLevel: database.AppSharingLevelOwner,
+			CORSBehavior: database.AppCorsBehaviorSimple,
 		}).Asserts(rbac.ResourceSystem, policy.ActionCreate)
 	}))
 	s.Run("InsertWorkspaceResourceMetadata", s.Subtest(func(db database.Store, check *expects) {
diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go
index 9c8696112dea8..271bb0056c4dc 100644
--- a/coderd/database/dbgen/dbgen.go
+++ b/coderd/database/dbgen/dbgen.go
@@ -618,6 +618,7 @@ func WorkspaceApp(t testing.TB, db database.Store, orig database.WorkspaceApp) d
 		Health:               takeFirst(orig.Health, database.WorkspaceAppHealthHealthy),
 		DisplayOrder:         takeFirst(orig.DisplayOrder, 1),
 		Hidden:               orig.Hidden,
+		CORSBehavior:         takeFirst(orig.CORSBehavior, database.AppCorsBehaviorSimple),
 	})
 	require.NoError(t, err, "insert app")
 	return resource
diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go
index cfab07333a6c2..7be45e76c2b79 100644
--- a/coderd/database/dbmem/dbmem.go
+++ b/coderd/database/dbmem/dbmem.go
@@ -8155,6 +8155,10 @@ func (q *FakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW
 		arg.SharingLevel = database.AppSharingLevelOwner
 	}
 
+	if arg.CORSBehavior == "" {
+		arg.CORSBehavior = database.AppCorsBehaviorSimple
+	}
+
 	// nolint:gosimple
 	workspaceApp := database.WorkspaceApp{
 		ID:                   arg.ID,
diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go
index 06ff54eec04fc..49cbb6b366219 100644
--- a/coderd/workspaceapps/apptest/setup.go
+++ b/coderd/workspaceapps/apptest/setup.go
@@ -32,11 +32,12 @@ import (
 )
 
 const (
-	proxyTestAgentName                        = "agent-name"
-	proxyTestAppNameFake                      = "test-app-fake"
-	proxyTestAppNameOwner                     = "test-app-owner"
-	proxyTestAppNameAuthenticated             = "test-app-authenticated"
-	proxyTestAppNamePublic                    = "test-app-public"
+	proxyTestAgentName            = "agent-name"
+	proxyTestAppNameFake          = "test-app-fake"
+	proxyTestAppNameOwner         = "test-app-owner"
+	proxyTestAppNameAuthenticated = "test-app-authenticated"
+	proxyTestAppNamePublic        = "test-app-public"
+	// nolint:gosec // Not a secret
 	proxyTestAppNameAuthenticatedCORSPassthru = "test-app-authenticated-cors-passthru"
 	proxyTestAppNamePublicCORSPassthru        = "test-app-public-cors-passthru"
 	proxyTestAppNameAuthenticatedCORSDefault  = "test-app-authenticated-cors-default"
diff --git a/coderd/workspaceapps/db_test.go b/coderd/workspaceapps/db_test.go
index bf364f1ce62b3..0ad7c5d99c1e7 100644
--- a/coderd/workspaceapps/db_test.go
+++ b/coderd/workspaceapps/db_test.go
@@ -280,11 +280,12 @@ func Test_ResolveRequest(t *testing.T) {
 						RegisteredClaims: jwtutils.RegisteredClaims{
 							Expiry: jwt.NewNumericDate(token.Expiry.Time()),
 						},
-						Request:     req,
-						UserID:      me.ID,
-						WorkspaceID: workspace.ID,
-						AgentID:     agentID,
-						AppURL:      appURL,
+						Request:      req,
+						UserID:       me.ID,
+						WorkspaceID:  workspace.ID,
+						AgentID:      agentID,
+						AppURL:       appURL,
+						CORSBehavior: token.CORSBehavior,
 					}, token)
 					require.NotZero(t, token.Expiry)
 					require.WithinDuration(t, time.Now().Add(workspaceapps.DefaultTokenExpiry), token.Expiry.Time(), time.Minute)
diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go
index 2585f381943b3..1111ff43d1b33 100644
--- a/provisioner/terraform/resources.go
+++ b/provisioner/terraform/resources.go
@@ -439,7 +439,7 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
 				corsBehavior = proto.AppCORSBehavior_PASSTHRU
 			default:
 				corsBehavior = proto.AppCORSBehavior_SIMPLE
-				logger.Debug(ctx, "CORS behavior not set, defaulting to 'simple'")
+				logger.Debug(ctx, "cors_behavior not set, defaulting to 'simple'", slog.F("address", convertAddressToLabel(resource.Address)))
 			}
 
 			for _, agents := range resourceAgents {
diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go
index 70922e445b343..b596d9cfef7a3 100644
--- a/provisionersdk/proto/provisioner.pb.go
+++ b/provisionersdk/proto/provisioner.pb.go
@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.30.0
-// 	protoc        v5.28.2
+// 	protoc        v4.23.3
 // source: provisionersdk/proto/provisioner.proto
 
 package proto
diff --git a/provisionersdk/proto/provisioner_drpc.pb.go b/provisionersdk/proto/provisioner_drpc.pb.go
index c9c54002439c2..de310e779dcaa 100644
--- a/provisionersdk/proto/provisioner_drpc.pb.go
+++ b/provisionersdk/proto/provisioner_drpc.pb.go
@@ -1,5 +1,5 @@
 // Code generated by protoc-gen-go-drpc. DO NOT EDIT.
-// protoc-gen-go-drpc version: (devel)
+// protoc-gen-go-drpc version: v0.0.33
 // source: provisionersdk/proto/provisioner.proto
 
 package proto

From 1f0b34c178bd759ba36ea66bc69388f8731f4925 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Wed, 27 Nov 2024 13:01:06 +0000
Subject: [PATCH 06/17] Move type def to codersdk

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/workspaceapps/apptest/setup.go |  7 +++----
 coderd/workspaceapps/cors/cors.go     | 15 ++++++---------
 coderd/workspaceapps/db.go            |  3 +--
 coderd/workspaceapps/proxy.go         |  6 +++---
 coderd/workspaceapps/token.go         |  3 +--
 codersdk/cors_behavior.go             | 17 +++++++++++++++++
 6 files changed, 31 insertions(+), 20 deletions(-)
 create mode 100644 codersdk/cors_behavior.go

diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go
index 49cbb6b366219..cb3f8139f20a0 100644
--- a/coderd/workspaceapps/apptest/setup.go
+++ b/coderd/workspaceapps/apptest/setup.go
@@ -22,7 +22,6 @@ import (
 	"github.com/coder/coder/v2/coderd/coderdtest"
 	"github.com/coder/coder/v2/coderd/workspaceapps"
 	"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
-	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 	"github.com/coder/coder/v2/codersdk/agentsdk"
 	"github.com/coder/coder/v2/cryptorand"
@@ -102,7 +101,7 @@ type App struct {
 	Path   string
 
 	// Control the behavior of CORS handling.
-	CORSBehavior cors.AppCORSBehavior
+	CORSBehavior codersdk.AppCORSBehavior
 }
 
 // Details are the full test details returned from setupProxyTestWithFactory.
@@ -271,7 +270,7 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
 		WorkspaceName: workspace.Name,
 		AgentName:     agnt.Name,
 		AppSlugOrPort: proxyTestAppNamePublicCORSPassthru,
-		CORSBehavior:  cors.AppCORSBehaviorPassthru,
+		CORSBehavior:  codersdk.AppCORSBehaviorPassthru,
 		Query:         proxyTestAppQuery,
 	}
 	details.Apps.AuthenticatedCORSPassthru = App{
@@ -279,7 +278,7 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
 		WorkspaceName: workspace.Name,
 		AgentName:     agnt.Name,
 		AppSlugOrPort: proxyTestAppNameAuthenticatedCORSPassthru,
-		CORSBehavior:  cors.AppCORSBehaviorPassthru,
+		CORSBehavior:  codersdk.AppCORSBehaviorPassthru,
 		Query:         proxyTestAppQuery,
 	}
 	details.Apps.PublicCORSDefault = App{
diff --git a/coderd/workspaceapps/cors/cors.go b/coderd/workspaceapps/cors/cors.go
index 0ee34cf390c5a..c204cb322f173 100644
--- a/coderd/workspaceapps/cors/cors.go
+++ b/coderd/workspaceapps/cors/cors.go
@@ -1,24 +1,21 @@
 package cors
 
-import "context"
+import (
+	"context"
 
-type AppCORSBehavior string
-
-const (
-	AppCORSBehaviorSimple   AppCORSBehavior = "simple"
-	AppCORSBehaviorPassthru AppCORSBehavior = "passthru"
+	"github.com/coder/coder/v2/codersdk"
 )
 
 type contextKeyBehavior struct{}
 
 // WithBehavior sets the CORS behavior for the given context.
-func WithBehavior(ctx context.Context, behavior AppCORSBehavior) context.Context {
+func WithBehavior(ctx context.Context, behavior codersdk.AppCORSBehavior) context.Context {
 	return context.WithValue(ctx, contextKeyBehavior{}, behavior)
 }
 
 // HasBehavior returns true if the given context has the specified CORS behavior.
-func HasBehavior(ctx context.Context, behavior AppCORSBehavior) bool {
+func HasBehavior(ctx context.Context, behavior codersdk.AppCORSBehavior) bool {
 	val := ctx.Value(contextKeyBehavior{})
-	b, ok := val.(AppCORSBehavior)
+	b, ok := val.(codersdk.AppCORSBehavior)
 	return ok && b == behavior
 }
diff --git a/coderd/workspaceapps/db.go b/coderd/workspaceapps/db.go
index 1a16a680dfb4d..2cd56fdd7d0dd 100644
--- a/coderd/workspaceapps/db.go
+++ b/coderd/workspaceapps/db.go
@@ -25,7 +25,6 @@ import (
 	"github.com/coder/coder/v2/coderd/jwtutils"
 	"github.com/coder/coder/v2/coderd/rbac"
 	"github.com/coder/coder/v2/coderd/rbac/policy"
-	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 )
 
@@ -132,7 +131,7 @@ func (p *DBTokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r *
 	if dbReq.AppURL != nil {
 		token.AppURL = dbReq.AppURL.String()
 	}
-	token.CORSBehavior = cors.AppCORSBehavior(dbReq.AppCORSBehavior)
+	token.CORSBehavior = codersdk.AppCORSBehavior(dbReq.AppCORSBehavior)
 
 	// Verify the user has access to the app.
 	authed, warnings, err := p.authorizeRequest(r.Context(), authz, dbReq)
diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go
index da6a075957837..87f439ad6fe24 100644
--- a/coderd/workspaceapps/proxy.go
+++ b/coderd/workspaceapps/proxy.go
@@ -441,7 +441,7 @@ func (s *Server) determineCORSBehavior(token *SignedToken, app appurl.Applicatio
 		corsHandler := httpmw.WorkspaceAppCors(s.HostnameRegex, app)(next)
 
 		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-			var behavior cors.AppCORSBehavior
+			var behavior codersdk.AppCORSBehavior
 			if token != nil {
 				behavior = token.CORSBehavior
 			}
@@ -452,7 +452,7 @@ func (s *Server) determineCORSBehavior(token *SignedToken, app appurl.Applicatio
 			r = r.WithContext(cors.WithBehavior(r.Context(), behavior))
 
 			switch behavior {
-			case cors.AppCORSBehaviorPassthru:
+			case codersdk.AppCORSBehaviorPassthru:
 				// Bypass the CORS middleware.
 				next.ServeHTTP(rw, r)
 				return
@@ -595,7 +595,7 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT
 
 	proxy.ModifyResponse = func(r *http.Response) error {
 		// If passthru behavior is set, disable our CORS header stripping.
-		if cors.HasBehavior(r.Request.Context(), cors.AppCORSBehaviorPassthru) {
+		if cors.HasBehavior(r.Request.Context(), codersdk.AppCORSBehaviorPassthru) {
 			return nil
 		}
 
diff --git a/coderd/workspaceapps/token.go b/coderd/workspaceapps/token.go
index 72b8db2bf8129..32b0641169693 100644
--- a/coderd/workspaceapps/token.go
+++ b/coderd/workspaceapps/token.go
@@ -11,7 +11,6 @@ import (
 
 	"github.com/coder/coder/v2/coderd/cryptokeys"
 	"github.com/coder/coder/v2/coderd/jwtutils"
-	"github.com/coder/coder/v2/coderd/workspaceapps/cors"
 	"github.com/coder/coder/v2/codersdk"
 )
 
@@ -27,7 +26,7 @@ type SignedToken struct {
 	WorkspaceID  uuid.UUID            `json:"workspace_id"`
 	AgentID      uuid.UUID            `json:"agent_id"`
 	AppURL       string               `json:"app_url"`
-	CORSBehavior cors.AppCORSBehavior `json:"cors_behavior"`
+	CORSBehavior codersdk.AppCORSBehavior `json:"cors_behavior"`
 }
 
 // MatchesRequest returns true if the token matches the request. Any token that
diff --git a/codersdk/cors_behavior.go b/codersdk/cors_behavior.go
new file mode 100644
index 0000000000000..8fd8b9a893e37
--- /dev/null
+++ b/codersdk/cors_behavior.go
@@ -0,0 +1,17 @@
+package codersdk
+
+import "golang.org/x/xerrors"
+
+type AppCORSBehavior string
+
+const (
+	AppCORSBehaviorSimple   AppCORSBehavior = "simple"
+	AppCORSBehaviorPassthru AppCORSBehavior = "passthru"
+)
+
+func (c AppCORSBehavior) Validate() error {
+	if c != AppCORSBehaviorSimple && c != AppCORSBehaviorPassthru {
+		return xerrors.New("Invalid CORS behavior.")
+	}
+	return nil
+}

From 5be547762bb9f8ad9be9d9b593b5ca6ed60f3ec8 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Wed, 27 Nov 2024 15:23:54 +0000
Subject: [PATCH 07/17] Appease the linter, again

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/workspaceapps/token.go  | 8 ++++----
 site/src/api/typesGenerated.ts | 4 ++++
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/coderd/workspaceapps/token.go b/coderd/workspaceapps/token.go
index 32b0641169693..d46f7b9335212 100644
--- a/coderd/workspaceapps/token.go
+++ b/coderd/workspaceapps/token.go
@@ -22,10 +22,10 @@ type SignedToken struct {
 	// Request details.
 	Request `json:"request"`
 
-	UserID       uuid.UUID            `json:"user_id"`
-	WorkspaceID  uuid.UUID            `json:"workspace_id"`
-	AgentID      uuid.UUID            `json:"agent_id"`
-	AppURL       string               `json:"app_url"`
+	UserID       uuid.UUID                `json:"user_id"`
+	WorkspaceID  uuid.UUID                `json:"workspace_id"`
+	AgentID      uuid.UUID                `json:"agent_id"`
+	AppURL       string                   `json:"app_url"`
 	CORSBehavior codersdk.AppCORSBehavior `json:"cors_behavior"`
 }
 
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index c1b409013b6d7..dc63e7f70fd54 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -2137,6 +2137,10 @@ export const APIKeyScopes: APIKeyScope[] = ["all", "application_connect"]
 export type AgentSubsystem = "envbox" | "envbuilder" | "exectrace"
 export const AgentSubsystems: AgentSubsystem[] = ["envbox", "envbuilder", "exectrace"]
 
+// From codersdk/cors_behavior.go
+export type AppCORSBehavior = "passthru" | "simple"
+export const AppCORSBehaviors: AppCORSBehavior[] = ["passthru", "simple"]
+
 // From codersdk/audit.go
 export type AuditAction = "create" | "delete" | "login" | "logout" | "register" | "request_password_reset" | "start" | "stop" | "write"
 export const AuditActions: AuditAction[] = ["create", "delete", "login", "logout", "register", "request_password_reset", "start", "stop", "write"]

From ddbbaa948b9e288896d7e4643cfd028371a2397c Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Thu, 28 Nov 2024 11:40:04 +0000
Subject: [PATCH 08/17] Fix migration num

Signed-off-by: Danny Kopping <danny@coder.com>
---
 ...avior.down.sql => 000278_workspace_app_cors_behavior.down.sql} | 0
 ..._behavior.up.sql => 000278_workspace_app_cors_behavior.up.sql} | 0
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename coderd/database/migrations/{000277_workspace_app_cors_behavior.down.sql => 000278_workspace_app_cors_behavior.down.sql} (100%)
 rename coderd/database/migrations/{000277_workspace_app_cors_behavior.up.sql => 000278_workspace_app_cors_behavior.up.sql} (100%)

diff --git a/coderd/database/migrations/000277_workspace_app_cors_behavior.down.sql b/coderd/database/migrations/000278_workspace_app_cors_behavior.down.sql
similarity index 100%
rename from coderd/database/migrations/000277_workspace_app_cors_behavior.down.sql
rename to coderd/database/migrations/000278_workspace_app_cors_behavior.down.sql
diff --git a/coderd/database/migrations/000277_workspace_app_cors_behavior.up.sql b/coderd/database/migrations/000278_workspace_app_cors_behavior.up.sql
similarity index 100%
rename from coderd/database/migrations/000277_workspace_app_cors_behavior.up.sql
rename to coderd/database/migrations/000278_workspace_app_cors_behavior.up.sql

From 63c1852d30bbad016c3a13ddc3752dff4685e42b Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Thu, 28 Nov 2024 12:04:46 +0000
Subject: [PATCH 09/17] Removing unnecessary header check

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/workspaceapps/apptest/apptest.go | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go
index d2918e571a98e..ac7fbbd060073 100644
--- a/coderd/workspaceapps/apptest/apptest.go
+++ b/coderd/workspaceapps/apptest/apptest.go
@@ -529,7 +529,6 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 			// Check CORS headers are passed through
 			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Credentials"), resp.Header.Get("Access-Control-Allow-Credentials"))
 			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
 		})
 
@@ -556,7 +555,6 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 			// Check CORS headers are passed through
 			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Credentials"), resp.Header.Get("Access-Control-Allow-Credentials"))
 			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
 		})
 
@@ -583,7 +581,6 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 			// Check CORS headers are passed through
 			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Credentials"), resp.Header.Get("Access-Control-Allow-Credentials"))
 			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
 		})
 	})

From 976cd78d90a878bb88fd5a1d7c9cdd3a08409d0e Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Thu, 28 Nov 2024 14:54:32 +0000
Subject: [PATCH 10/17] Improve tests

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/workspaceapps/apptest/apptest.go | 192 +++++++++++++-----------
 1 file changed, 101 insertions(+), 91 deletions(-)

diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go
index ac7fbbd060073..a677778114ceb 100644
--- a/coderd/workspaceapps/apptest/apptest.go
+++ b/coderd/workspaceapps/apptest/apptest.go
@@ -472,7 +472,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 		})
 	})
 
-	t.Run("CORS", func(t *testing.T) {
+	t.Run("WorkspaceApplicationCORS", func(t *testing.T) {
 		t.Parallel()
 
 		// Set up test headers that should be returned by the app
@@ -481,108 +481,118 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			"Access-Control-Allow-Methods": []string{"GET, POST, OPTIONS"},
 		}
 
-		t.Run("UnauthenticatedPassthruRejected", func(t *testing.T) {
-			t.Parallel()
-
-			ctx := testutil.Context(t, testutil.WaitLong)
-
-			appDetails := setupProxyTest(t, &DeploymentOptions{
-				headers: testHeaders,
-			})
-
-			// Given: an unauthenticated client
-			client := appDetails.AppClient(t)
-			client.SetSessionToken("")
-
-			// When: a request is made to an authenticated app with passthru CORS behavior
-			resp, err := requestWithRetries(ctx, t, client, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.AuthenticatedCORSPassthru).String(), nil)
-			require.NoError(t, err)
-			defer resp.Body.Close()
-
-			// Then: the request is redirected to login because even though CORS is passthru,
-			// the request must still be authenticated first
-			require.Equal(t, http.StatusSeeOther, resp.StatusCode)
-			gotLocation, err := resp.Location()
-			require.NoError(t, err)
-			require.Equal(t, appDetails.SDKClient.URL.Host, gotLocation.Host)
-			require.Equal(t, "/api/v2/applications/auth-redirect", gotLocation.Path)
-		})
-
-		t.Run("AuthenticatedPassthruOK", func(t *testing.T) {
-			t.Parallel()
-
-			ctx := testutil.Context(t, testutil.WaitLong)
-
-			appDetails := setupProxyTest(t, &DeploymentOptions{
-				headers: testHeaders,
-			})
-
-			userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
-			userAppClient := appDetails.AppClient(t)
-			userAppClient.SetSessionToken(userClient.SessionToken())
-
-			// Given: an authenticated app with passthru CORS behavior
-			resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.AuthenticatedCORSPassthru).String(), nil)
-			require.NoError(t, err)
-			defer resp.Body.Close()
-			require.Equal(t, http.StatusOK, resp.StatusCode)
-
-			// Check CORS headers are passed through
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
+		appDetails := setupProxyTest(t, &DeploymentOptions{
+			headers: testHeaders,
 		})
 
-		t.Run("UnauthenticatedPublicPassthruOK", func(t *testing.T) {
-			t.Parallel()
+		unauthenticatedClient := func(t *testing.T, appDetails *Details) *codersdk.Client {
+			c := appDetails.AppClient(t)
+			c.SetSessionToken("")
+			return c
+		}
 
-			ctx := testutil.Context(t, testutil.WaitLong)
+		authenticatedClient := func(t *testing.T, appDetails *Details) *codersdk.Client {
+			uc, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
+			c := appDetails.AppClient(t)
+			c.SetSessionToken(uc.SessionToken())
+			return c
+		}
 
-			appDetails := setupProxyTest(t, &DeploymentOptions{
-				headers: testHeaders,
-			})
+		ownerClient := func(t *testing.T, appDetails *Details) *codersdk.Client {
+			return appDetails.SDKClient
+		}
 
-			// Given: an unauthenticated client
-			client := appDetails.AppClient(t)
-			client.SetSessionToken("")
+		tests := []struct {
+			name                string
+			app                 App
+			client              func(t *testing.T, appDetails *Details) *codersdk.Client
+			expectedStatusCode  int
+			expectedCORSHeaders bool
+		}{
+			// Public
+			{
+				name:                "Default/Public",
+				app:                 appDetails.Apps.PublicCORSDefault,
+				client:              unauthenticatedClient,
+				expectedStatusCode:  http.StatusOK,
+				expectedCORSHeaders: false,
+			},
+			{
+				name:                "Passthru/Public",
+				app:                 appDetails.Apps.PublicCORSPassthru,
+				client:              unauthenticatedClient,
+				expectedStatusCode:  http.StatusOK,
+				expectedCORSHeaders: true,
+			},
+			// Authenticated
+			{
+				name:                "Default/Authenticated",
+				app:                 appDetails.Apps.AuthenticatedCORSDefault,
+				expectedCORSHeaders: false,
+				client:              authenticatedClient,
+				expectedStatusCode:  http.StatusOK,
+			},
+			{
+				name:                "Passthru/Authenticated",
+				app:                 appDetails.Apps.AuthenticatedCORSPassthru,
+				expectedCORSHeaders: true,
+				client:              authenticatedClient,
+				expectedStatusCode:  http.StatusOK,
+			},
+			{
+				// The CORS behavior will not affect unauthenticated requests.
+				// The request will be redirected to the login page.
+				name:                "Passthru/Unauthenticated",
+				app:                 appDetails.Apps.AuthenticatedCORSPassthru,
+				expectedCORSHeaders: false,
+				client:              unauthenticatedClient,
+				expectedStatusCode:  http.StatusSeeOther,
+			},
+			// Owner
+			{
+				name:                "Default/Owner",
+				app:                 appDetails.Apps.AuthenticatedCORSDefault,
+				expectedCORSHeaders: false,
+				client:              ownerClient,
+				expectedStatusCode:  http.StatusOK,
+			},
+			{
+				name:                "Passthru/Owner",
+				app:                 appDetails.Apps.AuthenticatedCORSPassthru,
+				expectedCORSHeaders: true,
+				client:              ownerClient,
+				expectedStatusCode:  http.StatusOK,
+			},
+		}
 
-			// When: a request is made to a public app with passthru CORS behavior
-			resp, err := requestWithRetries(ctx, t, client, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.PublicCORSPassthru).String(), nil)
-			require.NoError(t, err)
-			defer resp.Body.Close()
+		for _, tc := range tests {
+			t.Run(tc.name, func(t *testing.T) {
+				t.Parallel()
 
-			// Then: the request succeeds because the app is public
-			require.Equal(t, http.StatusOK, resp.StatusCode)
+				ctx := testutil.Context(t, testutil.WaitLong)
 
-			// Check CORS headers are passed through
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
-		})
+				// Given: a client
+				client := tc.client(t, appDetails)
 
-		t.Run("AuthenticatedPublicPassthruOK", func(t *testing.T) {
-			t.Parallel()
+				// When: a request is made to an authenticated app with a specified CORS behavior
+				resp, err := requestWithRetries(ctx, t, client, http.MethodGet, appDetails.SubdomainAppURL(tc.app).String(), nil)
+				require.NoError(t, err)
+				defer resp.Body.Close()
 
-			ctx := testutil.Context(t, testutil.WaitLong)
+				// Then: the request must match expectations
+				require.Equal(t, tc.expectedStatusCode, resp.StatusCode)
+				require.NoError(t, err)
 
-			appDetails := setupProxyTest(t, &DeploymentOptions{
-				headers: testHeaders,
+				// Then: the CORS headers must match expectations
+				if tc.expectedCORSHeaders {
+					require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
+					require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
+				} else {
+					require.Empty(t, resp.Header.Get("Access-Control-Allow-Origin"))
+					require.Empty(t, resp.Header.Get("Access-Control-Allow-Methods"))
+				}
 			})
-
-			userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
-			userAppClient := appDetails.AppClient(t)
-			userAppClient.SetSessionToken(userClient.SessionToken())
-
-			// Given: an authenticated client accessing a public app with passthru CORS behavior
-			resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.PublicCORSPassthru).String(), nil)
-			require.NoError(t, err)
-			defer resp.Body.Close()
-
-			// Then: the request succeeds because the app is public
-			require.Equal(t, http.StatusOK, resp.StatusCode)
-
-			// Check CORS headers are passed through
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-			require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
-		})
+		}
 	})
 
 	t.Run("WorkspaceApplicationAuth", func(t *testing.T) {

From 3dc00e1dc58d50f0c033b9c63d79e8fda9afcbb4 Mon Sep 17 00:00:00 2001
From: Danny Kopping <danny@coder.com>
Date: Fri, 29 Nov 2024 13:13:26 +0000
Subject: [PATCH 11/17] Comprehensive CORS test suite for both request &
 response

Signed-off-by: Danny Kopping <danny@coder.com>
---
 coderd/workspaceapps/apptest/apptest.go | 411 ++++++++++++++++++++----
 coderd/workspaceapps/apptest/setup.go   |  39 ++-
 2 files changed, 363 insertions(+), 87 deletions(-)

diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go
index a677778114ceb..b66e4cbbbc6be 100644
--- a/coderd/workspaceapps/apptest/apptest.go
+++ b/coderd/workspaceapps/apptest/apptest.go
@@ -475,15 +475,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 	t.Run("WorkspaceApplicationCORS", func(t *testing.T) {
 		t.Parallel()
 
-		// Set up test headers that should be returned by the app
-		testHeaders := http.Header{
-			"Access-Control-Allow-Origin":  []string{"*"},
-			"Access-Control-Allow-Methods": []string{"GET, POST, OPTIONS"},
-		}
-
-		appDetails := setupProxyTest(t, &DeploymentOptions{
-			headers: testHeaders,
-		})
+		const external = "https://example.com"
 
 		unauthenticatedClient := func(t *testing.T, appDetails *Details) *codersdk.Client {
 			c := appDetails.AppClient(t)
@@ -498,70 +490,310 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			return c
 		}
 
-		ownerClient := func(t *testing.T, appDetails *Details) *codersdk.Client {
-			return appDetails.SDKClient
+		ownSubdomain := func(details *Details, app App) string {
+			url := details.SubdomainAppURL(app)
+			return url.Scheme + "://" + url.Host
+		}
+
+		externalOrigin := func(*Details, App) string {
+			return external
 		}
 
 		tests := []struct {
-			name                string
-			app                 App
-			client              func(t *testing.T, appDetails *Details) *codersdk.Client
-			expectedStatusCode  int
-			expectedCORSHeaders bool
+			name                 string
+			app                  func(details *Details) App
+			client               func(t *testing.T, appDetails *Details) *codersdk.Client
+			behavior             codersdk.AppCORSBehavior
+			httpMethod           string
+			origin               func(details *Details, app App) string
+			expectedStatusCode   int
+			checkRequestHeaders  func(t *testing.T, origin string, req http.Header)
+			checkResponseHeaders func(t *testing.T, origin string, resp http.Header)
 		}{
 			// Public
 			{
-				name:                "Default/Public",
-				app:                 appDetails.Apps.PublicCORSDefault,
-				client:              unauthenticatedClient,
-				expectedStatusCode:  http.StatusOK,
-				expectedCORSHeaders: false,
+				// The default behavior is to accept preflight requests from the request origin if it matches the app's own subdomain.
+				name:               "Default/Public/Preflight/Subdomain",
+				app:                func(details *Details) App { return details.Apps.PublicCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             unauthenticatedClient,
+				httpMethod:         http.MethodOptions,
+				origin:             ownSubdomain,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Contains(t, resp.Get("Access-Control-Allow-Methods"), http.MethodGet)
+					assert.Equal(t, "true", resp.Get("Access-Control-Allow-Credentials"))
+				},
 			},
 			{
-				name:                "Passthru/Public",
-				app:                 appDetails.Apps.PublicCORSPassthru,
-				client:              unauthenticatedClient,
-				expectedStatusCode:  http.StatusOK,
-				expectedCORSHeaders: true,
+				// The default behavior is to reject preflight requests from origins other than the app's own subdomain.
+				name:               "Default/Public/Preflight/External",
+				app:                func(details *Details) App { return details.Apps.PublicCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             unauthenticatedClient,
+				httpMethod:         http.MethodOptions,
+				origin:             externalOrigin,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					// We don't add a valid Allow-Origin header for requests we won't proxy.
+					assert.Empty(t, resp.Get("Access-Control-Allow-Origin"))
+				},
+			},
+			{
+				// A request without an Origin header would be rejected by an actual browser since it lacks CORS headers.
+				name:               "Default/Public/GET/NoOrigin",
+				app:                func(details *Details) App { return details.Apps.PublicCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             unauthenticatedClient,
+				origin:             func(*Details, App) string { return "" },
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Empty(t, resp.Get("Access-Control-Allow-Origin"))
+					assert.Empty(t, resp.Get("Access-Control-Allow-Headers"))
+					assert.Empty(t, resp.Get("Access-Control-Allow-Credentials"))
+					// Added by the app handler.
+					assert.Equal(t, "simple", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// The passthru behavior will pass through the request headers to the upstream app.
+				name:               "Passthru/Public/Preflight/Subdomain",
+				app:                func(details *Details) App { return details.Apps.PublicCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusOK,
+				checkRequestHeaders: func(t *testing.T, origin string, req http.Header) {
+					assert.Equal(t, origin, req.Get("Origin"))
+					assert.Equal(t, "GET", req.Get("Access-Control-Request-Method"))
+				},
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, http.MethodGet, resp.Get("Access-Control-Allow-Methods"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// Identical to the previous test, but the origin is different.
+				name:               "Passthru/Public/PreflightOther",
+				app:                func(details *Details) App { return details.Apps.PublicCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusOK,
+				checkRequestHeaders: func(t *testing.T, origin string, req http.Header) {
+					assert.Equal(t, origin, req.Get("Origin"))
+					assert.Equal(t, "GET", req.Get("Access-Control-Request-Method"))
+					assert.Equal(t, "X-Got-Host", req.Get("Access-Control-Request-Headers"))
+				},
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, http.MethodGet, resp.Get("Access-Control-Allow-Methods"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// A request without an Origin header would be rejected by an actual browser since it lacks CORS headers.
+				name:               "Passthru/Public/GET/NoOrigin",
+				app:                func(details *Details) App { return details.Apps.PublicCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             func(*Details, App) string { return "" },
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Empty(t, resp.Get("Access-Control-Allow-Origin"))
+					assert.Empty(t, resp.Get("Access-Control-Allow-Headers"))
+					assert.Empty(t, resp.Get("Access-Control-Allow-Credentials"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
 			},
 			// Authenticated
 			{
-				name:                "Default/Authenticated",
-				app:                 appDetails.Apps.AuthenticatedCORSDefault,
-				expectedCORSHeaders: false,
-				client:              authenticatedClient,
-				expectedStatusCode:  http.StatusOK,
+				// Same behavior as Default/Public/Preflight/Subdomain.
+				name:               "Default/Authenticated/Preflight/Subdomain",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             authenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Contains(t, resp.Get("Access-Control-Allow-Methods"), http.MethodGet)
+					assert.Equal(t, "true", resp.Get("Access-Control-Allow-Credentials"))
+					assert.Equal(t, "X-Got-Host", resp.Get("Access-Control-Allow-Headers"))
+				},
+			},
+			{
+				// Same behavior as Default/Public/Preflight/External.
+				name:               "Default/Authenticated/Preflight/External",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             authenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Empty(t, resp.Get("Access-Control-Allow-Origin"))
+				},
+			},
+			{
+				// An authenticated request to the app is allowed from its own subdomain.
+				name:               "Default/Authenticated/GET/Subdomain",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             authenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, "true", resp.Get("Access-Control-Allow-Credentials"))
+					// Added by the app handler.
+					assert.Equal(t, "simple", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// An authenticated request to the app is allowed from an external origin.
+				// The origin doesn't match the app's own subdomain, so the CORS headers are not added.
+				name:               "Default/Authenticated/GET/External",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSDefault },
+				behavior:           codersdk.AppCORSBehaviorSimple,
+				client:             authenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Empty(t, resp.Get("Access-Control-Allow-Origin"))
+					assert.Empty(t, resp.Get("Access-Control-Allow-Headers"))
+					assert.Empty(t, resp.Get("Access-Control-Allow-Credentials"))
+					// Added by the app handler.
+					assert.Equal(t, "simple", resp.Get("X-CORS-Handler"))
+				},
 			},
 			{
-				name:                "Passthru/Authenticated",
-				app:                 appDetails.Apps.AuthenticatedCORSPassthru,
-				expectedCORSHeaders: true,
-				client:              authenticatedClient,
-				expectedStatusCode:  http.StatusOK,
+				// The request is rejected because the client is unauthenticated.
+				name:               "Passthru/Unauthenticated/Preflight/Subdomain",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusSeeOther,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.NotEmpty(t, resp.Get("Location"))
+				},
 			},
 			{
-				// The CORS behavior will not affect unauthenticated requests.
-				// The request will be redirected to the login page.
-				name:                "Passthru/Unauthenticated",
-				app:                 appDetails.Apps.AuthenticatedCORSPassthru,
-				expectedCORSHeaders: false,
-				client:              unauthenticatedClient,
-				expectedStatusCode:  http.StatusSeeOther,
+				// Same behavior as the above test, but the origin is different.
+				name:               "Passthru/Unauthenticated/Preflight/External",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusSeeOther,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.NotEmpty(t, resp.Get("Location"))
+				},
 			},
-			// Owner
 			{
-				name:                "Default/Owner",
-				app:                 appDetails.Apps.AuthenticatedCORSDefault,
-				expectedCORSHeaders: false,
-				client:              ownerClient,
-				expectedStatusCode:  http.StatusOK,
+				// The request is rejected because the client is unauthenticated.
+				name:               "Passthru/Unauthenticated/GET/Subdomain",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusSeeOther,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.NotEmpty(t, resp.Get("Location"))
+				},
 			},
 			{
-				name:                "Passthru/Owner",
-				app:                 appDetails.Apps.AuthenticatedCORSPassthru,
-				expectedCORSHeaders: true,
-				client:              ownerClient,
-				expectedStatusCode:  http.StatusOK,
+				// Same behavior as the above test, but the origin is different.
+				name:               "Passthru/Unauthenticated/GET/External",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             unauthenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusSeeOther,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.NotEmpty(t, resp.Get("Location"))
+				},
+			},
+			{
+				// The request is allowed because the client is authenticated.
+				name:               "Passthru/Authenticated/Preflight/Subdomain",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             authenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, http.MethodGet, resp.Get("Access-Control-Allow-Methods"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// Same behavior as the above test, but the origin is different.
+				name:               "Passthru/Authenticated/Preflight/External",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             authenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodOptions,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, http.MethodGet, resp.Get("Access-Control-Allow-Methods"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// The request is allowed because the client is authenticated.
+				name:               "Passthru/Authenticated/GET/Subdomain",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             authenticatedClient,
+				origin:             ownSubdomain,
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, http.MethodGet, resp.Get("Access-Control-Allow-Methods"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
+			},
+			{
+				// Same behavior as the above test, but the origin is different.
+				name:               "Passthru/Authenticated/GET/External",
+				app:                func(details *Details) App { return details.Apps.AuthenticatedCORSPassthru },
+				behavior:           codersdk.AppCORSBehaviorPassthru,
+				client:             authenticatedClient,
+				origin:             externalOrigin,
+				httpMethod:         http.MethodGet,
+				expectedStatusCode: http.StatusOK,
+				checkResponseHeaders: func(t *testing.T, origin string, resp http.Header) {
+					assert.Equal(t, origin, resp.Get("Access-Control-Allow-Origin"))
+					assert.Equal(t, http.MethodGet, resp.Get("Access-Control-Allow-Methods"))
+					// Added by the app handler.
+					assert.Equal(t, "passthru", resp.Get("X-CORS-Handler"))
+				},
 			},
 		}
 
@@ -571,26 +803,65 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 
 				ctx := testutil.Context(t, testutil.WaitLong)
 
-				// Given: a client
-				client := tc.client(t, appDetails)
+				var reqHeaders http.Header
+				// Setup an HTTP handler which is the "app"; this handler conditionally responds
+				// to requests based on the CORS behavior
+				appDetails := setupProxyTest(t, &DeploymentOptions{
+					handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+						_, err := r.Cookie(codersdk.SessionTokenCookie)
+						assert.ErrorIs(t, err, http.ErrNoCookie)
+
+						// Store the request headers for later assertions
+						reqHeaders = r.Header
+
+						switch tc.behavior {
+						case codersdk.AppCORSBehaviorPassthru:
+							w.Header().Set("X-CORS-Handler", "passthru")
+
+							// Only allow GET and OPTIONS requests
+							if r.Method != http.MethodGet && r.Method != http.MethodOptions {
+								w.WriteHeader(http.StatusMethodNotAllowed)
+								return
+							}
+
+							// If the Origin header is present, add the CORS headers.
+							if origin := r.Header.Get("Origin"); origin != "" {
+								w.Header().Set("Access-Control-Allow-Credentials", "true")
+								w.Header().Set("Access-Control-Allow-Origin", origin)
+								w.Header().Set("Access-Control-Allow-Methods", http.MethodGet)
+							}
+
+							w.WriteHeader(http.StatusOK)
+						case codersdk.AppCORSBehaviorSimple:
+							w.Header().Set("X-CORS-Handler", "simple")
+						}
+					}),
+				})
 
-				// When: a request is made to an authenticated app with a specified CORS behavior
-				resp, err := requestWithRetries(ctx, t, client, http.MethodGet, appDetails.SubdomainAppURL(tc.app).String(), nil)
+				// Given: a client and a workspace app
+				client := tc.client(t, appDetails)
+				path := appDetails.SubdomainAppURL(tc.app(appDetails)).String()
+				origin := tc.origin(appDetails, tc.app(appDetails))
+
+				// When: a preflight request is made to an app with a specified CORS behavior
+				resp, err := requestWithRetries(ctx, t, client, tc.httpMethod, path, nil, func(r *http.Request) {
+					// Mimic non-browser clients that don't send the Origin header.
+					if origin != "" {
+						r.Header.Set("Origin", origin)
+					}
+					r.Header.Set("Access-Control-Request-Method", "GET")
+					r.Header.Set("Access-Control-Request-Headers", "X-Got-Host")
+				})
 				require.NoError(t, err)
 				defer resp.Body.Close()
 
-				// Then: the request must match expectations
-				require.Equal(t, tc.expectedStatusCode, resp.StatusCode)
-				require.NoError(t, err)
-
-				// Then: the CORS headers must match expectations
-				if tc.expectedCORSHeaders {
-					require.Equal(t, testHeaders.Get("Access-Control-Allow-Origin"), resp.Header.Get("Access-Control-Allow-Origin"))
-					require.Equal(t, testHeaders.Get("Access-Control-Allow-Methods"), resp.Header.Get("Access-Control-Allow-Methods"))
-				} else {
-					require.Empty(t, resp.Header.Get("Access-Control-Allow-Origin"))
-					require.Empty(t, resp.Header.Get("Access-Control-Allow-Methods"))
+				// Then: the request & response must match expectations
+				assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
+				assert.NoError(t, err)
+				if tc.checkRequestHeaders != nil {
+					tc.checkRequestHeaders(t, origin, reqHeaders)
 				}
+				tc.checkResponseHeaders(t, origin, resp.Header)
 			})
 		}
 	})
@@ -1511,7 +1782,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
 			forceURLTransport(t, client)
 
 			// Create workspace.
-			port := appServer(t, nil, false)
+			port := appServer(t, nil, false, nil)
 			workspace, _ = createWorkspaceWithApps(t, client, user.OrganizationIDs[0], user, port, false)
 
 			// Verify that the apps have the correct sharing levels set.
diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go
index cb3f8139f20a0..c8c094479292c 100644
--- a/coderd/workspaceapps/apptest/setup.go
+++ b/coderd/workspaceapps/apptest/setup.go
@@ -65,6 +65,7 @@ type DeploymentOptions struct {
 	noWorkspace bool
 	port        uint16
 	headers     http.Header
+	handler     http.Handler
 }
 
 // Deployment is a license-agnostic deployment with all the fields that apps
@@ -214,7 +215,7 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
 	}
 
 	if opts.port == 0 {
-		opts.port = appServer(t, opts.headers, opts.ServeHTTPS)
+		opts.port = appServer(t, opts.headers, opts.ServeHTTPS, opts.handler)
 	}
 	workspace, agnt := createWorkspaceWithApps(t, deployment.SDKClient, deployment.FirstUser.OrganizationID, me, opts.port, opts.ServeHTTPS)
 
@@ -300,25 +301,29 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
 }
 
 //nolint:revive
-func appServer(t *testing.T, headers http.Header, isHTTPS bool) uint16 {
-	server := httptest.NewUnstartedServer(
-		http.HandlerFunc(
-			func(w http.ResponseWriter, r *http.Request) {
-				_, err := r.Cookie(codersdk.SessionTokenCookie)
-				assert.ErrorIs(t, err, http.ErrNoCookie)
-				w.Header().Set("X-Forwarded-For", r.Header.Get("X-Forwarded-For"))
-				w.Header().Set("X-Got-Host", r.Host)
-				for name, values := range headers {
-					for _, value := range values {
-						w.Header().Add(name, value)
-					}
+func appServer(t *testing.T, headers http.Header, isHTTPS bool, handler http.Handler) uint16 {
+	defaultHandler := http.HandlerFunc(
+		func(w http.ResponseWriter, r *http.Request) {
+			_, err := r.Cookie(codersdk.SessionTokenCookie)
+			assert.ErrorIs(t, err, http.ErrNoCookie)
+			w.Header().Set("X-Forwarded-For", r.Header.Get("X-Forwarded-For"))
+			w.Header().Set("X-Got-Host", r.Host)
+			for name, values := range headers {
+				for _, value := range values {
+					w.Header().Add(name, value)
 				}
-				w.WriteHeader(http.StatusOK)
-				_, _ = w.Write([]byte(proxyTestAppBody))
-			},
-		),
+			}
+			w.WriteHeader(http.StatusOK)
+			_, _ = w.Write([]byte(proxyTestAppBody))
+		},
 	)
 
+	if handler == nil {
+		handler = defaultHandler
+	}
+
+	server := httptest.NewUnstartedServer(handler)
+
 	server.Config.ReadHeaderTimeout = time.Minute
 	if isHTTPS {
 		server.StartTLS()

From 326696773b927745cfb67a811dd5d00336b0163e Mon Sep 17 00:00:00 2001
From: Steven Masley <stevenmasley@coder.com>
Date: Mon, 28 Apr 2025 21:05:45 +0000
Subject: [PATCH 12/17] make gen

---
 coderd/database/dump.sql               |   8 +-
 coderd/database/models.go              |  10 +-
 coderd/database/queries.sql.go         |  57 +-
 docs/admin/security/audit-logs.md      |   2 +-
 enterprise/audit/table.go              |   1 +
 provisionersdk/proto/provisioner.pb.go | 809 +++++++++++++------------
 site/e2e/provisionerGenerated.ts       |  80 +--
 site/src/api/typesGenerated.ts         |  31 +-
 8 files changed, 474 insertions(+), 524 deletions(-)

diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 83d998b2b9a3e..5b56820f46de9 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -10,6 +10,11 @@ CREATE TYPE api_key_scope AS ENUM (
     'application_connect'
 );
 
+CREATE TYPE app_cors_behavior AS ENUM (
+    'simple',
+    'passthru'
+);
+
 CREATE TYPE app_sharing_level AS ENUM (
     'owner',
     'authenticated',
@@ -1933,7 +1938,8 @@ CREATE TABLE workspace_apps (
     external boolean DEFAULT false NOT NULL,
     display_order integer DEFAULT 0 NOT NULL,
     hidden boolean DEFAULT false NOT NULL,
-    open_in workspace_app_open_in DEFAULT 'slim-window'::workspace_app_open_in NOT NULL
+    open_in workspace_app_open_in DEFAULT 'slim-window'::workspace_app_open_in NOT NULL,
+    cors_behavior app_cors_behavior DEFAULT 'simple'::app_cors_behavior NOT NULL
 );
 
 COMMENT ON COLUMN workspace_apps.display_order IS 'Specifies the order in which to display agent app in user interfaces.';
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 87354ae5f9cf4..a5c2dccd7a7c4 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -3585,12 +3585,9 @@ type WorkspaceApp struct {
 	// Specifies the order in which to display agent app in user interfaces.
 	DisplayOrder int32 `db:"display_order" json:"display_order"`
 	// Determines if the app is not shown in user interfaces.
-<<<<<<< HEAD
-	Hidden       bool            `db:"hidden" json:"hidden"`
-	CORSBehavior AppCORSBehavior `db:"cors_behavior" json:"cors_behavior"`
-=======
-	Hidden bool               `db:"hidden" json:"hidden"`
-	OpenIn WorkspaceAppOpenIn `db:"open_in" json:"open_in"`
+	Hidden       bool               `db:"hidden" json:"hidden"`
+	OpenIn       WorkspaceAppOpenIn `db:"open_in" json:"open_in"`
+	CORSBehavior AppCORSBehavior    `db:"cors_behavior" json:"cors_behavior"`
 }
 
 // Audit sessions for workspace apps, the data in this table is ephemeral and is used to deduplicate audit log entries for workspace apps. While a session is active, the same data will not be logged again. This table does not store historical data.
@@ -3614,7 +3611,6 @@ type WorkspaceAppAuditSession struct {
 	// The time the session was last updated.
 	UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
 	ID        uuid.UUID `db:"id" json:"id"`
->>>>>>> origin/main
 }
 
 // A record of workspace app usage statistics
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 326c5684ead6c..996387170c20d 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -15646,11 +15646,7 @@ func (q *sqlQuerier) GetLatestWorkspaceAppStatusesByWorkspaceIDs(ctx context.Con
 }
 
 const getWorkspaceAppByAgentIDAndSlug = `-- name: GetWorkspaceAppByAgentIDAndSlug :one
-<<<<<<< HEAD
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE agent_id = $1 AND slug = $2
-=======
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in FROM workspace_apps WHERE agent_id = $1 AND slug = $2
->>>>>>> origin/main
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in, cors_behavior FROM workspace_apps WHERE agent_id = $1 AND slug = $2
 `
 
 type GetWorkspaceAppByAgentIDAndSlugParams struct {
@@ -15679,11 +15675,8 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg Ge
 		&i.External,
 		&i.DisplayOrder,
 		&i.Hidden,
-<<<<<<< HEAD
-		&i.CORSBehavior,
-=======
 		&i.OpenIn,
->>>>>>> origin/main
+		&i.CORSBehavior,
 	)
 	return i, err
 }
@@ -15725,11 +15718,7 @@ func (q *sqlQuerier) GetWorkspaceAppStatusesByAppIDs(ctx context.Context, ids []
 }
 
 const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many
-<<<<<<< HEAD
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
-=======
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
->>>>>>> origin/main
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in, cors_behavior FROM workspace_apps WHERE agent_id = $1 ORDER BY slug ASC
 `
 
 func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) {
@@ -15759,11 +15748,8 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
-<<<<<<< HEAD
-			&i.CORSBehavior,
-=======
 			&i.OpenIn,
->>>>>>> origin/main
+			&i.CORSBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -15779,11 +15765,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
 }
 
 const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many
-<<<<<<< HEAD
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
-=======
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
->>>>>>> origin/main
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in, cors_behavior FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY slug ASC
 `
 
 func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) {
@@ -15813,11 +15795,8 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
-<<<<<<< HEAD
-			&i.CORSBehavior,
-=======
 			&i.OpenIn,
->>>>>>> origin/main
+			&i.CORSBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -15833,11 +15812,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
 }
 
 const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many
-<<<<<<< HEAD
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
-=======
-SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
->>>>>>> origin/main
+SELECT id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in, cors_behavior FROM workspace_apps WHERE created_at > $1 ORDER BY slug ASC
 `
 
 func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) {
@@ -15867,11 +15842,8 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
 			&i.External,
 			&i.DisplayOrder,
 			&i.Hidden,
-<<<<<<< HEAD
-			&i.CORSBehavior,
-=======
 			&i.OpenIn,
->>>>>>> origin/main
+			&i.CORSBehavior,
 		); err != nil {
 			return nil, err
 		}
@@ -15900,7 +15872,7 @@ INSERT INTO
         external,
         subdomain,
         sharing_level,
-        cors_behavior,
+		cors_behavior,
         healthcheck_url,
         healthcheck_interval,
         healthcheck_threshold,
@@ -15910,11 +15882,7 @@ INSERT INTO
         open_in
     )
 VALUES
-<<<<<<< HEAD
-    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, cors_behavior
-=======
-    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in
->>>>>>> origin/main
+    ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING id, created_at, agent_id, display_name, icon, command, url, healthcheck_url, healthcheck_interval, healthcheck_threshold, health, subdomain, sharing_level, slug, external, display_order, hidden, open_in, cors_behavior
 `
 
 type InsertWorkspaceAppParams struct {
@@ -15980,10 +15948,8 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
 		&i.External,
 		&i.DisplayOrder,
 		&i.Hidden,
-<<<<<<< HEAD
-		&i.CORSBehavior,
-=======
 		&i.OpenIn,
+		&i.CORSBehavior,
 	)
 	return i, err
 }
@@ -16026,7 +15992,6 @@ func (q *sqlQuerier) InsertWorkspaceAppStatus(ctx context.Context, arg InsertWor
 		&i.State,
 		&i.Message,
 		&i.Uri,
->>>>>>> origin/main
 	)
 	return i, err
 }
diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md
index c9124efa14bf0..f0fdc1ab68461 100644
--- a/docs/admin/security/audit-logs.md
+++ b/docs/admin/security/audit-logs.md
@@ -30,7 +30,7 @@ We track the following resources:
 | TemplateVersion<br><i>create, write</i>                  | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>source_example_id</td><td>false</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
 | User<br><i>create, write, delete</i>                     | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>false</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>is_system</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
 | WorkspaceAgent<br><i>connect, disconnect</i>             | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>api_version</td><td>false</td></tr><tr><td>architecture</td><td>false</td></tr><tr><td>auth_instance_id</td><td>false</td></tr><tr><td>auth_token</td><td>false</td></tr><tr><td>connection_timeout_seconds</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>directory</td><td>false</td></tr><tr><td>disconnected_at</td><td>false</td></tr><tr><td>display_apps</td><td>false</td></tr><tr><td>display_order</td><td>false</td></tr><tr><td>environment_variables</td><td>false</td></tr><tr><td>expanded_directory</td><td>false</td></tr><tr><td>first_connected_at</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>instance_metadata</td><td>false</td></tr><tr><td>last_connected_at</td><td>false</td></tr><tr><td>last_connected_replica_id</td><td>false</td></tr><tr><td>lifecycle_state</td><td>false</td></tr><tr><td>logs_length</td><td>false</td></tr><tr><td>logs_overflowed</td><td>false</td></tr><tr><td>motd_file</td><td>false</td></tr><tr><td>name</td><td>false</td></tr><tr><td>operating_system</td><td>false</td></tr><tr><td>ready_at</td><td>false</td></tr><tr><td>resource_id</td><td>false</td></tr><tr><td>resource_metadata</td><td>false</td></tr><tr><td>started_at</td><td>false</td></tr><tr><td>subsystems</td><td>false</td></tr><tr><td>troubleshooting_url</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>version</td><td>false</td></tr></tbody></table>                                                                                                                                                  |
-| WorkspaceApp<br><i>open, close</i>                       | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>agent_id</td><td>false</td></tr><tr><td>command</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>display_name</td><td>false</td></tr><tr><td>display_order</td><td>false</td></tr><tr><td>external</td><td>false</td></tr><tr><td>health</td><td>false</td></tr><tr><td>healthcheck_interval</td><td>false</td></tr><tr><td>healthcheck_threshold</td><td>false</td></tr><tr><td>healthcheck_url</td><td>false</td></tr><tr><td>hidden</td><td>false</td></tr><tr><td>icon</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>open_in</td><td>false</td></tr><tr><td>sharing_level</td><td>false</td></tr><tr><td>slug</td><td>false</td></tr><tr><td>subdomain</td><td>false</td></tr><tr><td>url</td><td>false</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
+| WorkspaceApp<br><i>open, close</i>                       | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>agent_id</td><td>false</td></tr><tr><td>command</td><td>false</td></tr><tr><td>cors_behavior</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>display_name</td><td>false</td></tr><tr><td>display_order</td><td>false</td></tr><tr><td>external</td><td>false</td></tr><tr><td>health</td><td>false</td></tr><tr><td>healthcheck_interval</td><td>false</td></tr><tr><td>healthcheck_threshold</td><td>false</td></tr><tr><td>healthcheck_url</td><td>false</td></tr><tr><td>hidden</td><td>false</td></tr><tr><td>icon</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>open_in</td><td>false</td></tr><tr><td>sharing_level</td><td>false</td></tr><tr><td>slug</td><td>false</td></tr><tr><td>subdomain</td><td>false</td></tr><tr><td>url</td><td>false</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
 | WorkspaceBuild<br><i>start, stop</i>                     | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>template_version_preset_id</td><td>false</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
 | WorkspaceProxy<br><i></i>                                | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
 | WorkspaceTable<br><i></i>                                | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>next_start_at</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go
index 84cc7d451b4f1..546824f9cd245 100644
--- a/enterprise/audit/table.go
+++ b/enterprise/audit/table.go
@@ -362,6 +362,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
 		"display_order":         ActionIgnore,
 		"hidden":                ActionIgnore,
 		"open_in":               ActionIgnore,
+		"cors_behavior":         ActionIgnore,
 	},
 }
 
diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go
index f258f79e36f94..ab6acbf6c2724 100644
--- a/provisionersdk/proto/provisioner.pb.go
+++ b/provisionersdk/proto/provisioner.pb.go
@@ -126,6 +126,52 @@ func (AppSharingLevel) EnumDescriptor() ([]byte, []int) {
 	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{1}
 }
 
+type AppCORSBehavior int32
+
+const (
+	AppCORSBehavior_SIMPLE   AppCORSBehavior = 0
+	AppCORSBehavior_PASSTHRU AppCORSBehavior = 1
+)
+
+// Enum value maps for AppCORSBehavior.
+var (
+	AppCORSBehavior_name = map[int32]string{
+		0: "SIMPLE",
+		1: "PASSTHRU",
+	}
+	AppCORSBehavior_value = map[string]int32{
+		"SIMPLE":   0,
+		"PASSTHRU": 1,
+	}
+)
+
+func (x AppCORSBehavior) Enum() *AppCORSBehavior {
+	p := new(AppCORSBehavior)
+	*p = x
+	return p
+}
+
+func (x AppCORSBehavior) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (AppCORSBehavior) Descriptor() protoreflect.EnumDescriptor {
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[2].Descriptor()
+}
+
+func (AppCORSBehavior) Type() protoreflect.EnumType {
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[2]
+}
+
+func (x AppCORSBehavior) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use AppCORSBehavior.Descriptor instead.
+func (AppCORSBehavior) EnumDescriptor() ([]byte, []int) {
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2}
+}
+
 type AppOpenIn int32
 
 const (
@@ -160,11 +206,11 @@ func (x AppOpenIn) String() string {
 }
 
 func (AppOpenIn) Descriptor() protoreflect.EnumDescriptor {
-	return file_provisionersdk_proto_provisioner_proto_enumTypes[2].Descriptor()
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor()
 }
 
 func (AppOpenIn) Type() protoreflect.EnumType {
-	return &file_provisionersdk_proto_provisioner_proto_enumTypes[2]
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[3]
 }
 
 func (x AppOpenIn) Number() protoreflect.EnumNumber {
@@ -173,7 +219,7 @@ func (x AppOpenIn) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use AppOpenIn.Descriptor instead.
 func (AppOpenIn) EnumDescriptor() ([]byte, []int) {
-	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2}
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3}
 }
 
 // WorkspaceTransition is the desired outcome of a build
@@ -210,11 +256,11 @@ func (x WorkspaceTransition) String() string {
 }
 
 func (WorkspaceTransition) Descriptor() protoreflect.EnumDescriptor {
-	return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor()
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[4].Descriptor()
 }
 
 func (WorkspaceTransition) Type() protoreflect.EnumType {
-	return &file_provisionersdk_proto_provisioner_proto_enumTypes[3]
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[4]
 }
 
 func (x WorkspaceTransition) Number() protoreflect.EnumNumber {
@@ -223,7 +269,7 @@ func (x WorkspaceTransition) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use WorkspaceTransition.Descriptor instead.
 func (WorkspaceTransition) EnumDescriptor() ([]byte, []int) {
-	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3}
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{4}
 }
 
 type TimingState int32
@@ -259,11 +305,11 @@ func (x TimingState) String() string {
 }
 
 func (TimingState) Descriptor() protoreflect.EnumDescriptor {
-	return file_provisionersdk_proto_provisioner_proto_enumTypes[4].Descriptor()
+	return file_provisionersdk_proto_provisioner_proto_enumTypes[5].Descriptor()
 }
 
 func (TimingState) Type() protoreflect.EnumType {
-	return &file_provisionersdk_proto_provisioner_proto_enumTypes[4]
+	return &file_provisionersdk_proto_provisioner_proto_enumTypes[5]
 }
 
 func (x TimingState) Number() protoreflect.EnumNumber {
@@ -272,7 +318,7 @@ func (x TimingState) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use TimingState.Descriptor instead.
 func (TimingState) EnumDescriptor() ([]byte, []int) {
-	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{4}
+	return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{5}
 }
 
 // Empty indicates a successful request/response.
@@ -1866,6 +1912,7 @@ type App struct {
 	Order        int64           `protobuf:"varint,10,opt,name=order,proto3" json:"order,omitempty"`
 	Hidden       bool            `protobuf:"varint,11,opt,name=hidden,proto3" json:"hidden,omitempty"`
 	OpenIn       AppOpenIn       `protobuf:"varint,12,opt,name=open_in,json=openIn,proto3,enum=provisioner.AppOpenIn" json:"open_in,omitempty"`
+	CorsBehavior AppCORSBehavior `protobuf:"varint,13,opt,name=cors_behavior,json=corsBehavior,proto3,enum=provisioner.AppCORSBehavior" json:"cors_behavior,omitempty"`
 }
 
 func (x *App) Reset() {
@@ -1984,6 +2031,13 @@ func (x *App) GetOpenIn() AppOpenIn {
 	return AppOpenIn_WINDOW
 }
 
+func (x *App) GetCorsBehavior() AppCORSBehavior {
+	if x != nil {
+		return x.CorsBehavior
+	}
+	return AppCORSBehavior_SIMPLE
+}
+
 // Healthcheck represents configuration for checking for app readiness.
 type Healthcheck struct {
 	state         protoimpl.MessageState
@@ -3739,7 +3793,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
 	0x61, 0x63, 0x65, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e,
 	0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
 	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x94,
+	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xd7,
 	0x03, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01,
 	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69,
 	0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
@@ -3765,269 +3819,276 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
 	0x64, 0x64, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18,
 	0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
 	0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x52, 0x06, 0x6f,
-	0x70, 0x65, 0x6e, 0x49, 0x6e, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63,
-	0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76,
-	0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76,
-	0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64,
-	0x22, 0x92, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a,
-	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18,
-	0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
-	0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74,
-	0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64,
-	0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a,
-	0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64,
-	0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
-	0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e,
-	0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61,
-	0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09,
-	0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64,
-	0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
-	0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65,
-	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c,
-	0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07,
-	0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69,
-	0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0x4c, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12,
-	0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
-	0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
-	0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
-	0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
-	0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0xe0, 0x08, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64,
-	0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c,
-	0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72,
-	0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72,
-	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
-	0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73,
-	0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
-	0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f,
-	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
-	0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72,
-	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b,
-	0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f,
-	0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
-	0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18,
-	0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65,
-	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12,
-	0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73,
-	0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c,
-	0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69,
-	0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
-	0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54,
-	0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
-	0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72,
-	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69,
-	0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c,
-	0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65,
-	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b,
-	0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-	0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72,
-	0x6f, 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b,
-	0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73,
-	0x12, 0x42, 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77,
-	0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b,
-	0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
-	0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69,
-	0x63, 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76,
-	0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68,
-	0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64,
-	0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b,
-	0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69,
-	0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69,
-	0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
-	0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x62, 0x61, 0x63, 0x5f, 0x72, 0x6f,
-	0x6c, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76,
-	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x17, 0x77, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x52, 0x62, 0x61, 0x63,
-	0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x62,
-	0x75, 0x69, 0x6c, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x50, 0x72,
-	0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e,
-	0x67, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x67, 0x65, 0x6e,
-	0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x72,
-	0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41,
-	0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
-	0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05,
-	0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61,
-	0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f,
-	0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65,
-	0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
-	0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c,
-	0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61,
-	0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f,
-	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
-	0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c,
-	0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06,
-	0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65,
-	0x61, 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70,
-	0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65,
-	0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
-	0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72,
-	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f,
-	0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
-	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
-	0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb5, 0x02, 0x0a,
-	0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08,
-	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74,
-	0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-	0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
-	0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63,
-	0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
-	0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61,
-	0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,
-	0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
-	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69,
-	0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61,
-	0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74,
-	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69,
-	0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f,
-	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
-	0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65,
-	0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69,
-	0x64, 0x65, 0x72, 0x73, 0x22, 0x99, 0x03, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d,
-	0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72,
-	0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
-	0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
-	0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
-	0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17,
+	0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x73, 0x5f, 0x62, 0x65,
+	0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x43, 0x4f,
+	0x52, 0x53, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x52, 0x0c, 0x63, 0x6f, 0x72, 0x73,
+	0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c,
+	0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f,
+	0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68,
+	0x6f, 0x6c, 0x64, 0x22, 0x92, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e,
+	0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
+	0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67,
+	0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04,
+	0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74,
+	0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a,
+	0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b,
+	0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x69, 0x0a,
+	0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12,
+	0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0x4c, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x22, 0xe0, 0x08, 0x0a, 0x08, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f,
+	0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72,
+	0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74,
+	0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72,
+	0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12,
+	0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e,
+	0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
+	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77,
+	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69,
+	0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72,
+	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61,
+	0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a,
+	0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61,
+	0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65,
+	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a,
+	0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72,
+	0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b,
+	0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
+	0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65,
+	0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+	0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
+	0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a,
+	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65,
+	0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65,
+	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77,
+	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+	0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a,
+	0x16, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72,
+	0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77,
+	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f,
+	0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+	0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69,
+	0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72,
+	0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75,
+	0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+	0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70,
+	0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72,
+	0x53, 0x73, 0x68, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a,
+	0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+	0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x77,
+	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6c,
+	0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x17, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4c,
+	0x6f, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x1a, 0x77, 0x6f, 0x72, 0x6b,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x62, 0x61, 0x63,
+	0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70,
+	0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52,
+	0x17, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x52,
+	0x62, 0x61, 0x63, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x70,
+	0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69,
+	0x73, 0x50, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x75, 0x6e,
+	0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x61,
+	0x67, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x1a, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
+	0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8a, 0x01, 0x0a,
+	0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c,
+	0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69,
+	0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
+	0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12,
+	0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
+	0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+	0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72,
+	0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61,
+	0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
+	0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61,
+	0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70,
+	0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65,
+	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12,
+	0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
+	0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+	0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
+	0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b,
+	0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d,
+	0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a,
+	0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
+	0xb5, 0x02, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d,
+	0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
+	0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
+	0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61,
+	0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56,
+	0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61,
+	0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17,
 	0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72,
-	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e,
 	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65,
 	0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
-	0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
-	0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12,
-	0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54,
-	0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d,
-	0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f,
-	0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a,
-	0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65,
-	0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04,
-	0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e,
-	0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
-	0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
-	0x61, 0x74, 0x61, 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d,
-	0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
-	0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
-	0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
-	0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
-	0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
-	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72,
-	0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
-	0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61,
-	0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72,
-	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15,
-	0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76,
-	0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73,
-	0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
-	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d,
-	0x69, 0x6e, 0x67, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12,
-	0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
-	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72,
-	0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
-	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12,
-	0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
-	0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73,
-	0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67,
-	0x65, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54,
-	0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
-	0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d,
-	0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a,
-	0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70,
-	0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65,
-	0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
-	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61,
-	0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e,
-	0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70,
-	0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70,
-	0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
-	0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70,
-	0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24,
-	0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72,
-	0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52,
-	0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
-	0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48,
-	0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
-	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
-	0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70,
-	0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
-	0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70,
-	0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a,
-	0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65,
-	0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
-	0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10,
-	0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45,
-	0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61,
-	0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e,
-	0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49,
-	0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49,
-	0x43, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e,
+	0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x99, 0x03, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e,
+	0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
+	0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33,
+	0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
+	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
+	0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65,
+	0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12,
+	0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68,
+	0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45,
+	0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69,
+	0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74,
+	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
+	0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+	0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67,
+	0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+	0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
+	0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x12,
+	0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70,
+	0x6c, 0x61, 0x6e, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+	0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79,
+	0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
+	0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09,
+	0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72,
+	0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68,
+	0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d,
+	0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
+	0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74,
+	0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50,
+	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69,
+	0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07,
+	0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69,
+	0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73,
+	0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65,
+	0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73,
+	0x74, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+	0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73,
+	0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61,
+	0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61,
+	0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+	0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70,
+	0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+	0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52,
+	0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04,
+	0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
+	0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67,
+	0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
+	0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70,
+	0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70,
+	0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05,
+	0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72,
+	0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43,
+	0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79,
+	0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c,
+	0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12,
+	0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
+	0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09,
+	0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70,
+	0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05,
+	0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45,
+	0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55,
+	0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x2b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x43, 0x4f, 0x52,
+	0x53, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x49, 0x4d,
+	0x50, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x41, 0x53, 0x53, 0x54, 0x48, 0x52,
+	0x55, 0x10, 0x01, 0x2a, 0x35, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e,
 	0x12, 0x0e, 0x0a, 0x06, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x00, 0x1a, 0x02, 0x08, 0x01,
 	0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10,
 	0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f,
@@ -4060,116 +4121,118 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte {
 	return file_provisionersdk_proto_provisioner_proto_rawDescData
 }
 
-var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
+var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
 var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 42)
 var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{
 	(LogLevel)(0),                        // 0: provisioner.LogLevel
 	(AppSharingLevel)(0),                 // 1: provisioner.AppSharingLevel
-	(AppOpenIn)(0),                       // 2: provisioner.AppOpenIn
-	(WorkspaceTransition)(0),             // 3: provisioner.WorkspaceTransition
-	(TimingState)(0),                     // 4: provisioner.TimingState
-	(*Empty)(nil),                        // 5: provisioner.Empty
-	(*TemplateVariable)(nil),             // 6: provisioner.TemplateVariable
-	(*RichParameterOption)(nil),          // 7: provisioner.RichParameterOption
-	(*RichParameter)(nil),                // 8: provisioner.RichParameter
-	(*RichParameterValue)(nil),           // 9: provisioner.RichParameterValue
-	(*Prebuild)(nil),                     // 10: provisioner.Prebuild
-	(*Preset)(nil),                       // 11: provisioner.Preset
-	(*PresetParameter)(nil),              // 12: provisioner.PresetParameter
-	(*VariableValue)(nil),                // 13: provisioner.VariableValue
-	(*Log)(nil),                          // 14: provisioner.Log
-	(*InstanceIdentityAuth)(nil),         // 15: provisioner.InstanceIdentityAuth
-	(*ExternalAuthProviderResource)(nil), // 16: provisioner.ExternalAuthProviderResource
-	(*ExternalAuthProvider)(nil),         // 17: provisioner.ExternalAuthProvider
-	(*Agent)(nil),                        // 18: provisioner.Agent
-	(*ResourcesMonitoring)(nil),          // 19: provisioner.ResourcesMonitoring
-	(*MemoryResourceMonitor)(nil),        // 20: provisioner.MemoryResourceMonitor
-	(*VolumeResourceMonitor)(nil),        // 21: provisioner.VolumeResourceMonitor
-	(*DisplayApps)(nil),                  // 22: provisioner.DisplayApps
-	(*Env)(nil),                          // 23: provisioner.Env
-	(*Script)(nil),                       // 24: provisioner.Script
-	(*Devcontainer)(nil),                 // 25: provisioner.Devcontainer
-	(*App)(nil),                          // 26: provisioner.App
-	(*Healthcheck)(nil),                  // 27: provisioner.Healthcheck
-	(*Resource)(nil),                     // 28: provisioner.Resource
-	(*Module)(nil),                       // 29: provisioner.Module
-	(*Role)(nil),                         // 30: provisioner.Role
-	(*Metadata)(nil),                     // 31: provisioner.Metadata
-	(*Config)(nil),                       // 32: provisioner.Config
-	(*ParseRequest)(nil),                 // 33: provisioner.ParseRequest
-	(*ParseComplete)(nil),                // 34: provisioner.ParseComplete
-	(*PlanRequest)(nil),                  // 35: provisioner.PlanRequest
-	(*PlanComplete)(nil),                 // 36: provisioner.PlanComplete
-	(*ApplyRequest)(nil),                 // 37: provisioner.ApplyRequest
-	(*ApplyComplete)(nil),                // 38: provisioner.ApplyComplete
-	(*Timing)(nil),                       // 39: provisioner.Timing
-	(*CancelRequest)(nil),                // 40: provisioner.CancelRequest
-	(*Request)(nil),                      // 41: provisioner.Request
-	(*Response)(nil),                     // 42: provisioner.Response
-	(*Agent_Metadata)(nil),               // 43: provisioner.Agent.Metadata
-	nil,                                  // 44: provisioner.Agent.EnvEntry
-	(*Resource_Metadata)(nil),            // 45: provisioner.Resource.Metadata
-	nil,                                  // 46: provisioner.ParseComplete.WorkspaceTagsEntry
-	(*timestamppb.Timestamp)(nil),        // 47: google.protobuf.Timestamp
+	(AppCORSBehavior)(0),                 // 2: provisioner.AppCORSBehavior
+	(AppOpenIn)(0),                       // 3: provisioner.AppOpenIn
+	(WorkspaceTransition)(0),             // 4: provisioner.WorkspaceTransition
+	(TimingState)(0),                     // 5: provisioner.TimingState
+	(*Empty)(nil),                        // 6: provisioner.Empty
+	(*TemplateVariable)(nil),             // 7: provisioner.TemplateVariable
+	(*RichParameterOption)(nil),          // 8: provisioner.RichParameterOption
+	(*RichParameter)(nil),                // 9: provisioner.RichParameter
+	(*RichParameterValue)(nil),           // 10: provisioner.RichParameterValue
+	(*Prebuild)(nil),                     // 11: provisioner.Prebuild
+	(*Preset)(nil),                       // 12: provisioner.Preset
+	(*PresetParameter)(nil),              // 13: provisioner.PresetParameter
+	(*VariableValue)(nil),                // 14: provisioner.VariableValue
+	(*Log)(nil),                          // 15: provisioner.Log
+	(*InstanceIdentityAuth)(nil),         // 16: provisioner.InstanceIdentityAuth
+	(*ExternalAuthProviderResource)(nil), // 17: provisioner.ExternalAuthProviderResource
+	(*ExternalAuthProvider)(nil),         // 18: provisioner.ExternalAuthProvider
+	(*Agent)(nil),                        // 19: provisioner.Agent
+	(*ResourcesMonitoring)(nil),          // 20: provisioner.ResourcesMonitoring
+	(*MemoryResourceMonitor)(nil),        // 21: provisioner.MemoryResourceMonitor
+	(*VolumeResourceMonitor)(nil),        // 22: provisioner.VolumeResourceMonitor
+	(*DisplayApps)(nil),                  // 23: provisioner.DisplayApps
+	(*Env)(nil),                          // 24: provisioner.Env
+	(*Script)(nil),                       // 25: provisioner.Script
+	(*Devcontainer)(nil),                 // 26: provisioner.Devcontainer
+	(*App)(nil),                          // 27: provisioner.App
+	(*Healthcheck)(nil),                  // 28: provisioner.Healthcheck
+	(*Resource)(nil),                     // 29: provisioner.Resource
+	(*Module)(nil),                       // 30: provisioner.Module
+	(*Role)(nil),                         // 31: provisioner.Role
+	(*Metadata)(nil),                     // 32: provisioner.Metadata
+	(*Config)(nil),                       // 33: provisioner.Config
+	(*ParseRequest)(nil),                 // 34: provisioner.ParseRequest
+	(*ParseComplete)(nil),                // 35: provisioner.ParseComplete
+	(*PlanRequest)(nil),                  // 36: provisioner.PlanRequest
+	(*PlanComplete)(nil),                 // 37: provisioner.PlanComplete
+	(*ApplyRequest)(nil),                 // 38: provisioner.ApplyRequest
+	(*ApplyComplete)(nil),                // 39: provisioner.ApplyComplete
+	(*Timing)(nil),                       // 40: provisioner.Timing
+	(*CancelRequest)(nil),                // 41: provisioner.CancelRequest
+	(*Request)(nil),                      // 42: provisioner.Request
+	(*Response)(nil),                     // 43: provisioner.Response
+	(*Agent_Metadata)(nil),               // 44: provisioner.Agent.Metadata
+	nil,                                  // 45: provisioner.Agent.EnvEntry
+	(*Resource_Metadata)(nil),            // 46: provisioner.Resource.Metadata
+	nil,                                  // 47: provisioner.ParseComplete.WorkspaceTagsEntry
+	(*timestamppb.Timestamp)(nil),        // 48: google.protobuf.Timestamp
 }
 var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{
-	7,  // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption
-	12, // 1: provisioner.Preset.parameters:type_name -> provisioner.PresetParameter
-	10, // 2: provisioner.Preset.prebuild:type_name -> provisioner.Prebuild
+	8,  // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption
+	13, // 1: provisioner.Preset.parameters:type_name -> provisioner.PresetParameter
+	11, // 2: provisioner.Preset.prebuild:type_name -> provisioner.Prebuild
 	0,  // 3: provisioner.Log.level:type_name -> provisioner.LogLevel
-	44, // 4: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
-	26, // 5: provisioner.Agent.apps:type_name -> provisioner.App
-	43, // 6: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata
-	22, // 7: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps
-	24, // 8: provisioner.Agent.scripts:type_name -> provisioner.Script
-	23, // 9: provisioner.Agent.extra_envs:type_name -> provisioner.Env
-	19, // 10: provisioner.Agent.resources_monitoring:type_name -> provisioner.ResourcesMonitoring
-	25, // 11: provisioner.Agent.devcontainers:type_name -> provisioner.Devcontainer
-	20, // 12: provisioner.ResourcesMonitoring.memory:type_name -> provisioner.MemoryResourceMonitor
-	21, // 13: provisioner.ResourcesMonitoring.volumes:type_name -> provisioner.VolumeResourceMonitor
-	27, // 14: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
+	45, // 4: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
+	27, // 5: provisioner.Agent.apps:type_name -> provisioner.App
+	44, // 6: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata
+	23, // 7: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps
+	25, // 8: provisioner.Agent.scripts:type_name -> provisioner.Script
+	24, // 9: provisioner.Agent.extra_envs:type_name -> provisioner.Env
+	20, // 10: provisioner.Agent.resources_monitoring:type_name -> provisioner.ResourcesMonitoring
+	26, // 11: provisioner.Agent.devcontainers:type_name -> provisioner.Devcontainer
+	21, // 12: provisioner.ResourcesMonitoring.memory:type_name -> provisioner.MemoryResourceMonitor
+	22, // 13: provisioner.ResourcesMonitoring.volumes:type_name -> provisioner.VolumeResourceMonitor
+	28, // 14: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
 	1,  // 15: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel
-	2,  // 16: provisioner.App.open_in:type_name -> provisioner.AppOpenIn
-	18, // 17: provisioner.Resource.agents:type_name -> provisioner.Agent
-	45, // 18: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
-	3,  // 19: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
-	30, // 20: provisioner.Metadata.workspace_owner_rbac_roles:type_name -> provisioner.Role
-	6,  // 21: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable
-	46, // 22: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry
-	31, // 23: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata
-	9,  // 24: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue
-	13, // 25: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue
-	17, // 26: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider
-	28, // 27: provisioner.PlanComplete.resources:type_name -> provisioner.Resource
-	8,  // 28: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter
-	16, // 29: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
-	39, // 30: provisioner.PlanComplete.timings:type_name -> provisioner.Timing
-	29, // 31: provisioner.PlanComplete.modules:type_name -> provisioner.Module
-	11, // 32: provisioner.PlanComplete.presets:type_name -> provisioner.Preset
-	31, // 33: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata
-	28, // 34: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource
-	8,  // 35: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter
-	16, // 36: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
-	39, // 37: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing
-	47, // 38: provisioner.Timing.start:type_name -> google.protobuf.Timestamp
-	47, // 39: provisioner.Timing.end:type_name -> google.protobuf.Timestamp
-	4,  // 40: provisioner.Timing.state:type_name -> provisioner.TimingState
-	32, // 41: provisioner.Request.config:type_name -> provisioner.Config
-	33, // 42: provisioner.Request.parse:type_name -> provisioner.ParseRequest
-	35, // 43: provisioner.Request.plan:type_name -> provisioner.PlanRequest
-	37, // 44: provisioner.Request.apply:type_name -> provisioner.ApplyRequest
-	40, // 45: provisioner.Request.cancel:type_name -> provisioner.CancelRequest
-	14, // 46: provisioner.Response.log:type_name -> provisioner.Log
-	34, // 47: provisioner.Response.parse:type_name -> provisioner.ParseComplete
-	36, // 48: provisioner.Response.plan:type_name -> provisioner.PlanComplete
-	38, // 49: provisioner.Response.apply:type_name -> provisioner.ApplyComplete
-	41, // 50: provisioner.Provisioner.Session:input_type -> provisioner.Request
-	42, // 51: provisioner.Provisioner.Session:output_type -> provisioner.Response
-	51, // [51:52] is the sub-list for method output_type
-	50, // [50:51] is the sub-list for method input_type
-	50, // [50:50] is the sub-list for extension type_name
-	50, // [50:50] is the sub-list for extension extendee
-	0,  // [0:50] is the sub-list for field type_name
+	3,  // 16: provisioner.App.open_in:type_name -> provisioner.AppOpenIn
+	2,  // 17: provisioner.App.cors_behavior:type_name -> provisioner.AppCORSBehavior
+	19, // 18: provisioner.Resource.agents:type_name -> provisioner.Agent
+	46, // 19: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
+	4,  // 20: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
+	31, // 21: provisioner.Metadata.workspace_owner_rbac_roles:type_name -> provisioner.Role
+	7,  // 22: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable
+	47, // 23: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry
+	32, // 24: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata
+	10, // 25: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue
+	14, // 26: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue
+	18, // 27: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider
+	29, // 28: provisioner.PlanComplete.resources:type_name -> provisioner.Resource
+	9,  // 29: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter
+	17, // 30: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
+	40, // 31: provisioner.PlanComplete.timings:type_name -> provisioner.Timing
+	30, // 32: provisioner.PlanComplete.modules:type_name -> provisioner.Module
+	12, // 33: provisioner.PlanComplete.presets:type_name -> provisioner.Preset
+	32, // 34: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata
+	29, // 35: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource
+	9,  // 36: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter
+	17, // 37: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
+	40, // 38: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing
+	48, // 39: provisioner.Timing.start:type_name -> google.protobuf.Timestamp
+	48, // 40: provisioner.Timing.end:type_name -> google.protobuf.Timestamp
+	5,  // 41: provisioner.Timing.state:type_name -> provisioner.TimingState
+	33, // 42: provisioner.Request.config:type_name -> provisioner.Config
+	34, // 43: provisioner.Request.parse:type_name -> provisioner.ParseRequest
+	36, // 44: provisioner.Request.plan:type_name -> provisioner.PlanRequest
+	38, // 45: provisioner.Request.apply:type_name -> provisioner.ApplyRequest
+	41, // 46: provisioner.Request.cancel:type_name -> provisioner.CancelRequest
+	15, // 47: provisioner.Response.log:type_name -> provisioner.Log
+	35, // 48: provisioner.Response.parse:type_name -> provisioner.ParseComplete
+	37, // 49: provisioner.Response.plan:type_name -> provisioner.PlanComplete
+	39, // 50: provisioner.Response.apply:type_name -> provisioner.ApplyComplete
+	42, // 51: provisioner.Provisioner.Session:input_type -> provisioner.Request
+	43, // 52: provisioner.Provisioner.Session:output_type -> provisioner.Response
+	52, // [52:53] is the sub-list for method output_type
+	51, // [51:52] is the sub-list for method input_type
+	51, // [51:51] is the sub-list for extension type_name
+	51, // [51:51] is the sub-list for extension extendee
+	0,  // [0:51] is the sub-list for field type_name
 }
 
 func init() { file_provisionersdk_proto_provisioner_proto_init() }
@@ -4682,7 +4745,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc,
-			NumEnums:      5,
+			NumEnums:      6,
 			NumMessages:   42,
 			NumExtensions: 0,
 			NumServices:   1,
diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts
index 20408ad2be182..2aed339e96bcb 100644
--- a/site/e2e/provisionerGenerated.ts
+++ b/site/e2e/provisionerGenerated.ts
@@ -22,6 +22,12 @@ export enum AppSharingLevel {
   UNRECOGNIZED = -1,
 }
 
+export enum AppCORSBehavior {
+  SIMPLE = 0,
+  PASSTHRU = 1,
+  UNRECOGNIZED = -1,
+}
+
 export enum AppOpenIn {
   /** @deprecated */
   WINDOW = 0,
@@ -30,12 +36,6 @@ export enum AppOpenIn {
   UNRECOGNIZED = -1,
 }
 
-export enum AppCORSBehavior {
-	SIMPLE = 0,
-	PASSTHRU = 1,
-	UNRECOGNIZED = -1,
-}
-
 /** WorkspaceTransition is the desired outcome of a build */
 export enum WorkspaceTransition {
   START = 0,
@@ -236,24 +236,6 @@ export interface Devcontainer {
 
 /** App represents a dev-accessible application on the workspace. */
 export interface App {
-<<<<<<< HEAD
-	/**
-	 * slug is the unique identifier for the app, usually the name from the
-	 * template. It must be URL-safe and hostname-safe.
-	 */
-	slug: string;
-	displayName: string;
-	command: string;
-	url: string;
-	icon: string;
-	subdomain: boolean;
-	healthcheck: Healthcheck | undefined;
-	sharingLevel: AppSharingLevel;
-	corsBehavior: AppCORSBehavior;
-	external: boolean;
-	order: number;
-	hidden: boolean;
-=======
   /**
    * slug is the unique identifier for the app, usually the name from the
    * template. It must be URL-safe and hostname-safe.
@@ -270,7 +252,7 @@ export interface App {
   order: number;
   hidden: boolean;
   openIn: AppOpenIn;
->>>>>>> origin/main
+  corsBehavior: AppCORSBehavior;
 }
 
 /** Healthcheck represents configuration for checking for app readiness. */
@@ -859,50 +841,6 @@ export const Devcontainer = {
 };
 
 export const App = {
-<<<<<<< HEAD
-	encode(message: App, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
-		if (message.slug !== "") {
-			writer.uint32(10).string(message.slug);
-		}
-		if (message.displayName !== "") {
-			writer.uint32(18).string(message.displayName);
-		}
-		if (message.command !== "") {
-			writer.uint32(26).string(message.command);
-		}
-		if (message.url !== "") {
-			writer.uint32(34).string(message.url);
-		}
-		if (message.icon !== "") {
-			writer.uint32(42).string(message.icon);
-		}
-		if (message.subdomain === true) {
-			writer.uint32(48).bool(message.subdomain);
-		}
-		if (message.healthcheck !== undefined) {
-			Healthcheck.encode(
-				message.healthcheck,
-				writer.uint32(58).fork(),
-			).ldelim();
-		}
-		if (message.sharingLevel !== 0) {
-			writer.uint32(64).int32(message.sharingLevel);
-		}
-		if (message.corsBehavior !== 0) {
-			writer.uint32(96).int32(message.corsBehavior);
-		}
-		if (message.external === true) {
-			writer.uint32(72).bool(message.external);
-		}
-		if (message.order !== 0) {
-			writer.uint32(80).int64(message.order);
-		}
-		if (message.hidden === true) {
-			writer.uint32(88).bool(message.hidden);
-		}
-		return writer;
-	},
-=======
   encode(message: App, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
     if (message.slug !== "") {
       writer.uint32(10).string(message.slug);
@@ -940,9 +878,11 @@ export const App = {
     if (message.openIn !== 0) {
       writer.uint32(96).int32(message.openIn);
     }
+    if (message.corsBehavior !== 0) {
+      writer.uint32(104).int32(message.corsBehavior);
+    }
     return writer;
   },
->>>>>>> origin/main
 };
 
 export const Healthcheck = {
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 00ca922dd7ef8..d6cc05e7d1243 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -81,6 +81,11 @@ export const AgentSubsystems: AgentSubsystem[] = [
 	"exectrace",
 ];
 
+// From codersdk/cors_behavior.go
+export type AppCORSBehavior = "passthru" | "simple";
+
+export const AppCORSBehaviors: AppCORSBehavior[] = ["passthru", "simple"];
+
 // From codersdk/deployment.go
 export interface AppHostResponse {
 	readonly host: string;
@@ -3665,34 +3670,8 @@ export interface WorkspacesResponse {
 	readonly count: number;
 }
 
-<<<<<<< HEAD
-// From codersdk/apikey.go
-export type APIKeyScope = "all" | "application_connect"
-export const APIKeyScopes: APIKeyScope[] = ["all", "application_connect"]
-
-// From codersdk/workspaceagents.go
-export type AgentSubsystem = "envbox" | "envbuilder" | "exectrace"
-export const AgentSubsystems: AgentSubsystem[] = ["envbox", "envbuilder", "exectrace"]
-
-// From codersdk/cors_behavior.go
-export type AppCORSBehavior = "passthru" | "simple"
-export const AppCORSBehaviors: AppCORSBehavior[] = ["passthru", "simple"]
-
-// From codersdk/audit.go
-export type AuditAction = "create" | "delete" | "login" | "logout" | "register" | "request_password_reset" | "start" | "stop" | "write"
-export const AuditActions: AuditAction[] = ["create", "delete", "login", "logout", "register", "request_password_reset", "start", "stop", "write"]
-
-// From codersdk/workspaces.go
-export type AutomaticUpdates = "always" | "never"
-export const AutomaticUpdateses: AutomaticUpdates[] = ["always", "never"]
-
-// From codersdk/workspacebuilds.go
-export type BuildReason = "autostart" | "autostop" | "initiator"
-export const BuildReasons: BuildReason[] = ["autostart", "autostop", "initiator"]
-=======
 // From codersdk/deployment.go
 export const annotationEnterpriseKey = "enterprise";
->>>>>>> origin/main
 
 // From codersdk/deployment.go
 export const annotationExternalProxies = "external_workspace_proxies";

From 906562369a4df6fc1616e986065182bf6cc218a9 Mon Sep 17 00:00:00 2001
From: Steven Masley <stevenmasley@gmail.com>
Date: Mon, 28 Apr 2025 16:14:21 -0500
Subject: [PATCH 13/17] formatting

---
 coderd/database/queries/workspaceapps.sql | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/coderd/database/queries/workspaceapps.sql b/coderd/database/queries/workspaceapps.sql
index fd9359f2fee8a..b41fa88e5879a 100644
--- a/coderd/database/queries/workspaceapps.sql
+++ b/coderd/database/queries/workspaceapps.sql
@@ -24,7 +24,7 @@ INSERT INTO
         external,
         subdomain,
         sharing_level,
-		cors_behavior,
+        cors_behavior,
         healthcheck_url,
         healthcheck_interval,
         healthcheck_threshold,

From 991d285a1f1e5f5ef4096fff64f8e70957ad6762 Mon Sep 17 00:00:00 2001
From: Steven Masley <stevenmasley@gmail.com>
Date: Mon, 28 Apr 2025 19:14:31 -0500
Subject: [PATCH 14/17] move cors to after app handling

---
 enterprise/wsproxy/wsproxy.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/enterprise/wsproxy/wsproxy.go b/enterprise/wsproxy/wsproxy.go
index bce49417fcd35..69241d8aa1c17 100644
--- a/enterprise/wsproxy/wsproxy.go
+++ b/enterprise/wsproxy/wsproxy.go
@@ -339,11 +339,11 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
 		httpmw.ExtractRealIP(s.Options.RealIPConfig),
 		loggermw.Logger(s.Logger),
 		prometheusMW,
-		corsMW,
 
 		// HandleSubdomain is a middleware that handles all requests to the
 		// subdomain-based workspace apps.
 		s.AppServer.HandleSubdomain(apiRateLimiter),
+		corsMW,
 		// Build-Version is helpful for debugging.
 		func(next http.Handler) http.Handler {
 			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

From 5ba7b823e1c6a260ce0ff6e077f8506ec387b57b Mon Sep 17 00:00:00 2001
From: Steven Masley <stevenmasley@gmail.com>
Date: Mon, 28 Apr 2025 19:47:24 -0500
Subject: [PATCH 15/17] gen

---
 coderd/database/queries.sql.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 996387170c20d..afc9987c6ac46 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -15872,7 +15872,7 @@ INSERT INTO
         external,
         subdomain,
         sharing_level,
-		cors_behavior,
+        cors_behavior,
         healthcheck_url,
         healthcheck_interval,
         healthcheck_threshold,

From bc8cce381b835dead4e5ce4327d958bacbde6f24 Mon Sep 17 00:00:00 2001
From: Steven Masley <stevenmasley@gmail.com>
Date: Mon, 28 Apr 2025 21:01:58 -0500
Subject: [PATCH 16/17] chore: work on template to test cors

---
 examples/cors_demo/README.md |   0
 examples/cors_demo/apps.tf   |  15 +++
 examples/cors_demo/main.go   |  79 ++++++++++++++
 examples/cors_demo/main.tf   | 200 +++++++++++++++++++++++++++++++++++
 4 files changed, 294 insertions(+)
 create mode 100644 examples/cors_demo/README.md
 create mode 100644 examples/cors_demo/apps.tf
 create mode 100644 examples/cors_demo/main.go
 create mode 100644 examples/cors_demo/main.tf

diff --git a/examples/cors_demo/README.md b/examples/cors_demo/README.md
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/examples/cors_demo/apps.tf b/examples/cors_demo/apps.tf
new file mode 100644
index 0000000000000..4835b7b69db43
--- /dev/null
+++ b/examples/cors_demo/apps.tf
@@ -0,0 +1,15 @@
+resource "coder_app" "code-server" {
+  agent_id     = coder_agent.main.id
+  slug         = "code-server"
+  display_name = "VS Code"
+  icon         = "${data.coder_workspace.me.access_url}/icon/code.svg"
+  url          = "http://localhost:13337"
+  share        = "owner"
+  subdomain    = false
+  open_in      = "window"
+  healthcheck {
+    url       = "http://localhost:13337/healthz"
+    interval  = 5
+    threshold = 6
+  }
+}
diff --git a/examples/cors_demo/main.go b/examples/cors_demo/main.go
new file mode 100644
index 0000000000000..95b3e2008e649
--- /dev/null
+++ b/examples/cors_demo/main.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"html/template"
+	"log"
+	"net/http"
+)
+
+var enableCORS bool
+var port int
+
+func main() {
+	flag.IntVar(&port, "port", 7600, "Port to run the server on")
+	flag.BoolVar(&enableCORS, "cors", false, "Enable CORS headers")
+	flag.Parse()
+
+	http.HandleFunc("/", serveHTML)
+	http.HandleFunc("/test", testHandler)
+
+	fmt.Printf("Server started at :%d\n", port)
+	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
+}
+
+func serveHTML(w http.ResponseWriter, r *http.Request) {
+	if enableCORS {
+		w.Header().Set("Access-Control-Allow-Origin", "*")
+	}
+	tmpl := `
+	<!DOCTYPE html>
+	<html>
+	<head>
+		<title>Fetch Test</title>
+	</head>
+	<body>
+		<h1>Fetch Tester</h1>
+		<input type="text" id="url" placeholder="Enter URL" size="50" value="https://enable-cors.org/" />
+		<select id="method">
+			<option>GET</option>
+			<option>POST</option>
+			<option>PUT</option>
+			<option>DELETE</option>
+		</select>
+		<button onclick="sendRequest()">Send Request</button>
+		<ul id="log"></ul>
+
+		<script>
+			function sendRequest() {
+				const url = document.getElementById('url').value;
+				const method = document.getElementById('method').value;
+				fetch(url, { method: method })
+					.then(response => {
+						const log = document.getElementById('log');
+						const li = document.createElement('li');
+						li.textContent = method + " " + url + ": " + response.status;
+						log.appendChild(li);
+					})
+					.catch(error => {
+						const log = document.getElementById('log');
+						const li = document.createElement('li');
+						li.textContent = '(check network log for details) Error: ' + error;
+						log.appendChild(li);
+					});
+			}
+		</script>
+	</body>
+	</html>
+	`
+	t := template.Must(template.New("page").Parse(tmpl))
+	t.Execute(w, nil)
+}
+
+func testHandler(w http.ResponseWriter, r *http.Request) {
+	if enableCORS {
+		w.Header().Set("Access-Control-Allow-Origin", "*")
+	}
+	fmt.Fprintf(w, "You made a %s request to /test", r.Method)
+}
diff --git a/examples/cors_demo/main.tf b/examples/cors_demo/main.tf
new file mode 100644
index 0000000000000..442d2af10c791
--- /dev/null
+++ b/examples/cors_demo/main.tf
@@ -0,0 +1,200 @@
+terraform {
+  required_providers {
+    coder = {
+      source = "coder/coder"
+    }
+    docker = {
+      source = "kreuzwerker/docker"
+    }
+  }
+}
+
+locals {
+  username = data.coder_workspace_owner.me.name
+}
+
+variable "docker_socket" {
+  default     = ""
+  description = "(Optional) Docker socket URI"
+  type        = string
+}
+
+provider "docker" {
+  # Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
+  host = var.docker_socket != "" ? var.docker_socket : null
+}
+
+data "coder_provisioner" "me" {}
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+
+resource "coder_agent" "main" {
+  arch           = data.coder_provisioner.me.arch
+  os             = "linux"
+  startup_script = <<-EOT
+    set -e
+
+    # Prepare user home with default files on first start.
+    if [ ! -f ~/.init_done ]; then
+      cp -rT /etc/skel ~
+      touch ~/.init_done
+    fi
+
+    # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
+  EOT
+
+  # These environment variables allow you to make Git commits right away after creating a
+  # workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
+  # You can remove this block if you'd prefer to configure Git manually or using
+  # dotfiles. (see docs/dotfiles.md)
+  env = {
+    GIT_AUTHOR_NAME     = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
+    GIT_AUTHOR_EMAIL    = "${data.coder_workspace_owner.me.email}"
+    GIT_COMMITTER_NAME  = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
+    GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
+  }
+
+  # The following metadata blocks are optional. They are used to display
+  # information about your workspace in the dashboard. You can remove them
+  # if you don't want to display any information.
+  # For basic resources, you can use the `coder stat` command.
+  # If you need more control, you can write your own script.
+  metadata {
+    display_name = "CPU Usage"
+    key          = "0_cpu_usage"
+    script       = "coder stat cpu"
+    interval     = 10
+    timeout      = 1
+  }
+
+  metadata {
+    display_name = "RAM Usage"
+    key          = "1_ram_usage"
+    script       = "coder stat mem"
+    interval     = 10
+    timeout      = 1
+  }
+
+  metadata {
+    display_name = "Home Disk"
+    key          = "3_home_disk"
+    script       = "coder stat disk --path $${HOME}"
+    interval     = 60
+    timeout      = 1
+  }
+
+  metadata {
+    display_name = "CPU Usage (Host)"
+    key          = "4_cpu_usage_host"
+    script       = "coder stat cpu --host"
+    interval     = 10
+    timeout      = 1
+  }
+
+  metadata {
+    display_name = "Memory Usage (Host)"
+    key          = "5_mem_usage_host"
+    script       = "coder stat mem --host"
+    interval     = 10
+    timeout      = 1
+  }
+
+  metadata {
+    display_name = "Load Average (Host)"
+    key          = "6_load_host"
+    # get load avg scaled by number of cores
+    script   = <<EOT
+      echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
+    EOT
+    interval = 60
+    timeout  = 1
+  }
+
+  metadata {
+    display_name = "Swap Usage (Host)"
+    key          = "7_swap_host"
+    script       = <<EOT
+      free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
+    EOT
+    interval     = 10
+    timeout      = 1
+  }
+}
+
+# See https://registry.coder.com/modules/code-server
+module "code-server" {
+  count  = data.coder_workspace.me.start_count
+  source = "registry.coder.com/modules/code-server/coder"
+
+  # This ensures that the latest version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
+  version = ">= 1.0.0"
+
+  agent_id = coder_agent.main.id
+  order    = 1
+}
+
+resource "docker_volume" "home_volume" {
+  name = "coder-${data.coder_workspace.me.id}-home"
+  # Protect the volume from being deleted due to changes in attributes.
+  lifecycle {
+    ignore_changes = all
+  }
+  # Add labels in Docker to keep track of orphan resources.
+  labels {
+    label = "coder.owner"
+    value = data.coder_workspace_owner.me.name
+  }
+  labels {
+    label = "coder.owner_id"
+    value = data.coder_workspace_owner.me.id
+  }
+  labels {
+    label = "coder.workspace_id"
+    value = data.coder_workspace.me.id
+  }
+  # This field becomes outdated if the workspace is renamed but can
+  # be useful for debugging or cleaning out dangling volumes.
+  labels {
+    label = "coder.workspace_name_at_creation"
+    value = data.coder_workspace.me.name
+  }
+}
+
+resource "docker_container" "workspace" {
+  count = data.coder_workspace.me.start_count
+  image = "codercom/enterprise-base:ubuntu"
+  # Uses lower() to avoid Docker restriction on container names.
+  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
+  # Hostname makes the shell more user friendly: coder@my-workspace:~$
+  hostname = data.coder_workspace.me.name
+  # Use the docker gateway if the access URL is 127.0.0.1
+  entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
+  env        = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
+  host {
+    host = "host.docker.internal"
+    ip   = "host-gateway"
+  }
+  volumes {
+    container_path = "/home/coder"
+    volume_name    = docker_volume.home_volume.name
+    read_only      = false
+  }
+
+  # Add labels in Docker to keep track of orphan resources.
+  labels {
+    label = "coder.owner"
+    value = data.coder_workspace_owner.me.name
+  }
+  labels {
+    label = "coder.owner_id"
+    value = data.coder_workspace_owner.me.id
+  }
+  labels {
+    label = "coder.workspace_id"
+    value = data.coder_workspace.me.id
+  }
+  labels {
+    label = "coder.workspace_name"
+    value = data.coder_workspace.me.name
+  }
+}

From de30a2952f210a2c9411b23dd648362db14d0447 Mon Sep 17 00:00:00 2001
From: Steven Masley <stevenmasley@gmail.com>
Date: Tue, 29 Apr 2025 09:26:05 -0500
Subject: [PATCH 17/17] chore: add template to test cors behavior

---
 examples/cors_demo/README.md |   0
 examples/cors_demo/apps.tf   |  15 ---
 examples/cors_demo/main.tf   | 200 -----------------------------------
 3 files changed, 215 deletions(-)
 delete mode 100644 examples/cors_demo/README.md
 delete mode 100644 examples/cors_demo/apps.tf
 delete mode 100644 examples/cors_demo/main.tf

diff --git a/examples/cors_demo/README.md b/examples/cors_demo/README.md
deleted file mode 100644
index e69de29bb2d1d..0000000000000
diff --git a/examples/cors_demo/apps.tf b/examples/cors_demo/apps.tf
deleted file mode 100644
index 4835b7b69db43..0000000000000
--- a/examples/cors_demo/apps.tf
+++ /dev/null
@@ -1,15 +0,0 @@
-resource "coder_app" "code-server" {
-  agent_id     = coder_agent.main.id
-  slug         = "code-server"
-  display_name = "VS Code"
-  icon         = "${data.coder_workspace.me.access_url}/icon/code.svg"
-  url          = "http://localhost:13337"
-  share        = "owner"
-  subdomain    = false
-  open_in      = "window"
-  healthcheck {
-    url       = "http://localhost:13337/healthz"
-    interval  = 5
-    threshold = 6
-  }
-}
diff --git a/examples/cors_demo/main.tf b/examples/cors_demo/main.tf
deleted file mode 100644
index 442d2af10c791..0000000000000
--- a/examples/cors_demo/main.tf
+++ /dev/null
@@ -1,200 +0,0 @@
-terraform {
-  required_providers {
-    coder = {
-      source = "coder/coder"
-    }
-    docker = {
-      source = "kreuzwerker/docker"
-    }
-  }
-}
-
-locals {
-  username = data.coder_workspace_owner.me.name
-}
-
-variable "docker_socket" {
-  default     = ""
-  description = "(Optional) Docker socket URI"
-  type        = string
-}
-
-provider "docker" {
-  # Defaulting to null if the variable is an empty string lets us have an optional variable without having to set our own default
-  host = var.docker_socket != "" ? var.docker_socket : null
-}
-
-data "coder_provisioner" "me" {}
-data "coder_workspace" "me" {}
-data "coder_workspace_owner" "me" {}
-
-resource "coder_agent" "main" {
-  arch           = data.coder_provisioner.me.arch
-  os             = "linux"
-  startup_script = <<-EOT
-    set -e
-
-    # Prepare user home with default files on first start.
-    if [ ! -f ~/.init_done ]; then
-      cp -rT /etc/skel ~
-      touch ~/.init_done
-    fi
-
-    # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
-  EOT
-
-  # These environment variables allow you to make Git commits right away after creating a
-  # workspace. Note that they take precedence over configuration defined in ~/.gitconfig!
-  # You can remove this block if you'd prefer to configure Git manually or using
-  # dotfiles. (see docs/dotfiles.md)
-  env = {
-    GIT_AUTHOR_NAME     = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
-    GIT_AUTHOR_EMAIL    = "${data.coder_workspace_owner.me.email}"
-    GIT_COMMITTER_NAME  = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name)
-    GIT_COMMITTER_EMAIL = "${data.coder_workspace_owner.me.email}"
-  }
-
-  # The following metadata blocks are optional. They are used to display
-  # information about your workspace in the dashboard. You can remove them
-  # if you don't want to display any information.
-  # For basic resources, you can use the `coder stat` command.
-  # If you need more control, you can write your own script.
-  metadata {
-    display_name = "CPU Usage"
-    key          = "0_cpu_usage"
-    script       = "coder stat cpu"
-    interval     = 10
-    timeout      = 1
-  }
-
-  metadata {
-    display_name = "RAM Usage"
-    key          = "1_ram_usage"
-    script       = "coder stat mem"
-    interval     = 10
-    timeout      = 1
-  }
-
-  metadata {
-    display_name = "Home Disk"
-    key          = "3_home_disk"
-    script       = "coder stat disk --path $${HOME}"
-    interval     = 60
-    timeout      = 1
-  }
-
-  metadata {
-    display_name = "CPU Usage (Host)"
-    key          = "4_cpu_usage_host"
-    script       = "coder stat cpu --host"
-    interval     = 10
-    timeout      = 1
-  }
-
-  metadata {
-    display_name = "Memory Usage (Host)"
-    key          = "5_mem_usage_host"
-    script       = "coder stat mem --host"
-    interval     = 10
-    timeout      = 1
-  }
-
-  metadata {
-    display_name = "Load Average (Host)"
-    key          = "6_load_host"
-    # get load avg scaled by number of cores
-    script   = <<EOT
-      echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
-    EOT
-    interval = 60
-    timeout  = 1
-  }
-
-  metadata {
-    display_name = "Swap Usage (Host)"
-    key          = "7_swap_host"
-    script       = <<EOT
-      free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }'
-    EOT
-    interval     = 10
-    timeout      = 1
-  }
-}
-
-# See https://registry.coder.com/modules/code-server
-module "code-server" {
-  count  = data.coder_workspace.me.start_count
-  source = "registry.coder.com/modules/code-server/coder"
-
-  # This ensures that the latest version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
-  version = ">= 1.0.0"
-
-  agent_id = coder_agent.main.id
-  order    = 1
-}
-
-resource "docker_volume" "home_volume" {
-  name = "coder-${data.coder_workspace.me.id}-home"
-  # Protect the volume from being deleted due to changes in attributes.
-  lifecycle {
-    ignore_changes = all
-  }
-  # Add labels in Docker to keep track of orphan resources.
-  labels {
-    label = "coder.owner"
-    value = data.coder_workspace_owner.me.name
-  }
-  labels {
-    label = "coder.owner_id"
-    value = data.coder_workspace_owner.me.id
-  }
-  labels {
-    label = "coder.workspace_id"
-    value = data.coder_workspace.me.id
-  }
-  # This field becomes outdated if the workspace is renamed but can
-  # be useful for debugging or cleaning out dangling volumes.
-  labels {
-    label = "coder.workspace_name_at_creation"
-    value = data.coder_workspace.me.name
-  }
-}
-
-resource "docker_container" "workspace" {
-  count = data.coder_workspace.me.start_count
-  image = "codercom/enterprise-base:ubuntu"
-  # Uses lower() to avoid Docker restriction on container names.
-  name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
-  # Hostname makes the shell more user friendly: coder@my-workspace:~$
-  hostname = data.coder_workspace.me.name
-  # Use the docker gateway if the access URL is 127.0.0.1
-  entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")]
-  env        = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
-  host {
-    host = "host.docker.internal"
-    ip   = "host-gateway"
-  }
-  volumes {
-    container_path = "/home/coder"
-    volume_name    = docker_volume.home_volume.name
-    read_only      = false
-  }
-
-  # Add labels in Docker to keep track of orphan resources.
-  labels {
-    label = "coder.owner"
-    value = data.coder_workspace_owner.me.name
-  }
-  labels {
-    label = "coder.owner_id"
-    value = data.coder_workspace_owner.me.id
-  }
-  labels {
-    label = "coder.workspace_id"
-    value = data.coder_workspace.me.id
-  }
-  labels {
-    label = "coder.workspace_name"
-    value = data.coder_workspace.me.name
-  }
-}