From d6d5f7fedfe1ec37adb12bee6ab8fca84af541f2 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 25 Jan 2024 23:48:15 +0000 Subject: [PATCH 01/14] feat: add backend for jfrog xray support --- coderd/database/dbauthz/dbauthz.go | 28 ++++++ coderd/database/dbmem/dbmem.go | 45 +++++++++ coderd/database/dbmetrics/dbmetrics.go | 14 +++ coderd/database/dbmock/dbmock.go | 29 ++++++ coderd/database/dump.sql | 15 +++ coderd/database/foreign_key_constraint.go | 2 + .../migrations/000186_jfrog_xray.down.sql | 1 + .../migrations/000186_jfrog_xray.up.sql | 5 + coderd/database/models.go | 6 ++ coderd/database/querier.go | 2 + coderd/database/queries.sql.go | 49 ++++++++++ coderd/database/queries/jfrog.sql | 23 +++++ coderd/database/unique_constraint.go | 1 + codersdk/jfrog.go | 50 ++++++++++ codersdk/templates.go | 2 +- enterprise/coderd/coderd.go | 9 ++ enterprise/coderd/jfrog.go | 98 +++++++++++++++++++ enterprise/coderd/jfrog_test.go | 75 ++++++++++++++ 18 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 coderd/database/migrations/000186_jfrog_xray.down.sql create mode 100644 coderd/database/migrations/000186_jfrog_xray.up.sql create mode 100644 coderd/database/queries/jfrog.sql create mode 100644 codersdk/jfrog.go create mode 100644 enterprise/coderd/jfrog.go create mode 100644 enterprise/coderd/jfrog_test.go diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a5b295e2e35eb..396020e0c45c1 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -656,6 +656,27 @@ func authorizedTemplateVersionFromJob(ctx context.Context, q *querier, job datab } } +func (q *querier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + // TODO: Having to do all this extra querying makes me a sad panda. + workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) + if err != nil { + return xerrors.Errorf("get workspace by id: %w", err) + } + + template, err := q.db.GetTemplateByID(ctx, workspace.TemplateID) + if err != nil { + return xerrors.Errorf("get template by id: %w", err) + } + + // Only template admins should be able to write JFrog Xray scans to a workspace. + // We don't want this to be a workspace-level permission because then users + // could overwrite their own results. + if err := q.authorizeContext(ctx, rbac.ActionCreate, template); err != nil { + return err + } + return q.db.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) +} + func (q *querier) AcquireLock(ctx context.Context, id int64) error { return q.db.AcquireLock(ctx, id) } @@ -1104,6 +1125,13 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim return q.db.GetHungProvisionerJobs(ctx, hungSince) } +func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { + if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil { + return database.JfrogXrayScan{}, nil + } + return q.db.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) +} + func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) { if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil { return "", err diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 0800fb5dd0a54..79db7d8438442 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -129,6 +129,7 @@ type data struct { gitSSHKey []database.GitSSHKey groupMembers []database.GroupMember groups []database.Group + jfrogXRayScans []database.JfrogXrayScan licenses []database.License oauth2ProviderApps []database.OAuth2ProviderApp oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret @@ -1966,6 +1967,24 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T return hungJobs, nil } +func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.JfrogXrayScan{}, err + } + + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, scan := range q.jfrogXRayScans { + if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID { + return scan, nil + } + } + + return database.JfrogXrayScan{}, sql.ErrNoRows +} + func (q *FakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -5012,6 +5031,32 @@ func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGr return nil } +func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for i, scan := range q.jfrogXRayScans { + if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID { + scan.Payload = arg.Payload + q.jfrogXRayScans[i] = scan + return nil + } + } + + q.jfrogXRayScans = append(q.jfrogXRayScans, database.JfrogXrayScan{ + WorkspaceID: arg.WorkspaceID, + AgentID: arg.AgentID, + Payload: arg.Payload, + }) + + return nil +} + func (q *FakeQuerier) InsertLicense( _ context.Context, arg database.InsertLicenseParams, ) (database.License, error) { diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 625871500dbeb..653abae3d5d29 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -538,6 +538,13 @@ func (m metricsStore) GetHungProvisionerJobs(ctx context.Context, hungSince time return jobs, err } +func (m metricsStore) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { + start := time.Now() + r0, r1 := m.s.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) + m.queryLatencies.WithLabelValues("GetJFrogXrayScanByWorkspaceAndAgentID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetLastUpdateCheck(ctx context.Context) (string, error) { start := time.Now() version, err := m.s.GetLastUpdateCheck(ctx) @@ -2013,6 +2020,13 @@ func (m metricsStore) UpsertHealthSettings(ctx context.Context, value string) er return r0 } +func (m metricsStore) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + start := time.Now() + r0 := m.s.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) + m.queryLatencies.WithLabelValues("UpsertJFrogXrayScanByWorkspaceAndAgentID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) UpsertLastUpdateCheck(ctx context.Context, value string) error { start := time.Now() r0 := m.s.UpsertLastUpdateCheck(ctx, value) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index bfb93405f5524..25be1dbb9dc60 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1055,6 +1055,21 @@ func (mr *MockStoreMockRecorder) GetHungProvisionerJobs(arg0, arg1 any) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHungProvisionerJobs", reflect.TypeOf((*MockStore)(nil).GetHungProvisionerJobs), arg0, arg1) } +// GetJFrogXrayScanByWorkspaceAndAgentID mocks base method. +func (m *MockStore) GetJFrogXrayScanByWorkspaceAndAgentID(arg0 context.Context, arg1 database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetJFrogXrayScanByWorkspaceAndAgentID", arg0, arg1) + ret0, _ := ret[0].(database.JfrogXrayScan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetJFrogXrayScanByWorkspaceAndAgentID indicates an expected call of GetJFrogXrayScanByWorkspaceAndAgentID. +func (mr *MockStoreMockRecorder) GetJFrogXrayScanByWorkspaceAndAgentID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJFrogXrayScanByWorkspaceAndAgentID", reflect.TypeOf((*MockStore)(nil).GetJFrogXrayScanByWorkspaceAndAgentID), arg0, arg1) +} + // GetLastUpdateCheck mocks base method. func (m *MockStore) GetLastUpdateCheck(arg0 context.Context) (string, error) { m.ctrl.T.Helper() @@ -4228,6 +4243,20 @@ func (mr *MockStoreMockRecorder) UpsertHealthSettings(arg0, arg1 any) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertHealthSettings", reflect.TypeOf((*MockStore)(nil).UpsertHealthSettings), arg0, arg1) } +// UpsertJFrogXrayScanByWorkspaceAndAgentID mocks base method. +func (m *MockStore) UpsertJFrogXrayScanByWorkspaceAndAgentID(arg0 context.Context, arg1 database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertJFrogXrayScanByWorkspaceAndAgentID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpsertJFrogXrayScanByWorkspaceAndAgentID indicates an expected call of UpsertJFrogXrayScanByWorkspaceAndAgentID. +func (mr *MockStoreMockRecorder) UpsertJFrogXrayScanByWorkspaceAndAgentID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertJFrogXrayScanByWorkspaceAndAgentID", reflect.TypeOf((*MockStore)(nil).UpsertJFrogXrayScanByWorkspaceAndAgentID), arg0, arg1) +} + // UpsertLastUpdateCheck mocks base method. func (m *MockStore) UpsertLastUpdateCheck(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index f9d1e4311b2b2..1a1a34388d616 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -438,6 +438,12 @@ COMMENT ON COLUMN groups.display_name IS 'Display name is a custom, human-friend COMMENT ON COLUMN groups.source IS 'Source indicates how the group was created. It can be created by a user manually, or through some system process like OIDC group sync.'; +CREATE TABLE jfrog_xray_scans ( + agent_id uuid NOT NULL, + workspace_id uuid NOT NULL, + payload jsonb DEFAULT '{}'::jsonb NOT NULL +); + CREATE TABLE licenses ( id integer NOT NULL, uploaded_at timestamp with time zone NOT NULL, @@ -1289,6 +1295,9 @@ ALTER TABLE ONLY groups ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); +ALTER TABLE ONLY jfrog_xray_scans + ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id); + ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); @@ -1533,6 +1542,12 @@ ALTER TABLE ONLY group_members ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; +ALTER TABLE ONLY jfrog_xray_scans + ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + +ALTER TABLE ONLY jfrog_xray_scans + ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index 5dc75af93f1be..f5ecbe0d156b5 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -13,6 +13,8 @@ const ( ForeignKeyGroupMembersGroupID ForeignKeyConstraint = "group_members_group_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE; ForeignKeyGroupMembersUserID ForeignKeyConstraint = "group_members_user_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ForeignKeyGroupsOrganizationID ForeignKeyConstraint = "groups_organization_id_fkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ForeignKeyJfrogXrayScansAgentID ForeignKeyConstraint = "jfrog_xray_scans_agent_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ForeignKeyJfrogXrayScansWorkspaceID ForeignKeyConstraint = "jfrog_xray_scans_workspace_id_fkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE; ForeignKeyOauth2ProviderAppSecretsAppID ForeignKeyConstraint = "oauth2_provider_app_secrets_app_id_fkey" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_fkey FOREIGN KEY (app_id) REFERENCES oauth2_provider_apps(id) ON DELETE CASCADE; ForeignKeyOrganizationMembersOrganizationIDUUID ForeignKeyConstraint = "organization_members_organization_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_organization_id_uuid_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyOrganizationMembersUserIDUUID ForeignKeyConstraint = "organization_members_user_id_uuid_fkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000186_jfrog_xray.down.sql b/coderd/database/migrations/000186_jfrog_xray.down.sql new file mode 100644 index 0000000000000..8fa8f99f47bb0 --- /dev/null +++ b/coderd/database/migrations/000186_jfrog_xray.down.sql @@ -0,0 +1 @@ +DROP TABLE jfrog_xray_scans; diff --git a/coderd/database/migrations/000186_jfrog_xray.up.sql b/coderd/database/migrations/000186_jfrog_xray.up.sql new file mode 100644 index 0000000000000..ca48b6ae4b4f0 --- /dev/null +++ b/coderd/database/migrations/000186_jfrog_xray.up.sql @@ -0,0 +1,5 @@ +CREATE TABLE jfrog_xray_scans ( + agent_id uuid NOT NULL PRIMARY KEY REFERENCES workspace_agents(id) ON DELETE CASCADE, + workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, + payload jsonb NOT NULL DEFAULT '{}' +); diff --git a/coderd/database/models.go b/coderd/database/models.go index 5308f88b35a79..fa7cd040038a4 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1779,6 +1779,12 @@ type GroupMember struct { GroupID uuid.UUID `db:"group_id" json:"group_id"` } +type JfrogXrayScan struct { + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + Payload json.RawMessage `db:"payload" json:"payload"` +} + type License struct { ID int32 `db:"id" json:"id"` UploadedAt time.Time `db:"uploaded_at" json:"uploaded_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 8947ba185d14d..cc46e1022cbc3 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -118,6 +118,7 @@ type sqlcQuerier interface { GetGroupsByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]Group, error) GetHealthSettings(ctx context.Context) (string, error) GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error) + GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) GetLastUpdateCheck(ctx context.Context) (string, error) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) @@ -382,6 +383,7 @@ type sqlcQuerier interface { // The functional values are immutable and controlled implicitly. UpsertDefaultProxy(ctx context.Context, arg UpsertDefaultProxyParams) error UpsertHealthSettings(ctx context.Context, value string) error + UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error UpsertLastUpdateCheck(ctx context.Context, value string) error UpsertLogoURL(ctx context.Context, value string) error UpsertOAuthSigningKey(ctx context.Context, value string) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4c4bfc6012e7b..1b64cc081d750 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2438,6 +2438,55 @@ func (q *sqlQuerier) GetUserLatencyInsights(ctx context.Context, arg GetUserLate return items, nil } +const getJFrogXrayScanByWorkspaceAndAgentID = `-- name: GetJFrogXrayScanByWorkspaceAndAgentID :one +SELECT + agent_id, workspace_id, payload +FROM + jfrog_xray_scans +WHERE + agent_id = $1 +AND + workspace_id = $2 +LIMIT + 1 +` + +type GetJFrogXrayScanByWorkspaceAndAgentIDParams struct { + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` +} + +func (q *sqlQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) { + row := q.db.QueryRowContext(ctx, getJFrogXrayScanByWorkspaceAndAgentID, arg.AgentID, arg.WorkspaceID) + var i JfrogXrayScan + err := row.Scan(&i.AgentID, &i.WorkspaceID, &i.Payload) + return i, err +} + +const upsertJFrogXrayScanByWorkspaceAndAgentID = `-- name: UpsertJFrogXrayScanByWorkspaceAndAgentID :exec +INSERT INTO + jfrog_xray_scans ( + agent_id, + workspace_id, + payload + ) +VALUES + ($1, $2, $3) +ON CONFLICT (agent_id, workspace_id) +DO UPDATE SET payload = $3 +` + +type UpsertJFrogXrayScanByWorkspaceAndAgentIDParams struct { + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + Payload json.RawMessage `db:"payload" json:"payload"` +} + +func (q *sqlQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + _, err := q.db.ExecContext(ctx, upsertJFrogXrayScanByWorkspaceAndAgentID, arg.AgentID, arg.WorkspaceID, arg.Payload) + return err +} + const deleteLicense = `-- name: DeleteLicense :one DELETE FROM licenses diff --git a/coderd/database/queries/jfrog.sql b/coderd/database/queries/jfrog.sql new file mode 100644 index 0000000000000..6a7634c4f8ad2 --- /dev/null +++ b/coderd/database/queries/jfrog.sql @@ -0,0 +1,23 @@ +-- name: GetJFrogXrayScanByWorkspaceAndAgentID :one +SELECT + * +FROM + jfrog_xray_scans +WHERE + agent_id = $1 +AND + workspace_id = $2 +LIMIT + 1; + +-- name: UpsertJFrogXrayScanByWorkspaceAndAgentID :exec +INSERT INTO + jfrog_xray_scans ( + agent_id, + workspace_id, + payload + ) +VALUES + ($1, $2, $3) +ON CONFLICT (agent_id, workspace_id) +DO UPDATE SET payload = $3; diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index f397692f1d6d1..8c3d303dc21f0 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -19,6 +19,7 @@ const ( UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); + UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id); UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); diff --git a/codersdk/jfrog.go b/codersdk/jfrog.go new file mode 100644 index 0000000000000..04f327d7d26cb --- /dev/null +++ b/codersdk/jfrog.go @@ -0,0 +1,50 @@ +package codersdk + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/google/uuid" + "golang.org/x/xerrors" +) + +type JFrogXrayScan struct { + WorkspaceID uuid.UUID `json:"workspace_id"` + AgentID uuid.UUID `json:"agent_id"` + Critical int `json:"critical"` + High int `json:"high"` + Medium int `json:"medium"` + ResultsURL string `json:"results_url"` +} + +func (c *Client) PostJFrogXrayScan(ctx context.Context, req JFrogXrayScan) error { + res, err := c.Request(ctx, http.MethodPost, "/api/v2/exp/jfrog/xray-scan", req) + if err != nil { + return xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusCreated { + return ReadBodyAsError(res) + } + return nil +} + +func (c *Client) JFrogXRayScan(ctx context.Context, workspaceID, agentID uuid.UUID) (JFrogXrayScan, error) { + res, err := c.Request(ctx, http.MethodGet, "/api/v2/exp/jfrog/xray-scan", nil, + WithQueryParam("workspace_id", workspaceID.String()), + WithQueryParam("agent_id", agentID.String()), + ) + if err != nil { + return JFrogXrayScan{}, xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return JFrogXrayScan{}, ReadBodyAsError(res) + } + + var resp JFrogXrayScan + return resp, json.NewDecoder(res.Body).Decode(&resp) +} diff --git a/codersdk/templates.go b/codersdk/templates.go index 1be4d931ad7a2..f60a66a80e5dc 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -263,7 +263,7 @@ type TemplateExample struct { func (c *Client) Template(ctx context.Context, template uuid.UUID) (Template, error) { res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s", template), nil) if err != nil { - return Template{}, nil + return Template{}, xerrors.Errorf("do request: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index af56626a8db68..80f37890a3034 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -347,6 +347,15 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { }) }) }) + r.Route("/exp", func(r chi.Router) { + r.Use( + apiKeyMiddleware, + api.jfrogEnabledMW, + ) + + r.Post("/jfrog/xray-scan", api.postJFrogXrayScan) + r.Get("/jfrog/xray-scan", api.jFrogXrayScan) + }) }) if len(options.SCIMAPIKey) != 0 { diff --git a/enterprise/coderd/jfrog.go b/enterprise/coderd/jfrog.go new file mode 100644 index 0000000000000..3dc646fc4dd69 --- /dev/null +++ b/enterprise/coderd/jfrog.go @@ -0,0 +1,98 @@ +package coderd + +import ( + "database/sql" + "encoding/json" + "net/http" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/httpapi" + "github.com/coder/coder/v2/codersdk" +) + +func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + ) + + var req codersdk.JFrogXrayScan + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + payload, err := json.Marshal(req) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + err = api.Database.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ + WorkspaceID: req.WorkspaceID, + AgentID: req.AgentID, + Payload: payload, + }) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.Response{ + Message: "Successfully inserted JFrog XRay scan!", + }) +} + +func (api *API) jFrogXrayScan(rw http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + wid = r.URL.Query().Get("workspace_id") + aid = r.URL.Query().Get("agent_id") + ) + + wsID, err := uuid.Parse(wid) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "'workspace_id' must be a valid UUID.", + }) + return + } + + agentID, err := uuid.Parse(aid) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "'agent_id' must be a valid UUID.", + }) + return + } + + scan, err := api.Database.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, database.GetJFrogXrayScanByWorkspaceAndAgentIDParams{ + WorkspaceID: wsID, + AgentID: agentID, + }) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + httpapi.InternalServerError(rw, err) + return + } + if scan.Payload == nil { + scan.Payload = []byte("{}") + } + + httpapi.Write(ctx, rw, http.StatusOK, scan.Payload) +} + +func (api *API) jfrogEnabledMW(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + api.entitlementsMu.RLock() + enabled := api.entitlements.Features[codersdk.FeatureMultipleExternalAuth].Enabled + api.entitlementsMu.RUnlock() + + if !enabled { + httpapi.RouteNotFound(rw) + return + } + + next.ServeHTTP(rw, r) + }) +} diff --git a/enterprise/coderd/jfrog_test.go b/enterprise/coderd/jfrog_test.go new file mode 100644 index 0000000000000..219144708a48f --- /dev/null +++ b/enterprise/coderd/jfrog_test.go @@ -0,0 +1,75 @@ +package coderd_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbfake" + "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" + "github.com/coder/coder/v2/enterprise/coderd/license" + "github.com/coder/coder/v2/testutil" +) + +func TestJFrogXrayScan(t *testing.T) { + t.Parallel() + + t.Run("Post/Get", func(t *testing.T) { + t.Parallel() + ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{codersdk.FeatureMultipleExternalAuth: 1}, + }, + }) + + tac, ta := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + + wsResp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: owner.OrganizationID, + OwnerID: ta.ID, + }).WithAgent().Do() + + ws := coderdtest.MustWorkspace(t, tac, wsResp.Workspace.ID) + require.Len(t, ws.LatestBuild.Resources, 1) + require.Len(t, ws.LatestBuild.Resources[0].Agents, 1) + + agentID := ws.LatestBuild.Resources[0].Agents[0].ID + expectedPayload := codersdk.JFrogXrayScan{ + WorkspaceID: ws.ID, + AgentID: agentID, + Critical: 19, + High: 5, + Medium: 3, + ResultsURL: "https://hello-world", + } + + ctx := testutil.Context(t, testutil.WaitMedium) + err := tac.PostJFrogXrayScan(ctx, expectedPayload) + require.NoError(t, err) + + resp1, err := tac.JFrogXRayScan(ctx, ws.ID, agentID) + require.NoError(t, err) + require.Equal(t, expectedPayload, resp1) + + // Can update again without error. + expectedPayload = codersdk.JFrogXrayScan{ + WorkspaceID: ws.ID, + AgentID: agentID, + Critical: 20, + High: 22, + Medium: 8, + ResultsURL: "https://goodbye-world", + } + err = tac.PostJFrogXrayScan(ctx, expectedPayload) + require.NoError(t, err) + + resp2, err := tac.JFrogXRayScan(ctx, ws.ID, agentID) + require.NoError(t, err) + require.NotEqual(t, expectedPayload, resp1) + require.Equal(t, expectedPayload, resp2) + }) +} From 31fa40f9b4ea75654fca329c2f87dce6a76e1e32 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 25 Jan 2024 23:57:21 +0000 Subject: [PATCH 02/14] make gen --- coderd/database/dbauthz/dbauthz.go | 42 +++++++-------- coderd/database/dbmem/dbmem.go | 52 +++++++++---------- .../migrations/000186_jfrog_xray.up.sql | 4 +- enterprise/coderd/jfrog.go | 6 +++ enterprise/coderd/jfrog_test.go | 47 +++++++++++++++++ site/src/api/typesGenerated.ts | 10 ++++ 6 files changed, 112 insertions(+), 49 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 396020e0c45c1..69aeb081eaf4b 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -656,27 +656,6 @@ func authorizedTemplateVersionFromJob(ctx context.Context, q *querier, job datab } } -func (q *querier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { - // TODO: Having to do all this extra querying makes me a sad panda. - workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) - if err != nil { - return xerrors.Errorf("get workspace by id: %w", err) - } - - template, err := q.db.GetTemplateByID(ctx, workspace.TemplateID) - if err != nil { - return xerrors.Errorf("get template by id: %w", err) - } - - // Only template admins should be able to write JFrog Xray scans to a workspace. - // We don't want this to be a workspace-level permission because then users - // could overwrite their own results. - if err := q.authorizeContext(ctx, rbac.ActionCreate, template); err != nil { - return err - } - return q.db.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) -} - func (q *querier) AcquireLock(ctx context.Context, id int64) error { return q.db.AcquireLock(ctx, id) } @@ -3167,6 +3146,27 @@ func (q *querier) UpsertHealthSettings(ctx context.Context, value string) error return q.db.UpsertHealthSettings(ctx, value) } +func (q *querier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + // TODO: Having to do all this extra querying makes me a sad panda. + workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) + if err != nil { + return xerrors.Errorf("get workspace by id: %w", err) + } + + template, err := q.db.GetTemplateByID(ctx, workspace.TemplateID) + if err != nil { + return xerrors.Errorf("get template by id: %w", err) + } + + // Only template admins should be able to write JFrog Xray scans to a workspace. + // We don't want this to be a workspace-level permission because then users + // could overwrite their own results. + if err := q.authorizeContext(ctx, rbac.ActionCreate, template); err != nil { + return err + } + return q.db.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) +} + func (q *querier) UpsertLastUpdateCheck(ctx context.Context, value string) error { if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil { return err diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 79db7d8438442..b27ba9dacf28b 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -5031,32 +5031,6 @@ func (q *FakeQuerier) InsertGroupMember(_ context.Context, arg database.InsertGr return nil } -func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { - err := validateDatabaseType(arg) - if err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - - for i, scan := range q.jfrogXRayScans { - if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID { - scan.Payload = arg.Payload - q.jfrogXRayScans[i] = scan - return nil - } - } - - q.jfrogXRayScans = append(q.jfrogXRayScans, database.JfrogXrayScan{ - WorkspaceID: arg.WorkspaceID, - AgentID: arg.AgentID, - Payload: arg.Payload, - }) - - return nil -} - func (q *FakeQuerier) InsertLicense( _ context.Context, arg database.InsertLicenseParams, ) (database.License, error) { @@ -7297,6 +7271,32 @@ func (q *FakeQuerier) UpsertHealthSettings(_ context.Context, data string) error return nil } +func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { + err := validateDatabaseType(arg) + if err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + for i, scan := range q.jfrogXRayScans { + if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID { + scan.Payload = arg.Payload + q.jfrogXRayScans[i] = scan + return nil + } + } + + q.jfrogXRayScans = append(q.jfrogXRayScans, database.JfrogXrayScan{ + WorkspaceID: arg.WorkspaceID, + AgentID: arg.AgentID, + Payload: arg.Payload, + }) + + return nil +} + func (q *FakeQuerier) UpsertLastUpdateCheck(_ context.Context, data string) error { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/coderd/database/migrations/000186_jfrog_xray.up.sql b/coderd/database/migrations/000186_jfrog_xray.up.sql index ca48b6ae4b4f0..576e295f647c0 100644 --- a/coderd/database/migrations/000186_jfrog_xray.up.sql +++ b/coderd/database/migrations/000186_jfrog_xray.up.sql @@ -1,5 +1,5 @@ CREATE TABLE jfrog_xray_scans ( - agent_id uuid NOT NULL PRIMARY KEY REFERENCES workspace_agents(id) ON DELETE CASCADE, + agent_id uuid NOT NULL PRIMARY KEY REFERENCES workspace_agents(id) ON DELETE CASCADE, workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, - payload jsonb NOT NULL DEFAULT '{}' + payload jsonb NOT NULL DEFAULT '{}' ); diff --git a/enterprise/coderd/jfrog.go b/enterprise/coderd/jfrog.go index 3dc646fc4dd69..f465dd23fa74f 100644 --- a/enterprise/coderd/jfrog.go +++ b/enterprise/coderd/jfrog.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" ) @@ -34,6 +35,11 @@ func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { AgentID: req.AgentID, Payload: payload, }) + if dbauthz.IsNotAuthorizedError(err) { + httpapi.Forbidden(rw) + return + } + if err != nil { httpapi.InternalServerError(rw, err) return diff --git a/enterprise/coderd/jfrog_test.go b/enterprise/coderd/jfrog_test.go index 219144708a48f..5dd2e3b963c79 100644 --- a/enterprise/coderd/jfrog_test.go +++ b/enterprise/coderd/jfrog_test.go @@ -1,6 +1,7 @@ package coderd_test import ( + "net/http" "testing" "github.com/stretchr/testify/require" @@ -72,4 +73,50 @@ func TestJFrogXrayScan(t *testing.T) { require.NotEqual(t, expectedPayload, resp1) require.Equal(t, expectedPayload, resp2) }) + + t.Run("MemberPostUnauthorized", func(t *testing.T) { + t.Parallel() + + ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{codersdk.FeatureMultipleExternalAuth: 1}, + }, + }) + + memberClient, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + + wsResp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: owner.OrganizationID, + OwnerID: member.ID, + }).WithAgent().Do() + + ws := coderdtest.MustWorkspace(t, memberClient, wsResp.Workspace.ID) + require.Len(t, ws.LatestBuild.Resources, 1) + require.Len(t, ws.LatestBuild.Resources[0].Agents, 1) + + agentID := ws.LatestBuild.Resources[0].Agents[0].ID + expectedPayload := codersdk.JFrogXrayScan{ + WorkspaceID: ws.ID, + AgentID: agentID, + Critical: 19, + High: 5, + Medium: 3, + ResultsURL: "https://hello-world", + } + + ctx := testutil.Context(t, testutil.WaitMedium) + err := memberClient.PostJFrogXrayScan(ctx, expectedPayload) + require.Error(t, err) + cerr, ok := codersdk.AsError(err) + require.True(t, ok) + require.Equal(t, http.StatusForbidden, cerr.StatusCode()) + + err = ownerClient.PostJFrogXrayScan(ctx, expectedPayload) + require.NoError(t, err) + + // We should still be able to fetch. + resp1, err := memberClient.JFrogXRayScan(ctx, ws.ID, agentID) + require.NoError(t, err) + require.Equal(t, expectedPayload, resp1) + }) } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 48d05be9d9e73..728e5ba792bbe 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -607,6 +607,16 @@ export interface IssueReconnectingPTYSignedTokenResponse { readonly signed_token: string; } +// From codersdk/jfrog.go +export interface JFrogXrayScan { + readonly workspace_id: string; + readonly agent_id: string; + readonly critical: number; + readonly high: number; + readonly medium: number; + readonly results_url: string; +} + // From codersdk/licenses.go export interface License { readonly id: number; From 0758aea7cdccfb62dd947ad52d47a0237fe0bf65 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 00:04:11 +0000 Subject: [PATCH 03/14] add unique constraint --- coderd/database/dump.sql | 3 +++ coderd/database/migrations/000186_jfrog_xray.down.sql | 1 + coderd/database/migrations/000186_jfrog_xray.up.sql | 2 ++ coderd/database/unique_constraint.go | 1 + 4 files changed, 7 insertions(+) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 1a1a34388d616..8f4607b26f1db 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1298,6 +1298,9 @@ ALTER TABLE ONLY groups ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id); +ALTER TABLE ONLY jfrog_xray_scans + ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); + ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); diff --git a/coderd/database/migrations/000186_jfrog_xray.down.sql b/coderd/database/migrations/000186_jfrog_xray.down.sql index 8fa8f99f47bb0..bb3a62da7c3f6 100644 --- a/coderd/database/migrations/000186_jfrog_xray.down.sql +++ b/coderd/database/migrations/000186_jfrog_xray.down.sql @@ -1 +1,2 @@ +ALTER TABLE jfrog_xray_scans DROP CONSTRAINT jfrog_xray_scans_workspace_id_agent_id; DROP TABLE jfrog_xray_scans; diff --git a/coderd/database/migrations/000186_jfrog_xray.up.sql b/coderd/database/migrations/000186_jfrog_xray.up.sql index 576e295f647c0..11e5db199a928 100644 --- a/coderd/database/migrations/000186_jfrog_xray.up.sql +++ b/coderd/database/migrations/000186_jfrog_xray.up.sql @@ -3,3 +3,5 @@ CREATE TABLE jfrog_xray_scans ( workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, payload jsonb NOT NULL DEFAULT '{}' ); + +ALTER TABLE jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index 8c3d303dc21f0..a4a5bcdae278b 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -20,6 +20,7 @@ const ( UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id); + UniqueJfrogXrayScansWorkspaceIDAgentID UniqueConstraint = "jfrog_xray_scans_workspace_id_agent_id" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); From 237221ef53839d9a6a05143444542be94e6645cd Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 00:05:03 +0000 Subject: [PATCH 04/14] lint --- coderd/database/dbmem/dbmem.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index b27ba9dacf28b..dddaf11393086 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -1967,7 +1967,7 @@ func (q *FakeQuerier) GetHungProvisionerJobs(_ context.Context, hungSince time.T return hungJobs, nil } -func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { +func (q *FakeQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { err := validateDatabaseType(arg) if err != nil { return database.JfrogXrayScan{}, err @@ -7271,7 +7271,7 @@ func (q *FakeQuerier) UpsertHealthSettings(_ context.Context, data string) error return nil } -func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { +func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(_ context.Context, arg database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { err := validateDatabaseType(arg) if err != nil { return err From cd8dc8b99d1a77155a64606d3d826c2a077c8bb8 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 00:05:56 +0000 Subject: [PATCH 05/14] whoops --- coderd/database/dbauthz/dbauthz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 69aeb081eaf4b..8c3d9f417f03b 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1106,7 +1106,7 @@ func (q *querier) GetHungProvisionerJobs(ctx context.Context, hungSince time.Tim func (q *querier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg database.GetJFrogXrayScanByWorkspaceAndAgentIDParams) (database.JfrogXrayScan, error) { if _, err := fetch(q.log, q.auth, q.db.GetWorkspaceByID)(ctx, arg.WorkspaceID); err != nil { - return database.JfrogXrayScan{}, nil + return database.JfrogXrayScan{}, err } return q.db.GetJFrogXrayScanByWorkspaceAndAgentID(ctx, arg) } From 7b41dba02dd0e14d69a79aa2532dba80ee28d985 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 01:11:45 +0000 Subject: [PATCH 06/14] lint --- coderd/database/dbmem/dbmem.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index dddaf11393086..6e7dda114c0dd 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -7288,6 +7288,7 @@ func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(_ context.Context } } + //nolint:gosimple q.jfrogXRayScans = append(q.jfrogXRayScans, database.JfrogXrayScan{ WorkspaceID: arg.WorkspaceID, AgentID: arg.AgentID, From d4f6dabaf6e19c3efb8f1b81353e0103a0d5e60f Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 01:18:20 +0000 Subject: [PATCH 07/14] bump migration --- coderd/apidoc/docs.go | 64 +++++++++++++++++++ coderd/apidoc/swagger.json | 60 +++++++++++++++++ ...ay.down.sql => 000187_jfrog_xray.down.sql} | 0 ...g_xray.up.sql => 000187_jfrog_xray.up.sql} | 0 docs/api/enterprise.md | 43 +++++++++++++ docs/api/schemas.md | 24 +++++++ enterprise/coderd/jfrog.go | 25 +++++++- 7 files changed, 213 insertions(+), 3 deletions(-) rename coderd/database/migrations/{000186_jfrog_xray.down.sql => 000187_jfrog_xray.down.sql} (100%) rename coderd/database/migrations/{000186_jfrog_xray.up.sql => 000187_jfrog_xray.up.sql} (100%) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 098ea767e4ffe..de114d684363a 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -692,6 +692,47 @@ const docTemplate = `{ } } }, + "/exp/jfrog/xray-scan": { + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Get JFrog XRay scan by workspace agent ID.", + "operationId": "get-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "type": "string", + "description": "Workspace ID", + "name": "workspace_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Agent ID", + "name": "agent_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + } + } + }, "/experiments": { "get": { "security": [ @@ -9581,6 +9622,29 @@ const docTemplate = `{ } } }, + "codersdk.JFrogXrayScan": { + "type": "object", + "properties": { + "agent_id": { + "type": "string" + }, + "critical": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "results_url": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + } + }, "codersdk.JobErrorCode": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 24bc5e29cc05c..a174a79feadd3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -590,6 +590,43 @@ } } }, + "/exp/jfrog/xray-scan": { + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Get JFrog XRay scan by workspace agent ID.", + "operationId": "get-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "type": "string", + "description": "Workspace ID", + "name": "workspace_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Agent ID", + "name": "agent_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + } + } + }, "/experiments": { "get": { "security": [ @@ -8607,6 +8644,29 @@ } } }, + "codersdk.JFrogXrayScan": { + "type": "object", + "properties": { + "agent_id": { + "type": "string" + }, + "critical": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "results_url": { + "type": "string" + }, + "workspace_id": { + "type": "string" + } + } + }, "codersdk.JobErrorCode": { "type": "string", "enum": ["REQUIRED_TEMPLATE_VARIABLES"], diff --git a/coderd/database/migrations/000186_jfrog_xray.down.sql b/coderd/database/migrations/000187_jfrog_xray.down.sql similarity index 100% rename from coderd/database/migrations/000186_jfrog_xray.down.sql rename to coderd/database/migrations/000187_jfrog_xray.down.sql diff --git a/coderd/database/migrations/000186_jfrog_xray.up.sql b/coderd/database/migrations/000187_jfrog_xray.up.sql similarity index 100% rename from coderd/database/migrations/000186_jfrog_xray.up.sql rename to coderd/database/migrations/000187_jfrog_xray.up.sql diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 1ae77d4b7edbb..998e0cdbac6d7 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -152,6 +152,49 @@ curl -X GET http://coder-server:8080/api/v2/entitlements \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get JFrog XRay scan by workspace agent ID. + +### Code samples + +```shell +# Example request using curl +curl -X POST http://coder-server:8080/api/v2/exp/jfrog/xray-scan?workspace_id=string&agent_id=string \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`POST /exp/jfrog/xray-scan` + +### Parameters + +| Name | In | Type | Required | Description | +| -------------- | ----- | ------ | -------- | ------------ | +| `workspace_id` | query | string | true | Workspace ID | +| `agent_id` | query | string | true | Agent ID | + +### Example responses + +> 200 Response + +```json +{ + "agent_id": "string", + "critical": 0, + "high": 0, + "medium": 0, + "results_url": "string", + "workspace_id": "string" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get group by ID ### Code samples diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 8114d0750b65e..c663beab0f51c 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3339,6 +3339,30 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | -------------- | ------ | -------- | ------------ | ----------- | | `signed_token` | string | false | | | +## codersdk.JFrogXrayScan + +```json +{ + "agent_id": "string", + "critical": 0, + "high": 0, + "medium": 0, + "results_url": "string", + "workspace_id": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ------- | -------- | ------------ | ----------- | +| `agent_id` | string | false | | | +| `critical` | integer | false | | | +| `high` | integer | false | | | +| `medium` | integer | false | | | +| `results_url` | string | false | | | +| `workspace_id` | string | false | | | + ## codersdk.JobErrorCode ```json diff --git a/enterprise/coderd/jfrog.go b/enterprise/coderd/jfrog.go index f465dd23fa74f..d5f6e2c272218 100644 --- a/enterprise/coderd/jfrog.go +++ b/enterprise/coderd/jfrog.go @@ -14,10 +14,18 @@ import ( "github.com/coder/coder/v2/codersdk" ) +// Post workspace agent results for a JFrog XRay scan. +// +// @Summary Post JFrog XRay scan by workspace agent ID. +// @ID post-jfrog-xray-scan-by-workspace-agent-id +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Param request body codersdk.JFrogXrayScan true "Post JFrog XRay scan request" +// @Success 200 {object} codersdk.Response +// @Router /exp/jfrog/xray-scan [post] func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - ) + ctx := r.Context() var req codersdk.JFrogXrayScan if !httpapi.Read(ctx, rw, r, &req) { @@ -50,6 +58,17 @@ func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { }) } +// Get workspace agent results for a JFrog XRay scan. +// +// @Summary Get JFrog XRay scan by workspace agent ID. +// @ID get-jfrog-xray-scan-by-workspace-agent-id +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Param workspace_id query string true "Workspace ID" +// @Param agent_id query string true "Agent ID" +// @Success 200 {object} codersdk.JFrogXrayScan +// @Router /exp/jfrog/xray-scan [post] func (api *API) jFrogXrayScan(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() From da35a469452132f1a0904bf875231125ac5b11ef Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 01:27:35 +0000 Subject: [PATCH 08/14] fix swagger --- coderd/apidoc/docs.go | 36 +++++++++++++++++++++- coderd/apidoc/swagger.json | 32 +++++++++++++++++++- docs/api/enterprise.md | 62 ++++++++++++++++++++++++++++++++++++-- enterprise/coderd/jfrog.go | 2 +- 4 files changed, 127 insertions(+), 5 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index de114d684363a..81f01a826f91f 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -693,7 +693,7 @@ const docTemplate = `{ } }, "/exp/jfrog/xray-scan": { - "post": { + "get": { "security": [ { "CoderSessionToken": [] @@ -731,6 +731,40 @@ const docTemplate = `{ } } } + }, + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Post JFrog XRay scan by workspace agent ID.", + "operationId": "post-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "description": "Post JFrog XRay scan request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + } } }, "/experiments": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a174a79feadd3..0de7307216a7f 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -591,7 +591,7 @@ } }, "/exp/jfrog/xray-scan": { - "post": { + "get": { "security": [ { "CoderSessionToken": [] @@ -625,6 +625,36 @@ } } } + }, + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Post JFrog XRay scan by workspace agent ID.", + "operationId": "post-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "description": "Post JFrog XRay scan request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + } } }, "/experiments": { diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 998e0cdbac6d7..0fe76dedf8069 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -158,12 +158,12 @@ To perform this operation, you must be authenticated. [Learn more](authenticatio ```shell # Example request using curl -curl -X POST http://coder-server:8080/api/v2/exp/jfrog/xray-scan?workspace_id=string&agent_id=string \ +curl -X GET http://coder-server:8080/api/v2/exp/jfrog/xray-scan?workspace_id=string&agent_id=string \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`POST /exp/jfrog/xray-scan` +`GET /exp/jfrog/xray-scan` ### Parameters @@ -195,6 +195,64 @@ curl -X POST http://coder-server:8080/api/v2/exp/jfrog/xray-scan?workspace_id=st To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Post JFrog XRay scan by workspace agent ID. + +### Code samples + +```shell +# Example request using curl +curl -X POST http://coder-server:8080/api/v2/exp/jfrog/xray-scan \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`POST /exp/jfrog/xray-scan` + +> Body parameter + +```json +{ + "agent_id": "string", + "critical": 0, + "high": 0, + "medium": 0, + "results_url": "string", + "workspace_id": "string" +} +``` + +### Parameters + +| Name | In | Type | Required | Description | +| ------ | ---- | ---------------------------------------------------------- | -------- | ---------------------------- | +| `body` | body | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | true | Post JFrog XRay scan request | + +### Example responses + +> 200 Response + +```json +{ + "detail": "string", + "message": "string", + "validations": [ + { + "detail": "string", + "field": "string" + } + ] +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get group by ID ### Code samples diff --git a/enterprise/coderd/jfrog.go b/enterprise/coderd/jfrog.go index d5f6e2c272218..00e080a21aaff 100644 --- a/enterprise/coderd/jfrog.go +++ b/enterprise/coderd/jfrog.go @@ -68,7 +68,7 @@ func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { // @Param workspace_id query string true "Workspace ID" // @Param agent_id query string true "Agent ID" // @Success 200 {object} codersdk.JFrogXrayScan -// @Router /exp/jfrog/xray-scan [post] +// @Router /exp/jfrog/xray-scan [get] func (api *API) jFrogXrayScan(rw http.ResponseWriter, r *http.Request) { var ( ctx = r.Context() From c11cef4d773f4625c894bb7f5d8937e2cee8047e Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 01:50:31 +0000 Subject: [PATCH 09/14] try again --- coderd/apidoc/docs.go | 9 +++++++-- coderd/apidoc/swagger.json | 7 +++++-- codersdk/jfrog.go | 4 ++-- docs/api/enterprise.md | 8 ++++---- docs/api/schemas.md | 4 ++-- enterprise/coderd/jfrog.go | 1 + 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 81f01a826f91f..5cf71bc155e21 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -738,6 +738,9 @@ const docTemplate = `{ "CoderSessionToken": [] } ], + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], @@ -9660,7 +9663,8 @@ const docTemplate = `{ "type": "object", "properties": { "agent_id": { - "type": "string" + "type": "string", + "format": "uuid" }, "critical": { "type": "integer" @@ -9675,7 +9679,8 @@ const docTemplate = `{ "type": "string" }, "workspace_id": { - "type": "string" + "type": "string", + "format": "uuid" } } }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 0de7307216a7f..f1bd1f335f7d3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -632,6 +632,7 @@ "CoderSessionToken": [] } ], + "consumes": ["application/json"], "produces": ["application/json"], "tags": ["Enterprise"], "summary": "Post JFrog XRay scan by workspace agent ID.", @@ -8678,7 +8679,8 @@ "type": "object", "properties": { "agent_id": { - "type": "string" + "type": "string", + "format": "uuid" }, "critical": { "type": "integer" @@ -8693,7 +8695,8 @@ "type": "string" }, "workspace_id": { - "type": "string" + "type": "string", + "format": "uuid" } } }, diff --git a/codersdk/jfrog.go b/codersdk/jfrog.go index 04f327d7d26cb..8464a35918f0b 100644 --- a/codersdk/jfrog.go +++ b/codersdk/jfrog.go @@ -10,8 +10,8 @@ import ( ) type JFrogXrayScan struct { - WorkspaceID uuid.UUID `json:"workspace_id"` - AgentID uuid.UUID `json:"agent_id"` + WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"` + AgentID uuid.UUID `json:"agent_id" format:"uuid"` Critical int `json:"critical"` High int `json:"high"` Medium int `json:"medium"` diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 0fe76dedf8069..ec470ca1aabb3 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -178,12 +178,12 @@ curl -X GET http://coder-server:8080/api/v2/exp/jfrog/xray-scan?workspace_id=str ```json { - "agent_id": "string", + "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", "critical": 0, "high": 0, "medium": 0, "results_url": "string", - "workspace_id": "string" + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" } ``` @@ -213,12 +213,12 @@ curl -X POST http://coder-server:8080/api/v2/exp/jfrog/xray-scan \ ```json { - "agent_id": "string", + "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", "critical": 0, "high": 0, "medium": 0, "results_url": "string", - "workspace_id": "string" + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" } ``` diff --git a/docs/api/schemas.md b/docs/api/schemas.md index c663beab0f51c..0ec54af432301 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3343,12 +3343,12 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { - "agent_id": "string", + "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", "critical": 0, "high": 0, "medium": 0, "results_url": "string", - "workspace_id": "string" + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" } ``` diff --git a/enterprise/coderd/jfrog.go b/enterprise/coderd/jfrog.go index 00e080a21aaff..c5423cd546e0a 100644 --- a/enterprise/coderd/jfrog.go +++ b/enterprise/coderd/jfrog.go @@ -19,6 +19,7 @@ import ( // @Summary Post JFrog XRay scan by workspace agent ID. // @ID post-jfrog-xray-scan-by-workspace-agent-id // @Security CoderSessionToken +// @Accept json // @Produce json // @Tags Enterprise // @Param request body codersdk.JFrogXrayScan true "Post JFrog XRay scan request" From 02050bd14390be1b3615c1bf128380575cb049b8 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 26 Jan 2024 02:11:56 +0000 Subject: [PATCH 10/14] add dbauthz test --- coderd/database/dbauthz/dbauthz_test.go | 34 ++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 9668497dbfbae..8d114a4125cab 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -364,7 +364,7 @@ func (s *MethodTestSuite) TestGroup() { })) } -func (s *MethodTestSuite) TestProvsionerJob() { +func (s *MethodTestSuite) TestProvisionerJob() { s.Run("ArchiveUnusedTemplateVersions", s.Subtest(func(db database.Store, check *expects) { j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ Type: database.ProvisionerJobTypeTemplateVersionImport, @@ -2216,6 +2216,38 @@ func (s *MethodTestSuite) TestSystemFunctions() { s.Run("GetUserLinksByUserID", s.Subtest(func(db database.Store, check *expects) { check.Args(uuid.New()).Asserts(rbac.ResourceSystem, rbac.ActionRead) })) + s.Run("GetJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) { + ws := dbgen.Workspace(s.T(), db, database.Workspace{}) + agent := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{}) + + err := db.UpsertJFrogXrayScanByWorkspaceAndAgentID(context.Background(), database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ + AgentID: agent.ID, + WorkspaceID: ws.ID, + Payload: []byte("{}"), + }) + require.NoError(s.T(), err) + + expect := database.JfrogXrayScan{ + WorkspaceID: ws.ID, + AgentID: agent.ID, + Payload: []byte("{}"), + } + + check.Args(database.GetJFrogXrayScanByWorkspaceAndAgentIDParams{ + WorkspaceID: ws.ID, + AgentID: agent.ID, + }).Asserts(ws, rbac.ActionRead).Returns(expect) + })) + s.Run("UpsertJFrogXrayScanByWorkspaceAndAgentID", s.Subtest(func(db database.Store, check *expects) { + tpl := dbgen.Template(s.T(), db, database.Template{}) + ws := dbgen.Workspace(s.T(), db, database.Workspace{ + TemplateID: tpl.ID, + }) + check.Args(database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ + WorkspaceID: ws.ID, + AgentID: uuid.New(), + }).Asserts(tpl, rbac.ActionCreate) + })) } func (s *MethodTestSuite) TestOAuth2ProviderApps() { From 91d076b5481bcc5f35b059845f1c24c32bf625db Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 29 Jan 2024 22:21:21 +0000 Subject: [PATCH 11/14] pr comments --- coderd/database/dbauthz/dbauthz_test.go | 10 ++- coderd/database/dbmem/dbmem.go | 10 ++- coderd/database/dump.sql | 5 +- .../migrations/000187_jfrog_xray.up.sql | 5 +- .../fixtures/000187_jfrog_xray.up.sql | 11 +++ coderd/database/models.go | 9 ++- coderd/database/queries.sql.go | 38 +++++++--- coderd/database/queries/jfrog.sql | 9 ++- codersdk/jfrog.go | 4 +- enterprise/coderd/coderd.go | 2 +- enterprise/coderd/jfrog.go | 69 +++++++++---------- enterprise/coderd/jfrog_test.go | 2 +- 12 files changed, 113 insertions(+), 61 deletions(-) create mode 100644 coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 8d114a4125cab..34b3c7ddc0334 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2223,14 +2223,20 @@ func (s *MethodTestSuite) TestSystemFunctions() { err := db.UpsertJFrogXrayScanByWorkspaceAndAgentID(context.Background(), database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ AgentID: agent.ID, WorkspaceID: ws.ID, - Payload: []byte("{}"), + Critical: 1, + High: 12, + Medium: 14, + ResultsUrl: "http://hello", }) require.NoError(s.T(), err) expect := database.JfrogXrayScan{ WorkspaceID: ws.ID, AgentID: agent.ID, - Payload: []byte("{}"), + Critical: 1, + High: 12, + Medium: 14, + ResultsUrl: "http://hello", } check.Args(database.GetJFrogXrayScanByWorkspaceAndAgentIDParams{ diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 5934b3fa65c44..80c80547f48e1 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -7322,7 +7322,10 @@ func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(_ context.Context for i, scan := range q.jfrogXRayScans { if scan.AgentID == arg.AgentID && scan.WorkspaceID == arg.WorkspaceID { - scan.Payload = arg.Payload + scan.Critical = arg.Critical + scan.High = arg.High + scan.Medium = arg.Medium + scan.ResultsUrl = arg.ResultsUrl q.jfrogXRayScans[i] = scan return nil } @@ -7332,7 +7335,10 @@ func (q *FakeQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(_ context.Context q.jfrogXRayScans = append(q.jfrogXRayScans, database.JfrogXrayScan{ WorkspaceID: arg.WorkspaceID, AgentID: arg.AgentID, - Payload: arg.Payload, + Critical: arg.Critical, + High: arg.High, + Medium: arg.Medium, + ResultsUrl: arg.ResultsUrl, }) return nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 34a994dea0d6f..b5de14360cadf 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -441,7 +441,10 @@ COMMENT ON COLUMN groups.source IS 'Source indicates how the group was created. CREATE TABLE jfrog_xray_scans ( agent_id uuid NOT NULL, workspace_id uuid NOT NULL, - payload jsonb DEFAULT '{}'::jsonb NOT NULL + critical integer DEFAULT 0 NOT NULL, + high integer DEFAULT 0 NOT NULL, + medium integer DEFAULT 0 NOT NULL, + results_url text DEFAULT ''::text NOT NULL ); CREATE TABLE licenses ( diff --git a/coderd/database/migrations/000187_jfrog_xray.up.sql b/coderd/database/migrations/000187_jfrog_xray.up.sql index 11e5db199a928..e1c3571638f42 100644 --- a/coderd/database/migrations/000187_jfrog_xray.up.sql +++ b/coderd/database/migrations/000187_jfrog_xray.up.sql @@ -1,7 +1,10 @@ CREATE TABLE jfrog_xray_scans ( agent_id uuid NOT NULL PRIMARY KEY REFERENCES workspace_agents(id) ON DELETE CASCADE, workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, - payload jsonb NOT NULL DEFAULT '{}' + critical integer NOT NULL DEFAULT 0, + high integer NOT NULL DEFAULT 0, + medium integer NOT NULL DEFAULT 0, + results_url text NOT NULL DEFAULT '' ); ALTER TABLE jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); diff --git a/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql b/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql new file mode 100644 index 0000000000000..ac2407b311182 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql @@ -0,0 +1,11 @@ +INSERT INTO jfrog_xray_scans + (workspace_id, agent_id, critical, high, medium, results_url) +VALUES ( + gen_random_uuid(), + gen_random_uuid(), + 10, + 5, + 2, + "https://hello-world" +); + diff --git a/coderd/database/models.go b/coderd/database/models.go index 7459fdcc4672d..dd12b0868249a 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1780,9 +1780,12 @@ type GroupMember struct { } type JfrogXrayScan struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` - Payload json.RawMessage `db:"payload" json:"payload"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + Critical int32 `db:"critical" json:"critical"` + High int32 `db:"high" json:"high"` + Medium int32 `db:"medium" json:"medium"` + ResultsUrl string `db:"results_url" json:"results_url"` } type License struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b55b28a4640a0..e3725ebdf5941 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2440,7 +2440,7 @@ func (q *sqlQuerier) GetUserLatencyInsights(ctx context.Context, arg GetUserLate const getJFrogXrayScanByWorkspaceAndAgentID = `-- name: GetJFrogXrayScanByWorkspaceAndAgentID :one SELECT - agent_id, workspace_id, payload + agent_id, workspace_id, critical, high, medium, results_url FROM jfrog_xray_scans WHERE @@ -2459,7 +2459,14 @@ type GetJFrogXrayScanByWorkspaceAndAgentIDParams struct { func (q *sqlQuerier) GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error) { row := q.db.QueryRowContext(ctx, getJFrogXrayScanByWorkspaceAndAgentID, arg.AgentID, arg.WorkspaceID) var i JfrogXrayScan - err := row.Scan(&i.AgentID, &i.WorkspaceID, &i.Payload) + err := row.Scan( + &i.AgentID, + &i.WorkspaceID, + &i.Critical, + &i.High, + &i.Medium, + &i.ResultsUrl, + ) return i, err } @@ -2468,22 +2475,35 @@ INSERT INTO jfrog_xray_scans ( agent_id, workspace_id, - payload + critical, + high, + medium, + results_url ) VALUES - ($1, $2, $3) + ($1, $2, $3, $4, $5, $6) ON CONFLICT (agent_id, workspace_id) -DO UPDATE SET payload = $3 +DO UPDATE SET critical = $3, high = $4, medium = $5, results_url = $6 ` type UpsertJFrogXrayScanByWorkspaceAndAgentIDParams struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` - Payload json.RawMessage `db:"payload" json:"payload"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + Critical int32 `db:"critical" json:"critical"` + High int32 `db:"high" json:"high"` + Medium int32 `db:"medium" json:"medium"` + ResultsUrl string `db:"results_url" json:"results_url"` } func (q *sqlQuerier) UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error { - _, err := q.db.ExecContext(ctx, upsertJFrogXrayScanByWorkspaceAndAgentID, arg.AgentID, arg.WorkspaceID, arg.Payload) + _, err := q.db.ExecContext(ctx, upsertJFrogXrayScanByWorkspaceAndAgentID, + arg.AgentID, + arg.WorkspaceID, + arg.Critical, + arg.High, + arg.Medium, + arg.ResultsUrl, + ) return err } diff --git a/coderd/database/queries/jfrog.sql b/coderd/database/queries/jfrog.sql index 6a7634c4f8ad2..de9467c5323dd 100644 --- a/coderd/database/queries/jfrog.sql +++ b/coderd/database/queries/jfrog.sql @@ -15,9 +15,12 @@ INSERT INTO jfrog_xray_scans ( agent_id, workspace_id, - payload + critical, + high, + medium, + results_url ) VALUES - ($1, $2, $3) + ($1, $2, $3, $4, $5, $6) ON CONFLICT (agent_id, workspace_id) -DO UPDATE SET payload = $3; +DO UPDATE SET critical = $3, high = $4, medium = $5, results_url = $6; diff --git a/codersdk/jfrog.go b/codersdk/jfrog.go index 8464a35918f0b..aa7fec25727cd 100644 --- a/codersdk/jfrog.go +++ b/codersdk/jfrog.go @@ -19,7 +19,7 @@ type JFrogXrayScan struct { } func (c *Client) PostJFrogXrayScan(ctx context.Context, req JFrogXrayScan) error { - res, err := c.Request(ctx, http.MethodPost, "/api/v2/exp/jfrog/xray-scan", req) + res, err := c.Request(ctx, http.MethodPost, "/api/v2/integrations/jfrog/xray-scan", req) if err != nil { return xerrors.Errorf("make request: %w", err) } @@ -32,7 +32,7 @@ func (c *Client) PostJFrogXrayScan(ctx context.Context, req JFrogXrayScan) error } func (c *Client) JFrogXRayScan(ctx context.Context, workspaceID, agentID uuid.UUID) (JFrogXrayScan, error) { - res, err := c.Request(ctx, http.MethodGet, "/api/v2/exp/jfrog/xray-scan", nil, + res, err := c.Request(ctx, http.MethodGet, "/api/v2/integrations/jfrog/xray-scan", nil, WithQueryParam("workspace_id", workspaceID.String()), WithQueryParam("agent_id", agentID.String()), ) diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 1424fb407098b..925b5b9229e46 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -348,7 +348,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { }) }) }) - r.Route("/exp", func(r chi.Router) { + r.Route("/integrations", func(r chi.Router) { r.Use( apiKeyMiddleware, api.jfrogEnabledMW, diff --git a/enterprise/coderd/jfrog.go b/enterprise/coderd/jfrog.go index c5423cd546e0a..7195aee908dc9 100644 --- a/enterprise/coderd/jfrog.go +++ b/enterprise/coderd/jfrog.go @@ -1,15 +1,11 @@ package coderd import ( - "database/sql" - "encoding/json" "net/http" "github.com/google/uuid" - "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" ) @@ -24,7 +20,7 @@ import ( // @Tags Enterprise // @Param request body codersdk.JFrogXrayScan true "Post JFrog XRay scan request" // @Success 200 {object} codersdk.Response -// @Router /exp/jfrog/xray-scan [post] +// @Router /integrations/jfrog/xray-scan [post] func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -33,22 +29,18 @@ func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { return } - payload, err := json.Marshal(req) - if err != nil { - httpapi.InternalServerError(rw, err) - return - } - - err = api.Database.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ + err := api.Database.UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx, database.UpsertJFrogXrayScanByWorkspaceAndAgentIDParams{ WorkspaceID: req.WorkspaceID, AgentID: req.AgentID, - Payload: payload, + Critical: int32(req.Critical), + High: int32(req.High), + Medium: int32(req.Medium), + ResultsUrl: req.ResultsURL, }) - if dbauthz.IsNotAuthorizedError(err) { - httpapi.Forbidden(rw) + if httpapi.Is404Error(err) { + httpapi.ResourceNotFound(rw) return } - if err != nil { httpapi.InternalServerError(rw, err) return @@ -69,26 +61,20 @@ func (api *API) postJFrogXrayScan(rw http.ResponseWriter, r *http.Request) { // @Param workspace_id query string true "Workspace ID" // @Param agent_id query string true "Agent ID" // @Success 200 {object} codersdk.JFrogXrayScan -// @Router /exp/jfrog/xray-scan [get] +// @Router /integrations/jfrog/xray-scan [get] func (api *API) jFrogXrayScan(rw http.ResponseWriter, r *http.Request) { var ( - ctx = r.Context() - wid = r.URL.Query().Get("workspace_id") - aid = r.URL.Query().Get("agent_id") + ctx = r.Context() + vals = r.URL.Query() + p = httpapi.NewQueryParamParser() + wsID = p.Required("workspace_id").UUID(vals, uuid.UUID{}, "workspace_id") + agentID = p.Required("agent_id").UUID(vals, uuid.UUID{}, "agent_id") ) - wsID, err := uuid.Parse(wid) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "'workspace_id' must be a valid UUID.", - }) - return - } - - agentID, err := uuid.Parse(aid) - if err != nil { + if len(p.Errors) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "'agent_id' must be a valid UUID.", + Message: "Invalid query params.", + Validations: p.Errors, }) return } @@ -97,20 +83,31 @@ func (api *API) jFrogXrayScan(rw http.ResponseWriter, r *http.Request) { WorkspaceID: wsID, AgentID: agentID, }) - if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - httpapi.InternalServerError(rw, err) + if httpapi.Is404Error(err) { + httpapi.ResourceNotFound(rw) return } - if scan.Payload == nil { - scan.Payload = []byte("{}") + if err != nil { + httpapi.InternalServerError(rw, err) + return } - httpapi.Write(ctx, rw, http.StatusOK, scan.Payload) + httpapi.Write(ctx, rw, http.StatusOK, codersdk.JFrogXrayScan{ + WorkspaceID: scan.WorkspaceID, + AgentID: scan.AgentID, + Critical: int(scan.Critical), + High: int(scan.High), + Medium: int(scan.Medium), + ResultsURL: scan.ResultsUrl, + }) } func (api *API) jfrogEnabledMW(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { api.entitlementsMu.RLock() + // This doesn't actually use the external auth feature but we want + // to lock this behind an enterprise license and it's somewhat + // related to external auth (in that it is JFrog integration). enabled := api.entitlements.Features[codersdk.FeatureMultipleExternalAuth].Enabled api.entitlementsMu.RUnlock() diff --git a/enterprise/coderd/jfrog_test.go b/enterprise/coderd/jfrog_test.go index 5dd2e3b963c79..fd47f80b3ee92 100644 --- a/enterprise/coderd/jfrog_test.go +++ b/enterprise/coderd/jfrog_test.go @@ -109,7 +109,7 @@ func TestJFrogXrayScan(t *testing.T) { require.Error(t, err) cerr, ok := codersdk.AsError(err) require.True(t, ok) - require.Equal(t, http.StatusForbidden, cerr.StatusCode()) + require.Equal(t, http.StatusNotFound, cerr.StatusCode()) err = ownerClient.PostJFrogXrayScan(ctx, expectedPayload) require.NoError(t, err) From c90f0382b4ffe2728ddd068d41f6aab5af038628 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 29 Jan 2024 22:22:15 +0000 Subject: [PATCH 12/14] make gen --- coderd/apidoc/docs.go | 156 ++++++++++++++-------------- coderd/apidoc/swagger.json | 136 ++++++++++++------------- docs/api/enterprise.md | 202 ++++++++++++++++++------------------- 3 files changed, 247 insertions(+), 247 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 5cf71bc155e21..15b52df7bf1d8 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -692,84 +692,6 @@ const docTemplate = `{ } } }, - "/exp/jfrog/xray-scan": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Enterprise" - ], - "summary": "Get JFrog XRay scan by workspace agent ID.", - "operationId": "get-jfrog-xray-scan-by-workspace-agent-id", - "parameters": [ - { - "type": "string", - "description": "Workspace ID", - "name": "workspace_id", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Agent ID", - "name": "agent_id", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.JFrogXrayScan" - } - } - } - }, - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Enterprise" - ], - "summary": "Post JFrog XRay scan by workspace agent ID.", - "operationId": "post-jfrog-xray-scan-by-workspace-agent-id", - "parameters": [ - { - "description": "Post JFrog XRay scan request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/codersdk.JFrogXrayScan" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.Response" - } - } - } - } - }, "/experiments": { "get": { "security": [ @@ -1260,6 +1182,84 @@ const docTemplate = `{ } } }, + "/integrations/jfrog/xray-scan": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Get JFrog XRay scan by workspace agent ID.", + "operationId": "get-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "type": "string", + "description": "Workspace ID", + "name": "workspace_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Agent ID", + "name": "agent_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + } + }, + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Post JFrog XRay scan by workspace agent ID.", + "operationId": "post-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "description": "Post JFrog XRay scan request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + } + } + }, "/licenses": { "get": { "security": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index f1bd1f335f7d3..e8872f103d360 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -590,74 +590,6 @@ } } }, - "/exp/jfrog/xray-scan": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Enterprise"], - "summary": "Get JFrog XRay scan by workspace agent ID.", - "operationId": "get-jfrog-xray-scan-by-workspace-agent-id", - "parameters": [ - { - "type": "string", - "description": "Workspace ID", - "name": "workspace_id", - "in": "query", - "required": true - }, - { - "type": "string", - "description": "Agent ID", - "name": "agent_id", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.JFrogXrayScan" - } - } - } - }, - "post": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Enterprise"], - "summary": "Post JFrog XRay scan by workspace agent ID.", - "operationId": "post-jfrog-xray-scan-by-workspace-agent-id", - "parameters": [ - { - "description": "Post JFrog XRay scan request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/codersdk.JFrogXrayScan" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.Response" - } - } - } - } - }, "/experiments": { "get": { "security": [ @@ -1086,6 +1018,74 @@ } } }, + "/integrations/jfrog/xray-scan": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Get JFrog XRay scan by workspace agent ID.", + "operationId": "get-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "type": "string", + "description": "Workspace ID", + "name": "workspace_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Agent ID", + "name": "agent_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + } + }, + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Post JFrog XRay scan by workspace agent ID.", + "operationId": "post-jfrog-xray-scan-by-workspace-agent-id", + "parameters": [ + { + "description": "Post JFrog XRay scan request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.JFrogXrayScan" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + } + } + }, "/licenses": { "get": { "security": [ diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index ec470ca1aabb3..cb100f346f17b 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -152,107 +152,6 @@ curl -X GET http://coder-server:8080/api/v2/entitlements \ To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get JFrog XRay scan by workspace agent ID. - -### Code samples - -```shell -# Example request using curl -curl -X GET http://coder-server:8080/api/v2/exp/jfrog/xray-scan?workspace_id=string&agent_id=string \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`GET /exp/jfrog/xray-scan` - -### Parameters - -| Name | In | Type | Required | Description | -| -------------- | ----- | ------ | -------- | ------------ | -| `workspace_id` | query | string | true | Workspace ID | -| `agent_id` | query | string | true | Agent ID | - -### Example responses - -> 200 Response - -```json -{ - "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", - "critical": 0, - "high": 0, - "medium": 0, - "results_url": "string", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" -} -``` - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - -## Post JFrog XRay scan by workspace agent ID. - -### Code samples - -```shell -# Example request using curl -curl -X POST http://coder-server:8080/api/v2/exp/jfrog/xray-scan \ - -H 'Content-Type: application/json' \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`POST /exp/jfrog/xray-scan` - -> Body parameter - -```json -{ - "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", - "critical": 0, - "high": 0, - "medium": 0, - "results_url": "string", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" -} -``` - -### Parameters - -| Name | In | Type | Required | Description | -| ------ | ---- | ---------------------------------------------------------- | -------- | ---------------------------- | -| `body` | body | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | true | Post JFrog XRay scan request | - -### Example responses - -> 200 Response - -```json -{ - "detail": "string", - "message": "string", - "validations": [ - { - "detail": "string", - "field": "string" - } - ] -} -``` - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - ## Get group by ID ### Code samples @@ -460,6 +359,107 @@ curl -X PATCH http://coder-server:8080/api/v2/groups/{group} \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get JFrog XRay scan by workspace agent ID. + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/integrations/jfrog/xray-scan?workspace_id=string&agent_id=string \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /integrations/jfrog/xray-scan` + +### Parameters + +| Name | In | Type | Required | Description | +| -------------- | ----- | ------ | -------- | ------------ | +| `workspace_id` | query | string | true | Workspace ID | +| `agent_id` | query | string | true | Agent ID | + +### Example responses + +> 200 Response + +```json +{ + "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", + "critical": 0, + "high": 0, + "medium": 0, + "results_url": "string", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Post JFrog XRay scan by workspace agent ID. + +### Code samples + +```shell +# Example request using curl +curl -X POST http://coder-server:8080/api/v2/integrations/jfrog/xray-scan \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`POST /integrations/jfrog/xray-scan` + +> Body parameter + +```json +{ + "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", + "critical": 0, + "high": 0, + "medium": 0, + "results_url": "string", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" +} +``` + +### Parameters + +| Name | In | Type | Required | Description | +| ------ | ---- | ---------------------------------------------------------- | -------- | ---------------------------- | +| `body` | body | [codersdk.JFrogXrayScan](schemas.md#codersdkjfrogxrayscan) | true | Post JFrog XRay scan request | + +### Example responses + +> 200 Response + +```json +{ + "detail": "string", + "message": "string", + "validations": [ + { + "detail": "string", + "field": "string" + } + ] +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get licenses ### Code samples From cdfeed7d83b7bd58cd766d73ea98bd4bd70bd74e Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Mon, 29 Jan 2024 22:39:07 +0000 Subject: [PATCH 13/14] single quotes --- .../migrations/testdata/fixtures/000187_jfrog_xray.up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql b/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql index ac2407b311182..051fb7f87b465 100644 --- a/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql @@ -6,6 +6,6 @@ VALUES ( 10, 5, 2, - "https://hello-world" + 'https://hello-world' ); From 55402b243ff1bd8383355bf223ff4ed03c496740 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 30 Jan 2024 01:07:59 +0000 Subject: [PATCH 14/14] add migration fixture --- coderd/database/dump.sql | 5 +---- coderd/database/migrations/000187_jfrog_xray.down.sql | 1 - coderd/database/migrations/000187_jfrog_xray.up.sql | 7 +++---- .../migrations/testdata/fixtures/000187_jfrog_xray.up.sql | 4 ++-- coderd/database/unique_constraint.go | 3 +-- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index b5de14360cadf..cc6f3c59b3d43 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1302,10 +1302,7 @@ ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); ALTER TABLE ONLY jfrog_xray_scans - ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id); - -ALTER TABLE ONLY jfrog_xray_scans - ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); + ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id); ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); diff --git a/coderd/database/migrations/000187_jfrog_xray.down.sql b/coderd/database/migrations/000187_jfrog_xray.down.sql index bb3a62da7c3f6..8fa8f99f47bb0 100644 --- a/coderd/database/migrations/000187_jfrog_xray.down.sql +++ b/coderd/database/migrations/000187_jfrog_xray.down.sql @@ -1,2 +1 @@ -ALTER TABLE jfrog_xray_scans DROP CONSTRAINT jfrog_xray_scans_workspace_id_agent_id; DROP TABLE jfrog_xray_scans; diff --git a/coderd/database/migrations/000187_jfrog_xray.up.sql b/coderd/database/migrations/000187_jfrog_xray.up.sql index e1c3571638f42..8143dac49d52f 100644 --- a/coderd/database/migrations/000187_jfrog_xray.up.sql +++ b/coderd/database/migrations/000187_jfrog_xray.up.sql @@ -1,10 +1,9 @@ CREATE TABLE jfrog_xray_scans ( - agent_id uuid NOT NULL PRIMARY KEY REFERENCES workspace_agents(id) ON DELETE CASCADE, + agent_id uuid NOT NULL REFERENCES workspace_agents(id) ON DELETE CASCADE, workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, critical integer NOT NULL DEFAULT 0, high integer NOT NULL DEFAULT 0, medium integer NOT NULL DEFAULT 0, - results_url text NOT NULL DEFAULT '' + results_url text NOT NULL DEFAULT '', + PRIMARY KEY (agent_id, workspace_id) ); - -ALTER TABLE jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); diff --git a/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql b/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql index 051fb7f87b465..3dc664242c46a 100644 --- a/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000187_jfrog_xray.up.sql @@ -1,8 +1,8 @@ INSERT INTO jfrog_xray_scans (workspace_id, agent_id, critical, high, medium, results_url) VALUES ( - gen_random_uuid(), - gen_random_uuid(), + 'b90547be-8870-4d68-8184-e8b2242b7c01', + '8fa17bbd-c48c-44c7-91ae-d4acbc755fad', 10, 5, 2, diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index a4a5bcdae278b..af9e7b3cbf5e8 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -19,8 +19,7 @@ const ( UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); - UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id); - UniqueJfrogXrayScansWorkspaceIDAgentID UniqueConstraint = "jfrog_xray_scans_workspace_id_agent_id" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_workspace_id_agent_id UNIQUE (agent_id, workspace_id); + UniqueJfrogXrayScansPkey UniqueConstraint = "jfrog_xray_scans_pkey" // ALTER TABLE ONLY jfrog_xray_scans ADD CONSTRAINT jfrog_xray_scans_pkey PRIMARY KEY (agent_id, workspace_id); UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); UniqueOauth2ProviderAppSecretsAppIDHashedSecretKey UniqueConstraint = "oauth2_provider_app_secrets_app_id_hashed_secret_key" // ALTER TABLE ONLY oauth2_provider_app_secrets ADD CONSTRAINT oauth2_provider_app_secrets_app_id_hashed_secret_key UNIQUE (app_id, hashed_secret); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy