Skip to content

Commit

Permalink
fix: user info (#50)
Browse files Browse the repository at this point in the history
* fix: return error during failed userinfo lookup

Closes #48.

* fix: look up auth, user info once during snyk enrich

Closes #46.
  • Loading branch information
mcombuechen authored Feb 5, 2024
1 parent 60602d5 commit bcd1448
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 50 deletions.
26 changes: 24 additions & 2 deletions internal/commands/snyk/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,33 @@ func NewPackageCommand(logger zerolog.Logger) *cobra.Command {
if err != nil {
logger.Fatal().Err(err).Msg("Not a valid purl")
}
logger.Debug().Str("purl", args[0]).Msg("Looking up package vulnerabilities from Snyk")
resp, err := snyk.GetPackageVulnerabilities(purl)

logger.
Debug().
Str("purl", args[0]).
Msg("Looking up package vulnerabilities from Snyk")

auth, err := snyk.AuthFromToken(snyk.APIToken())
if err != nil {
logger.
Fatal().
Err(err).
Msg("Failed to get API credentials.")
}

orgID, err := snyk.SnykOrgID(auth)
if err != nil {
logger.
Fatal().
Err(err).
Msg("Failed to look up user info.")
}

resp, err := snyk.GetPackageVulnerabilities(&purl, auth, orgID)
if err != nil {
logger.Fatal().Err(err).Msg("An error occurred")
}

fmt.Print(string(resp.Body))
},
}
Expand Down
16 changes: 15 additions & 1 deletion lib/snyk/enrich_cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ func enrichCycloneDX(bom *cdx.BOM, logger zerolog.Logger) *cdx.BOM {
return bom
}

auth, err := AuthFromToken(APIToken())
if err != nil {
// TODO: log error when logger instance available.
// See https://github.com/snyk/parlay/pull/49
return nil
}

orgID, err := SnykOrgID(auth)
if err != nil {
// TODO: log error when logger instance available.
// See https://github.com/snyk/parlay/pull/49
return nil
}

wg := sizedwaitgroup.New(20)
var mutex = &sync.Mutex{}
vulnerabilities := make(map[cdx.Component][]issues.CommonIssueModelVTwo)
Expand All @@ -53,7 +67,7 @@ func enrichCycloneDX(bom *cdx.BOM, logger zerolog.Logger) *cdx.BOM {
return
}

