Skip to content

Commit c37d94e

Browse files
tommytroenybelMekk
andcommitted
refactor: update api with new rpc
* add package to grpc api * create testdata for nais-api * change docker compose ports to not interfere with nais api * change numbers for severity to better fit with sorting Co-authored-by: ybelmekk <[email protected]>
1 parent fe372bc commit c37d94e

24 files changed

+1068
-480
lines changed

.env.sample

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
V13S_LISTEN_ADDR=localhost:50051
2-
V13S_DATABASE_URL=postgres://v13s:[email protected]:3002/v13s?sslmode=disable
2+
V13S_DATABASE_URL=postgres://v13s:[email protected]:4002/v13s?sslmode=disable
33
V13S_DEPENDENCYTRACK_URL=todo
44
V13S_DEPENDENCYTRACK_TEAM=todo
55
V13S_DEPENDENCYTRACK_USERNAME=todo

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ generate: generate-proto generate_dp_track generate-sql generate-mocks
3333

3434
generate-proto:
3535
protoc \
36-
-I pkg/api/vulnerabilities/schema/ \
37-
./pkg/api/vulnerabilities/schema/*.proto \
36+
-I pkg/api/vulnerabilities/schemas/ \
37+
./pkg/api/vulnerabilities/schemas/*.proto \
3838
--go_out=. \
3939
--go-grpc_out=.
4040

cmd/seed/main.go

+121-14
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,37 @@ func main() {
2323
fmt.Println("No .env file found")
2424
}
2525

26-
dpClient, err := dependencytrack.NewClient(
27-
os.Getenv("V13S_DEPENDENCYTRACK_URL"),
28-
os.Getenv("V13S_DEPENDENCYTRACK_TEAM"),
29-
os.Getenv("V13S_DEPENDENCYTRACK_USERNAME"),
30-
os.Getenv("V13S_DEPENDENCYTRACK_PASSWORD"),
31-
)
26+
log.Infof("initializing database")
27+
28+
pool, err := database.New(ctx, "postgres://v13s:[email protected]:4002/v13s?sslmode=disable", log.WithField("component", "database"))
3229
if err != nil {
3330
panic(err)
3431
}
32+
defer pool.Close()
3533

36-
projects, err := dpClient.GetProjectsByTag(ctx, "team:nais-system", 10, 0)
34+
db := sql.New(pool)
35+
36+
log.Infof("reseting database")
37+
err = db.ResetDatabase(ctx)
3738
if err != nil {
3839
panic(err)
3940
}
4041

41-
log.Infof("initializing database")
42+
createNaisApiTestdata(ctx, db)
43+
}
4244

43-
pool, err := database.New(ctx, "postgres://v13s:[email protected]:3002/v13s?sslmode=disable", log.WithField("component", "database"))
45+
func seedFromDependencyTrack(ctx context.Context, db sql.Querier) {
46+
dpClient, err := dependencytrack.NewClient(
47+
os.Getenv("V13S_DEPENDENCYTRACK_URL"),
48+
os.Getenv("V13S_DEPENDENCYTRACK_TEAM"),
49+
os.Getenv("V13S_DEPENDENCYTRACK_USERNAME"),
50+
os.Getenv("V13S_DEPENDENCYTRACK_PASSWORD"),
51+
)
4452
if err != nil {
4553
panic(err)
4654
}
47-
defer pool.Close()
48-
49-
db := sql.New(pool)
5055

51-
log.Infof("reseting database")
52-
err = db.ResetDatabase(ctx)
56+
projects, err := dpClient.GetProjectsByTag(ctx, "team:nais-system", 10, 0)
5357
if err != nil {
5458
panic(err)
5559
}
@@ -96,6 +100,109 @@ func main() {
96100
}
97101
}
98102

103+
func createNaisApiTestdata(ctx context.Context, db sql.Querier) {
104+
for chicken := 1; chicken < 9; chicken++ {
105+
if err := db.CreateImage(ctx, sql.CreateImageParams{
106+
Name: "ghcr.io/nais/nais-deploy-chicken-" + fmt.Sprintf("%d", chicken),
107+
Tag: "1",
108+
Metadata: map[string]string{},
109+
}); err != nil {
110+
panic(err)
111+
}
112+
113+
_, err := db.CreateWorkload(ctx, sql.CreateWorkloadParams{
114+
Cluster: "dev",
115+
Namespace: "devteam",
116+
WorkloadType: "app",
117+
Name: "nais-deploy-chicken-" + fmt.Sprintf("%d", chicken),
118+
ImageName: "ghcr.io/nais/nais-deploy-chicken-" + fmt.Sprintf("%d", chicken),
119+
ImageTag: "1",
120+
})
121+
if err != nil {
122+
panic(err)
123+
}
124+
125+
batch := generateVulnerabilities(chicken, "ghcr.io/nais/nais-deploy-chicken-"+fmt.Sprintf("%d", chicken), "1")
126+
127+
db.BatchUpsertCve(ctx, batch.cve).Exec(func(i int, err error) {
128+
if err != nil {
129+
panic(err)
130+
}
131+
})
132+
133+
db.BatchUpsertVulnerabilities(ctx, batch.vuln).Exec(func(i int, err error) {
134+
if err != nil {
135+
panic(err)
136+
}
137+
})
138+
139+
sumRow, err := db.GenerateVulnerabilitySummaryForImage(ctx, sql.GenerateVulnerabilitySummaryForImageParams{
140+
ImageName: "ghcr.io/nais/nais-deploy-chicken-" + fmt.Sprintf("%d", chicken),
141+
ImageTag: "1",
142+
})
143+
if err != nil {
144+
panic(err)
145+
}
146+
147+
summary := sql.CreateVulnerabilitySummaryParams{
148+
ImageName: "ghcr.io/nais/nais-deploy-chicken-" + fmt.Sprintf("%d", chicken),
149+
ImageTag: "1",
150+
Critical: int32(sumRow.Critical),
151+
High: int32(sumRow.High),
152+
Medium: int32(sumRow.Medium),
153+
Low: int32(sumRow.Low),
154+
Unassigned: int32(sumRow.Unassigned),
155+
RiskScore: sumRow.RiskScore,
156+
}
157+
158+
_, err = db.CreateVulnerabilitySummary(ctx, summary)
159+
if err != nil {
160+
panic(fmt.Errorf("summary error: %v", err))
161+
}
162+
}
163+
}
164+
165+
type BatchVulnerabilities struct {
166+
vuln []sql.BatchUpsertVulnerabilitiesParams
167+
cve []sql.BatchUpsertCveParams
168+
}
169+
170+
// generateVulnerabilities creates a different number of vulnerabilities per workload
171+
func generateVulnerabilities(chicken int, imageName string, imageTag string) BatchVulnerabilities {
172+
vulns := make([]sql.BatchUpsertVulnerabilitiesParams, 0)
173+
cves := make([]sql.BatchUpsertCveParams, 0)
174+
175+
for j := 1; j <= chicken; j++ {
176+
for s := 1; s <= 5; s++ {
177+
v, c := createVulnerability(s, fmt.Sprintf("CWE-%d-%d-%d", chicken, j, s), imageName, imageTag)
178+
vulns = append(vulns, v)
179+
cves = append(cves, c)
180+
}
181+
}
182+
183+
return BatchVulnerabilities{
184+
vuln: vulns,
185+
cve: cves,
186+
}
187+
}
188+
189+
// createVulnerability generates a predictable vulnerability instance
190+
func createVulnerability(severity int, cveID string, imageName string, imageTag string) (sql.BatchUpsertVulnerabilitiesParams, sql.BatchUpsertCveParams) {
191+
return sql.BatchUpsertVulnerabilitiesParams{
192+
ImageName: imageName,
193+
ImageTag: imageTag,
194+
Package: fmt.Sprintf("package-%s", cveID),
195+
Source: "seed",
196+
CveID: cveID,
197+
}, sql.BatchUpsertCveParams{
198+
CveID: cveID,
199+
CveTitle: "Title for " + cveID,
200+
CveDesc: "description for " + cveID,
201+
CveLink: "https://example.com/" + cveID,
202+
Severity: int32(severity),
203+
}
204+
}
205+
99206
func toCreateWorkloadParams(p client.Project, image sql.CreateImageParams) []*sql.CreateWorkloadParams {
100207
workloads := make([]*sql.CreateWorkloadParams, 0)
101208
for _, t := range p.Tags {

docker-compose.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33
image: postgres:15-alpine
44
command: ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"]
55
ports:
6-
- "3002:5432"
6+
- "4002:5432"
77
environment:
88
POSTGRES_USER: v13s
99
POSTGRES_PASSWORD: v13s
@@ -16,7 +16,7 @@ services:
1616
- postgres
1717
image: adminer:latest
1818
ports:
19-
- "3003:8080"
19+
- "4003:8080"
2020
environment:
2121
ADMINER_DEFAULT_SERVER: postgres
2222

internal/api/grpcvulnerabilities/server.go

+69-13
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package grpcvulnerabilities
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"github.com/jackc/pgx/v5"
68
"github.com/nais/v13s/internal/api/grpcpagination"
7-
"github.com/nais/v13s/internal/database/sql"
8-
"google.golang.org/protobuf/types/known/timestamppb"
9-
"time"
10-
119
"github.com/nais/v13s/internal/collections"
10+
"github.com/nais/v13s/internal/database/sql"
1211
"github.com/nais/v13s/pkg/api/vulnerabilities"
12+
"google.golang.org/protobuf/types/known/timestamppb"
1313
)
1414

1515
var _ vulnerabilities.VulnerabilitiesServer = (*Server)(nil)
@@ -172,20 +172,76 @@ func (s *Server) GetVulnerabilitySummary(ctx context.Context, request *vulnerabi
172172
return nil, err
173173
}
174174

175-
var summary = vulnerabilities.Summary{
176-
Critical: sum.CriticalVulnerabilities,
177-
High: sum.HighVulnerabilities,
178-
Medium: sum.MediumVulnerabilities,
179-
Low: sum.LowVulnerabilities,
180-
Unassigned: sum.UnassignedVulnerabilities,
181-
RiskScore: sum.TotalRiskScore,
182-
LastUpdated: timestamppb.New(time.Now()),
175+
summary := &vulnerabilities.Summary{}
176+
if sum != nil {
177+
summary.Critical = sum.CriticalVulnerabilities
178+
summary.High = sum.HighVulnerabilities
179+
summary.Medium = sum.MediumVulnerabilities
180+
summary.Low = sum.LowVulnerabilities
181+
summary.Unassigned = sum.UnassignedVulnerabilities
182+
summary.RiskScore = sum.TotalRiskScore
183+
summary.HasSbom = true
183184
}
184185

185186
response := &vulnerabilities.GetVulnerabilitySummaryResponse{
186187
Filter: request.Filter,
187-
VulnerabilitySummary: &summary,
188+
VulnerabilitySummary: summary,
188189
WorkloadCount: sum.WorkloadCount,
189190
}
190191
return response, nil
191192
}
193+
194+
func (s *Server) GetVulnerabilitySummaryForImage(ctx context.Context, request *vulnerabilities.GetVulnerabilitySummaryForImageRequest) (*vulnerabilities.GetVulnerabilitySummaryForImageResponse, error) {
195+
summary, err := s.db.GetVulnerabilitySummaryForImage(ctx, sql.GetVulnerabilitySummaryForImageParams{
196+
ImageName: request.ImageName,
197+
ImageTag: request.ImageTag,
198+
})
199+
if err != nil {
200+
if errors.Is(err, pgx.ErrNoRows) {
201+
return &vulnerabilities.GetVulnerabilitySummaryForImageResponse{
202+
VulnerabilitySummary: &vulnerabilities.Summary{},
203+
WorkloadRef: make([]*vulnerabilities.Workload, 0),
204+
}, nil
205+
}
206+
207+
return nil, fmt.Errorf("failed to get vulnerability summary for image: %w", err)
208+
}
209+
workloads, err := s.db.ListWorkloadsByImage(ctx, sql.ListWorkloadsByImageParams{
210+
ImageName: request.ImageName,
211+
ImageTag: request.ImageTag,
212+
})
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to list workloads by image: %w", err)
215+
}
216+
217+
refs := make([]*vulnerabilities.Workload, 0)
218+
for _, w := range workloads {
219+
refs = append(refs, &vulnerabilities.Workload{
220+
Cluster: w.Cluster,
221+
Namespace: w.Namespace,
222+
Name: w.Name,
223+
Type: w.WorkloadType,
224+
ImageName: w.ImageName,
225+
ImageTag: w.ImageTag,
226+
})
227+
}
228+
229+
vulnSummary := &vulnerabilities.Summary{}
230+
if summary != nil {
231+
vulnSummary = &vulnerabilities.Summary{
232+
Critical: summary.Critical,
233+
High: summary.High,
234+
Medium: summary.Medium,
235+
Low: summary.Low,
236+
Unassigned: summary.Unassigned,
237+
RiskScore: summary.RiskScore,
238+
LastUpdated: timestamppb.New(summary.UpdatedAt.Time),
239+
HasSbom: true,
240+
}
241+
}
242+
243+
return &vulnerabilities.GetVulnerabilitySummaryForImageResponse{
244+
VulnerabilitySummary: vulnSummary,
245+
WorkloadRef: refs,
246+
}, nil
247+
}

internal/database/queries/vulnerabilities.sql

+19
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,22 @@ WHERE image_name = @image_name
143143
ORDER BY updated_at DESC
144144
LIMIT sqlc.arg('limit') OFFSET sqlc.arg('offset')
145145
;
146+
147+
-- name: GenerateVulnerabilitySummaryForImage :one
148+
SELECT COUNT(*) AS total,
149+
SUM(CASE WHEN c.severity = 5 THEN 1 ELSE 0 END) AS critical,
150+
SUM(CASE WHEN c.severity = 4 THEN 1 ELSE 0 END) AS high,
151+
SUM(CASE WHEN c.severity = 3 THEN 1 ELSE 0 END) AS medium,
152+
SUM(CASE WHEN c.severity = 2 THEN 1 ELSE 0 END) AS low,
153+
SUM(CASE WHEN c.severity = 1 THEN 1 ELSE 0 END) AS unassigned,
154+
-- 10*critical + 5*high + 3*medium + 1*low + 5*unassigned
155+
10 * SUM(CASE WHEN c.severity = 5 THEN 1 ELSE 0 END) +
156+
5 * SUM(CASE WHEN c.severity = 4 THEN 1 ELSE 0 END) +
157+
3 * SUM(CASE WHEN c.severity = 3 THEN 1 ELSE 0 END) +
158+
1 * SUM(CASE WHEN c.severity = 2 THEN 1 ELSE 0 END) +
159+
5 * SUM(CASE WHEN c.severity = 1 THEN 1 ELSE 0 END) AS risk_score
160+
161+
FROM vulnerabilities v
162+
JOIN cve c ON v.cve_id = c.cve_id
163+
WHERE v.image_name = @image_name
164+
AND v.image_tag = @image_tag;

internal/database/queries/vulnerbility_summary.sql

+7
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ WHERE
9393
AND (CASE WHEN sqlc.narg('namespace')::TEXT is not null THEN w.namespace = sqlc.narg('namespace')::TEXT ELSE TRUE END)
9494
AND (CASE WHEN sqlc.narg('workload_type')::TEXT is not null THEN w.workload_type = sqlc.narg('workload_type')::TEXT ELSE TRUE END)
9595
AND (CASE WHEN sqlc.narg('workload_name')::TEXT is not null THEN w.name = sqlc.narg('workload_name')::TEXT ELSE TRUE END)
96+
AND (CASE WHEN sqlc.narg('image_name')::TEXT is not null THEN v.image_name = sqlc.narg('image_name')::TEXT ELSE TRUE END)
97+
AND (CASE WHEN sqlc.narg('image_tag')::TEXT is not null THEN v.image_tag = sqlc.narg('image_tag')::TEXT ELSE TRUE END)
9698
ORDER BY w.updated_at DESC
9799
LIMIT
98100
sqlc.arg('limit')
@@ -117,3 +119,8 @@ WHERE
117119
AND (CASE WHEN sqlc.narg('namespace')::TEXT IS NOT NULL THEN w.namespace = sqlc.narg('namespace')::TEXT ELSE TRUE END)
118120
AND (CASE WHEN sqlc.narg('workload_type')::TEXT IS NOT NULL THEN w.workload_type = sqlc.narg('workload_type')::TEXT ELSE TRUE END)
119121
AND (CASE WHEN sqlc.narg('workload_name')::TEXT IS NOT NULL THEN w.name = sqlc.narg('workload_name')::TEXT ELSE TRUE END);
122+
123+
-- name: GetVulnerabilitySummaryForImage :one
124+
SELECT * FROM vulnerability_summary
125+
WHERE image_name = @image_name
126+
AND image_tag = @image_tag;

internal/database/queries/workloads.sql

+9-1
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ WHERE
4343
name = @name
4444
RETURNING
4545
*
46-
;
46+
;
47+
48+
-- name: ListWorkloadsByImage :many
49+
SELECT *
50+
FROM workloads
51+
WHERE image_name = @image_name
52+
AND image_tag = @image_tag
53+
ORDER BY
54+
(name, cluster, updated_at) DESC;

internal/database/sql/querier.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)