Skip to content

Commit

Permalink
Add Neo4j Backend queries (guacsec#417)
Browse files Browse the repository at this point in the history
* inital commit for ingesting into neo4j

Signed-off-by: pxp928 <[email protected]>

* added test data ingestion for neo4j

Signed-off-by: pxp928 <[email protected]>

* added missing error check

Signed-off-by: pxp928 <[email protected]>

* moved testdata into one file and created pointer for nodes

Signed-off-by: pxp928 <[email protected]>

* inital commit for package query

Signed-off-by: pxp928 <[email protected]>

* added neo4j resolvers

Signed-off-by: pxp928 <[email protected]>

* remove unused firstmatch

Signed-off-by: pxp928 <[email protected]>

* consolidated WHERE and AND

Signed-off-by: pxp928 <[email protected]>

* added pkgQualifier node and edge

Signed-off-by: pxp928 <[email protected]>

* updated query result logic

Signed-off-by: pxp928 <[email protected]>

* use map value from if

Signed-off-by: pxp928 <[email protected]>

---------

Signed-off-by: pxp928 <[email protected]>
  • Loading branch information
pxp928 authored Feb 9, 2023
1 parent 5a0be16 commit 43f3e34
Show file tree
Hide file tree
Showing 12 changed files with 724 additions and 62 deletions.
59 changes: 59 additions & 0 deletions pkg/assembler/backends/neo4j/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
package neo4jBackend

import (
"context"
"strings"

"github.com/guacsec/guac/pkg/assembler/graphql/model"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/vektah/gqlparser/v2/gqlerror"
)

// ArtifactNode is a node that represents an artifact
Expand Down Expand Up @@ -45,3 +50,57 @@ func (an *artifactNode) IdentifiablePropertyNames() []string {
// An artifact can be uniquely identified by algorithm and digest
return []string{"algorithm", "digest"}
}

func (c *neo4jClient) Artifacts(ctx context.Context, artifactSpec *model.ArtifactSpec) ([]*model.Artifact, error) {
session := c.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()

if artifactSpec.Algorithm == nil && artifactSpec.Digest == nil {
return nil, gqlerror.Errorf("must specify both algorithm and digest for artifact")
}

result, err := session.ReadTransaction(
func(tx neo4j.Transaction) (interface{}, error) {

var sb strings.Builder
var firstMatch bool = true
queryValues := map[string]any{}

sb.WriteString("MATCH (n:Artifact)")

if artifactSpec.Algorithm != nil {
matchProperties(&sb, firstMatch, "n", "algorithm", "$artifactAlgo")
firstMatch = false
queryValues["artifactAlgo"] = artifactSpec.Algorithm
}
if artifactSpec.Digest != nil {
matchProperties(&sb, firstMatch, "n", "digest", "$artifactDigest")
queryValues["artifactDigest"] = artifactSpec.Digest
}

sb.WriteString(" RETURN n.algorithm, n.digest")
result, err := tx.Run(sb.String(), queryValues)
if err != nil {
return nil, err
}

artifacts := []*model.Artifact{}
for result.Next() {
artifact := &model.Artifact{
Algorithm: result.Record().Values[0].(string),
Digest: result.Record().Values[1].(string),
}
artifacts = append(artifacts, artifact)
}
if err = result.Err(); err != nil {
return nil, err
}

return artifacts, nil
})
if err != nil {
return nil, err
}

return result.([]*model.Artifact), nil
}
53 changes: 26 additions & 27 deletions pkg/assembler/backends/neo4j/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
package neo4jBackend

import (
"context"
"fmt"
"strings"

"github.com/guacsec/guac/pkg/assembler/backends"
"github.com/guacsec/guac/pkg/assembler/graphql/model"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

Expand Down Expand Up @@ -82,30 +80,31 @@ func GetBackend(args backends.BackendArgs) (backends.Backend, error) {
return client, nil
}

func (c *neo4jClient) Packages(ctx context.Context, pkgSpec *model.PkgSpec) ([]*model.Package, error) {
panic(fmt.Errorf("not implemented: Packages - packages in Neo4j backend"))
}

func (c *neo4jClient) Sources(ctx context.Context, sourceSpec *model.SourceSpec) ([]*model.Source, error) {
panic(fmt.Errorf("not implemented: Sources - sources in Neo4j backend"))
}

func (c *neo4jClient) Cve(ctx context.Context, cveSpec *model.CVESpec) ([]*model.Cve, error) {
panic(fmt.Errorf("not implemented: Cve - cve in Neo4j backend"))
}

func (c *neo4jClient) Ghsa(ctx context.Context, ghsaSpec *model.GHSASpec) ([]*model.Ghsa, error) {
panic(fmt.Errorf("not implemented: Ghsa - ghsa in Neo4j backend"))
}

func (c *neo4jClient) Osv(ctx context.Context, osvSpec *model.OSVSpec) ([]*model.Osv, error) {
panic(fmt.Errorf("not implemented: Osv - osv in Neo4j backend"))
}

func (c *neo4jClient) Artifacts(ctx context.Context, artifactSpec *model.ArtifactSpec) ([]*model.Artifact, error) {
panic(fmt.Errorf("not implemented: Artifacts - artifacts in Neo4j backend"))
func matchProperties(sb *strings.Builder, firstMatch bool, label, property string, resolver string) {
if firstMatch {
sb.WriteString(" WHERE ")
} else {
sb.WriteString(" AND ")
}
sb.WriteString(label)
sb.WriteString(".")
sb.WriteString(property)
sb.WriteString(" = ")
sb.WriteString(resolver)
}

func (c *neo4jClient) Builders(ctx context.Context, builderSpec *model.BuilderSpec) ([]*model.Builder, error) {
panic(fmt.Errorf("not implemented: Builders - builders in Neo4j backend"))
func matchNotEdge(sb *strings.Builder, firstMatch bool, firstNodeLabel string, edgeLabel string, secondNodeLabel string) {
// -[:PkgHasQualifier]->(qualifier:PkgQualifier)
if firstMatch {
sb.WriteString(" WHERE ")
} else {
sb.WriteString(" AND ")
}
sb.WriteString("NOT (")
sb.WriteString(firstNodeLabel)
sb.WriteString(")-[:")
sb.WriteString(edgeLabel)
sb.WriteString("]->(:")
sb.WriteString(secondNodeLabel)
sb.WriteString(")")
}
51 changes: 51 additions & 0 deletions pkg/assembler/backends/neo4j/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@

package neo4jBackend

import (
"context"
"strings"

"github.com/guacsec/guac/pkg/assembler/graphql/model"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

// builderNode represents the builder
type builderNode struct {
uri string
Expand All @@ -38,3 +46,46 @@ func (bn builderNode) PropertyNames() []string {
func (bn builderNode) IdentifiablePropertyNames() []string {
return []string{"uri"}
}

func (c *neo4jClient) Builders(ctx context.Context, builderSpec *model.BuilderSpec) ([]*model.Builder, error) {
session := c.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()

result, err := session.ReadTransaction(
func(tx neo4j.Transaction) (interface{}, error) {

var sb strings.Builder
queryValues := map[string]any{}

sb.WriteString("MATCH (n:Builder)")

if builderSpec.URI != nil {
matchProperties(&sb, true, "n", "uri", "$builderUri")
queryValues["builderUri"] = builderSpec.URI
}

sb.WriteString(" RETURN n.uri")
result, err := tx.Run(sb.String(), queryValues)
if err != nil {
return nil, err
}

builders := []*model.Builder{}
for result.Next() {
builder := &model.Builder{
URI: result.Record().Values[0].(string),
}
builders = append(builders, builder)
}
if err = result.Err(); err != nil {
return nil, err
}

return builders, nil
})
if err != nil {
return nil, err
}

return result.([]*model.Builder), nil
}
112 changes: 112 additions & 0 deletions pkg/assembler/backends/neo4j/cve.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
package neo4jBackend

import (
"context"
"strings"

"github.com/guacsec/guac/pkg/assembler"
"github.com/guacsec/guac/pkg/assembler/graphql/model"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

// cveNode represents the top level CVE->Year->CVEID
Expand Down Expand Up @@ -137,3 +142,110 @@ func (e *cveYearToCveID) PropertyNames() []string {
func (e *cveYearToCveID) IdentifiablePropertyNames() []string {
return []string{}
}

func (c *neo4jClient) Cve(ctx context.Context, cveSpec *model.CVESpec) ([]*model.Cve, error) {
session := c.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()

result, err := session.ReadTransaction(
func(tx neo4j.Transaction) (interface{}, error) {

var sb strings.Builder
var firstMatch bool = true
queryValues := map[string]any{}

sb.WriteString("MATCH (n:Cve)-[:CveIsYear]->(cveYear:CveYear)-[:CveHasID]->(cveID:CveID)")

if cveSpec.Year != nil {

matchProperties(&sb, firstMatch, "cveYear", "year", "$cveYear")
firstMatch = false

queryValues["cveYear"] = cveSpec.Year
}

if cveSpec.CveID != nil {

matchProperties(&sb, firstMatch, "cveID", "id", "$cveID")
queryValues["cveID"] = cveSpec.CveID
}

sb.WriteString(" RETURN cveYear.year, cveID.id")
result, err := tx.Run(sb.String(), queryValues)
if err != nil {
return nil, err
}

cvesPerYear := map[string][]*model.CVEId{}
for result.Next() {
cveID := &model.CVEId{
ID: result.Record().Values[1].(string),
}
cvesPerYear[result.Record().Values[0].(string)] = append(cvesPerYear[result.Record().Values[0].(string)], cveID)
}
if err = result.Err(); err != nil {
return nil, err
}

cves := []*model.Cve{}
for year := range cvesPerYear {
cve := &model.Cve{
Year: year,
CveID: cvesPerYear[year],
}
cves = append(cves, cve)
}

return cves, nil
})
if err != nil {
return nil, err
}

return result.([]*model.Cve), nil
}

func (c *neo4jClient) CveOnlyYear(ctx context.Context, cveSpec *model.CVESpec) ([]*model.Cve, error) {
session := c.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()

result, err := session.ReadTransaction(
func(tx neo4j.Transaction) (interface{}, error) {

var sb strings.Builder
queryValues := map[string]any{}

sb.WriteString("MATCH (n:Cve)-[:CveIsYear]->(cveYear:CveYear)")

if cveSpec.Year != nil {

matchProperties(&sb, true, "cveYear", "year", "$cveYear")
queryValues["cveYear"] = cveSpec.Year
}

sb.WriteString(" RETURN cveYear.year")
result, err := tx.Run(sb.String(), queryValues)
if err != nil {
return nil, err
}

cves := []*model.Cve{}
for result.Next() {
cve := &model.Cve{
Year: result.Record().Values[0].(string),
CveID: []*model.CVEId{},
}
cves = append(cves, cve)
}
if err = result.Err(); err != nil {
return nil, err
}

return cves, nil
})
if err != nil {
return nil, err
}

return result.([]*model.Cve), nil
}
53 changes: 53 additions & 0 deletions pkg/assembler/backends/neo4j/ghsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
package neo4jBackend

import (
"context"
"strings"

"github.com/guacsec/guac/pkg/assembler"
"github.com/guacsec/guac/pkg/assembler/graphql/model"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

// ghsaNode represents the top level GHSA->GHSAID
Expand Down Expand Up @@ -89,3 +94,51 @@ func (e *ghsaToID) PropertyNames() []string {
func (e *ghsaToID) IdentifiablePropertyNames() []string {
return []string{}
}

func (c *neo4jClient) Ghsa(ctx context.Context, ghsaSpec *model.GHSASpec) ([]*model.Ghsa, error) {
session := c.driver.NewSession(neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead})
defer session.Close()

result, err := session.ReadTransaction(
func(tx neo4j.Transaction) (interface{}, error) {

var sb strings.Builder
queryValues := map[string]any{}

sb.WriteString("MATCH (n:Ghsa)-[:GhsaHasID]->(ghsaID:GhsaID)")

if ghsaSpec.GhsaID != nil {

matchProperties(&sb, true, "ghsaID", "id", "$ghsaID")
queryValues["ghsaID"] = ghsaSpec.GhsaID
}

sb.WriteString(" RETURN ghsaID.id")
result, err := tx.Run(sb.String(), queryValues)
if err != nil {
return nil, err
}

ghsaIds := []*model.GHSAId{}
for result.Next() {
ghsaId := &model.GHSAId{
ID: result.Record().Values[0].(string),
}
ghsaIds = append(ghsaIds, ghsaId)
}
if err = result.Err(); err != nil {
return nil, err
}

ghsa := &model.Ghsa{
GhsaID: ghsaIds,
}

return []*model.Ghsa{ghsa}, nil
})
if err != nil {
return nil, err
}

return result.([]*model.Ghsa), nil
}
Loading

0 comments on commit 43f3e34

Please sign in to comment.