resp, err := GetPackageVulnerabilities(purl)
resp, err := GetPackageVulnerabilities(&purl, auth, orgID)
if err != nil {
logger.Err(err).
Str("purl", purl.ToString()).
Expand Down
47 changes: 26 additions & 21 deletions lib/snyk/enrich_spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ const (
)

func enrichSPDX(bom *spdx.Document, logger zerolog.Logger) *spdx.Document {
auth, err := AuthFromToken(APIToken())
if err != nil {
// TODO: log error when logger instance available.
// See https://github.com/snyk/parlay/pull/49
return nil
}

orgID, err := SnykOrgID(auth)
if err != nil {
// TODO: log error when logger instance available.
// See https://github.com/snyk/parlay/pull/49
return nil
}

mutex := &sync.Mutex{}
wg := sizedwaitgroup.New(20)
vulnerabilities := make(map[*spdx_2_3.Package][]issues.CommonIssueModelVTwo)
Expand All @@ -54,27 +68,18 @@ func enrichSPDX(bom *spdx.Document, logger zerolog.Logger) *spdx.Document {
return
}

resp, err := GetPackageVulnerabilities(*purl)
if err != nil {
logger.Err(err).
Str("purl", purl.String()).
Msg("Failed to fetch vulnerabilities for package.")
return
}

packageData := resp.Body
var packageDoc issues.IssuesWithPurlsResponse
if err := json.Unmarshal(packageData, &packageDoc); err != nil {
logger.Err(err).
Str("status", resp.Status()).
Msg("Failed to decode Snyk vulnerability response.")
return
}

if packageDoc.Data != nil {
mutex.Lock()
vulnerabilities[pkg] = *packageDoc.Data
mutex.Unlock()
resp, err := GetPackageVulnerabilities(purl, auth, orgID)

if err == nil {
packageData := resp.Body
var packageDoc issues.IssuesWithPurlsResponse
if err := json.Unmarshal(packageData, &packageDoc); err == nil {
if packageDoc.Data != nil {
mutex.Lock()
vulnerabilities[pkg] = *packageDoc.Data
mutex.Unlock()
}
}
}
}(pkg, i)
}
Expand Down
23 changes: 3 additions & 20 deletions lib/snyk/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ package snyk

import (
"context"
"errors"
"fmt"
"os"

"github.com/deepmap/oapi-codegen/pkg/securityprovider"
"github.com/google/uuid"
"github.com/package-url/packageurl-go"

"github.com/snyk/parlay/snyk/issues"
Expand All @@ -31,29 +29,14 @@ import (
const snykServer = "https://api.snyk.io/rest"
const version = "2023-04-28"

func GetPackageVulnerabilities(purl packageurl.PackageURL) (*issues.FetchIssuesPerPurlResponse, error) {
token := os.Getenv("SNYK_TOKEN")
if token == "" {
return nil, errors.New("Must provide a SNYK_TOKEN environment variable")
}

auth, err := securityprovider.NewSecurityProviderApiKey("header", "Authorization", fmt.Sprintf("token %s", token))
if err != nil {
return nil, err
}

org, err := getSnykOrg(auth)
if err != nil {
return nil, err
}

func GetPackageVulnerabilities(purl *packageurl.PackageURL, auth *securityprovider.SecurityProviderApiKey, orgID *uuid.UUID) (*issues.FetchIssuesPerPurlResponse, error) {
client, err := issues.NewClientWithResponses(snykServer, issues.WithRequestEditorFn(auth.Intercept))
if err != nil {
return nil, err
}

params := issues.FetchIssuesPerPurlParams{Version: version}
resp, err := client.FetchIssuesPerPurlWithResponse(context.Background(), *org, purl.ToString(), &params)
resp, err := client.FetchIssuesPerPurlWithResponse(context.Background(), *orgID, purl.ToString(), &params)
if err != nil {
return nil, err
}
Expand Down
33 changes: 30 additions & 3 deletions lib/snyk/self.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ package snyk
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"

"github.com/deepmap/oapi-codegen/pkg/securityprovider"
"github.com/google/uuid"
Expand All @@ -34,7 +38,7 @@ type selfDocument struct {
}
}

func getSnykOrg(auth *securityprovider.SecurityProviderApiKey) (*uuid.UUID, error) {
func SnykOrgID(auth *securityprovider.SecurityProviderApiKey) (*uuid.UUID, error) {
experimental, err := users.NewClientWithResponses(snykServer, users.WithRequestEditorFn(auth.Intercept))
if err != nil {
return nil, err
Expand All @@ -46,12 +50,35 @@ func getSnykOrg(auth *securityprovider.SecurityProviderApiKey) (*uuid.UUID, erro
return nil, err
}

if self.HTTPResponse.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Failed to get user info (%s).", self.HTTPResponse.Status)
}

var userInfo selfDocument
if err = json.Unmarshal(self.Body, &userInfo); err != nil {
return nil, err
}

org := userInfo.Data.Attributes.DefaultOrgContext
if org := userInfo.Data.Attributes.DefaultOrgContext; org != nil {
return org, nil
}

return nil, errors.New("Failed to get org ID.")
}

func AuthFromToken(token string) (*securityprovider.SecurityProviderApiKey, error) {
if token == "" {
return nil, errors.New("Must provide a SNYK_TOKEN environment variable")
}

auth, err := securityprovider.NewSecurityProviderApiKey("header", "Authorization", fmt.Sprintf("token %s", token))
if err != nil {
return nil, err
}

return auth, nil
}

return org, nil
func APIToken() string {
return os.Getenv("SNYK_TOKEN")
}
22 changes: 19 additions & 3 deletions lib/snyk/self_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package snyk

import (
"net/http"
"testing"

"github.com/deepmap/oapi-codegen/pkg/securityprovider"
Expand All @@ -26,18 +27,33 @@ import (
"github.com/stretchr/testify/require"
)

func TestGetSnykOrg(t *testing.T) {
func TestGetSnykOrg_Success(t *testing.T) {
expectedOrg := uuid.MustParse("00000000-0000-0000-0000-000000000000")
auth, err := securityprovider.NewSecurityProviderApiKey("header", "name", "value")
require.NoError(t, err)

httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", "https://api.snyk.io/rest/self",
httpmock.NewJsonResponderOrPanic(200, httpmock.File("testdata/self.json")),
httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("testdata/self.json")),
)

actualOrg, err := getSnykOrg(auth)
actualOrg, err := SnykOrgID(auth)
assert.NoError(t, err)
assert.Equal(t, expectedOrg, *actualOrg)
}

func TestGetSnykOrg_Unauthorized(t *testing.T) {
auth, err := securityprovider.NewSecurityProviderApiKey("header", "name", "value")
require.NoError(t, err)

httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", "https://api.snyk.io/rest/self",
httpmock.NewJsonResponderOrPanic(http.StatusUnauthorized, []byte(`{"msg":"unauthorized"}`)),
)

actualOrg, err := SnykOrgID(auth)
assert.ErrorContains(t, err, "Failed to get user info (401)")
assert.Nil(t, actualOrg)
}

0 comments on commit bcd1448

Please sign in to comment.