feat: add support for swift cocoapods lock files (#2956)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
DmitriyLewen
2022-12-11 20:15:10 +03:00
committed by GitHub
parent c67fe17b4e
commit aea298b3dc
17 changed files with 281 additions and 5 deletions

View File

@@ -0,0 +1,13 @@
## Packages that support vulnerability scanning
- [OS packages][os_packages]
- [Language-specific packages][language_packages]
## Other language-specific packages
| Language | File |
|----------|--------------|
| Swift | PodFile.lock |
[os_packages]: ../vulnerability/detection/os.md
[language_packages]: ../vulnerability/detection/language.md

2
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/alicebob/miniredis/v2 v2.23.0 github.com/alicebob/miniredis/v2 v2.23.0
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
github.com/aquasecurity/defsec v0.82.6 github.com/aquasecurity/defsec v0.82.6
github.com/aquasecurity/go-dep-parser v0.0.0-20221115110529-0f27198c8fba github.com/aquasecurity/go-dep-parser v0.0.0-20221116104127-55a1fcada673
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46

4
go.sum
View File

@@ -192,8 +192,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
github.com/aquasecurity/defsec v0.82.6 h1:whb9ygS+cANcvGSq51s44+hY3nU6OV3VOR2Q4dIz3kc= github.com/aquasecurity/defsec v0.82.6 h1:whb9ygS+cANcvGSq51s44+hY3nU6OV3VOR2Q4dIz3kc=
github.com/aquasecurity/defsec v0.82.6/go.mod h1:sUdW6pzASralDcs+CDOE+QpWfBJt3/PY1Qbg8CS5flg= github.com/aquasecurity/defsec v0.82.6/go.mod h1:sUdW6pzASralDcs+CDOE+QpWfBJt3/PY1Qbg8CS5flg=
github.com/aquasecurity/go-dep-parser v0.0.0-20221115110529-0f27198c8fba h1:YJTAuz/SimQCplNoqSYuzH3XZYmgmdfgoGdOkjCDceE= github.com/aquasecurity/go-dep-parser v0.0.0-20221116104127-55a1fcada673 h1:EALYO9fV6ZFKYLm6FqE4YvQ48psKnxUqMtNOBS8k4lI=
github.com/aquasecurity/go-dep-parser v0.0.0-20221115110529-0f27198c8fba/go.mod h1:ZCiGJgdQxCateSw3nPMwZvp9J/+nU8/3DcGY/NO71e4= github.com/aquasecurity/go-dep-parser v0.0.0-20221116104127-55a1fcada673/go.mod h1:ZCiGJgdQxCateSw3nPMwZvp9J/+nU8/3DcGY/NO71e4=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s=
github.com/aquasecurity/go-mock-aws v0.0.0-20220726154943-99847deb62b0 h1:tihCUjLWkF0b1SAjAKcFltUs3SpsqGrLtI+Frye0D10= github.com/aquasecurity/go-mock-aws v0.0.0-20220726154943-99847deb62b0 h1:tihCUjLWkF0b1SAjAKcFltUs3SpsqGrLtI+Frye0D10=

View File

@@ -138,6 +138,15 @@ func TestFilesystem(t *testing.T) {
}, },
golden: "testdata/dotnet.json.golden", golden: "testdata/dotnet.json.golden",
}, },
{
name: "cocoapods",
args: args{
securityChecks: "vuln",
listAllPkgs: true,
input: "testdata/fixtures/fs/cocoapods",
},
golden: "testdata/cocoapods.json.golden",
},
{ {
name: "dockerfile", name: "dockerfile",
args: args{ args: args{

View File

@@ -0,0 +1,61 @@
{
"SchemaVersion": 2,
"ArtifactName": "testdata/fixtures/fs/cocoapods",
"ArtifactType": "filesystem",
"Metadata": {
"ImageConfig": {
"architecture": "",
"created": "0001-01-01T00:00:00Z",
"os": "",
"rootfs": {
"type": "",
"diff_ids": null
},
"config": {}
}
},
"Results": [
{
"Target": "Podfile.lock",
"Class": "lang-pkgs",
"Type": "cocoapods",
"Packages": [
{
"ID": "AppCenter/4.2.0",
"Name": "AppCenter",
"Version": "4.2.0",
"DependsOn": [
"AppCenter/Analytics/4.2.0",
"AppCenter/Crashes/4.2.0"
]
},
{
"ID": "AppCenter/Analytics/4.2.0",
"Name": "AppCenter/Analytics",
"Version": "4.2.0",
"DependsOn": [
"AppCenter/Core/4.2.0"
]
},
{
"ID": "AppCenter/Core/4.2.0",
"Name": "AppCenter/Core",
"Version": "4.2.0"
},
{
"ID": "AppCenter/Crashes/4.2.0",
"Name": "AppCenter/Crashes",
"Version": "4.2.0",
"DependsOn": [
"AppCenter/Core/4.2.0"
]
},
{
"ID": "KeychainAccess/4.2.1",
"Name": "KeychainAccess",
"Version": "4.2.1"
}
]
}
]
}

View File

@@ -0,0 +1,12 @@
PODS:
- AppCenter (4.2.0):
- AppCenter/Analytics (= 4.2.0)
- AppCenter/Crashes (= 4.2.0)
- AppCenter/Analytics (4.2.0):
- AppCenter/Core
- AppCenter/Core (4.2.0)
- AppCenter/Crashes (4.2.0):
- AppCenter/Core
- KeychainAccess (4.2.1)
COCOAPODS: 1.11.2

View File

@@ -98,6 +98,7 @@ nav:
- AWS EC2: docs/vm/aws.md - AWS EC2: docs/vm/aws.md
- SBOM: - SBOM:
- Overview: docs/sbom/index.md - Overview: docs/sbom/index.md
- Supported: docs/sbom/supported.md
- CycloneDX: docs/sbom/cyclonedx.md - CycloneDX: docs/sbom/cyclonedx.md
- SPDX: docs/sbom/spdx.md - SPDX: docs/sbom/spdx.md
- Attestation: - Attestation:

View File

@@ -1,6 +1,8 @@
package library package library
import ( import (
"errors"
"golang.org/x/xerrors" "golang.org/x/xerrors"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
@@ -11,6 +13,9 @@ import (
func Detect(libType string, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { func Detect(libType string, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
driver, err := NewDriver(libType) driver, err := NewDriver(libType)
if err != nil { if err != nil {
if errors.Is(err, ErrSBOMSupportOnly) {
return nil, nil
}
return nil, xerrors.Errorf("failed to initialize a driver: %w", err) return nil, xerrors.Errorf("failed to initialize a driver: %w", err)
} }

View File

@@ -4,21 +4,23 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/aquasecurity/trivy/pkg/detector/library/compare/maven"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/db"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types" dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
"github.com/aquasecurity/trivy/pkg/detector/library/compare" "github.com/aquasecurity/trivy/pkg/detector/library/compare"
"github.com/aquasecurity/trivy/pkg/detector/library/compare/maven"
"github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm"
"github.com/aquasecurity/trivy/pkg/detector/library/compare/pep440" "github.com/aquasecurity/trivy/pkg/detector/library/compare/pep440"
"github.com/aquasecurity/trivy/pkg/detector/library/compare/rubygems" "github.com/aquasecurity/trivy/pkg/detector/library/compare/rubygems"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/types"
) )
var ErrSBOMSupportOnly = xerrors.New("SBOM support only")
// NewDriver returns a driver according to the library type // NewDriver returns a driver according to the library type
func NewDriver(libType string) (Driver, error) { func NewDriver(libType string) (Driver, error) {
var ecosystem dbTypes.Ecosystem var ecosystem dbTypes.Ecosystem
@@ -54,6 +56,9 @@ func NewDriver(libType string) (Driver, error) {
// Only semver can be used for version ranges // Only semver can be used for version ranges
// https://docs.conan.io/en/latest/versioning/version_ranges.html // https://docs.conan.io/en/latest/versioning/version_ranges.html
comparer = compare.GenericComparer{} comparer = compare.GenericComparer{}
case ftypes.Cocoapods:
log.Logger.Warn("CocoaPods is supported for SBOM, not for vulnerability scanning")
return Driver{}, ErrSBOMSupportOnly
default: default:
return Driver{}, xerrors.Errorf("unsupported type %s", libType) return Driver{}, xerrors.Errorf("unsupported type %s", libType)
} }

View File

@@ -26,6 +26,7 @@ import (
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/gemspec" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/ruby/gemspec"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/rust/binary" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/rust/binary"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/rust/cargo" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/rust/cargo"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/swift/cocoapods"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/licensing"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/amazonlinux" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/amazonlinux"

View File

@@ -74,6 +74,9 @@ const (
// C/C++ // C/C++
TypeConanLock Type = "conan-lock" TypeConanLock Type = "conan-lock"
// Swift
TypeCocoaPods Type = "cocoapods"
// ============ // ============
// Non-packaged // Non-packaged
// ============ // ============
@@ -125,12 +128,14 @@ var (
TypeBundler, TypeGemSpec, TypeCargo, TypeComposer, TypeJar, TypePom, TypeGradleLock, TypeBundler, TypeGemSpec, TypeCargo, TypeComposer, TypeJar, TypePom, TypeGradleLock,
TypeNpmPkgLock, TypeNodePkg, TypeYarn, TypePnpm, TypeNuget, TypeDotNetCore, TypeNpmPkgLock, TypeNodePkg, TypeYarn, TypePnpm, TypeNuget, TypeDotNetCore,
TypePythonPkg, TypePip, TypePipenv, TypePoetry, TypeGoBinary, TypeGoMod, TypeRustBinary, TypeConanLock, TypePythonPkg, TypePip, TypePipenv, TypePoetry, TypeGoBinary, TypeGoMod, TypeRustBinary, TypeConanLock,
TypeCocoaPods,
} }
// TypeLockfiles has all lock file analyzers // TypeLockfiles has all lock file analyzers
TypeLockfiles = []Type{ TypeLockfiles = []Type{
TypeBundler, TypeNpmPkgLock, TypeYarn, TypeBundler, TypeNpmPkgLock, TypeYarn,
TypePnpm, TypePip, TypePipenv, TypePoetry, TypeGoMod, TypePom, TypeConanLock, TypeGradleLock, TypePnpm, TypePip, TypePipenv, TypePoetry, TypeGoMod, TypePom, TypeConanLock, TypeGradleLock,
TypeCocoaPods,
} }
// TypeIndividualPkgs has all analyzers for individual packages // TypeIndividualPkgs has all analyzers for individual packages

View File

@@ -0,0 +1,45 @@
package cocoapods
import (
"context"
"os"
"github.com/aquasecurity/go-dep-parser/pkg/swift/cocoapods"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"golang.org/x/xerrors"
)
func init() {
analyzer.RegisterAnalyzer(&cocoaPodsLockAnalyzer{})
}
const (
version = 1
)
// cocoaPodsLockAnalyzer analyzes Podfile.lock
type cocoaPodsLockAnalyzer struct{}
func (a cocoaPodsLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
p := cocoapods.NewParser()
res, err := language.Analyze(types.Cocoapods, input.FilePath, input.Content, p)
if err != nil {
return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err)
}
return res, nil
}
func (a cocoaPodsLockAnalyzer) Required(_ string, fileInfo os.FileInfo) bool {
return fileInfo.Name() == types.CocoaPodsLock
}
func (a cocoaPodsLockAnalyzer) Type() analyzer.Type {
return analyzer.TypeCocoaPods
}
func (a cocoaPodsLockAnalyzer) Version() int {
return version
}

View File

@@ -0,0 +1,99 @@
package cocoapods
import (
"os"
"sort"
"testing"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) {
tests := []struct {
name string
inputFile string
want *analyzer.AnalysisResult
}{
{
name: "happy path",
inputFile: "testdata/happy.lock",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Cocoapods,
FilePath: "testdata/happy.lock",
Libraries: []types.Package{
{
ID: "AppCenter/4.2.0",
Name: "AppCenter",
Version: "4.2.0",
DependsOn: []string{
"AppCenter/Analytics/4.2.0",
"AppCenter/Crashes/4.2.0",
},
},
{
ID: "AppCenter/Analytics/4.2.0",
Name: "AppCenter/Analytics",
Version: "4.2.0",
DependsOn: []string{
"AppCenter/Core/4.2.0",
},
},
{
ID: "AppCenter/Core/4.2.0",
Name: "AppCenter/Core",
Version: "4.2.0",
},
{
ID: "AppCenter/Crashes/4.2.0",
Name: "AppCenter/Crashes",
Version: "4.2.0",
DependsOn: []string{
"AppCenter/Core/4.2.0",
},
},
{
ID: "KeychainAccess/4.2.1",
Name: "KeychainAccess",
Version: "4.2.1",
},
},
},
},
},
},
{
name: "empty file",
inputFile: "testdata/empty.lock",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()
a := cocoaPodsLockAnalyzer{}
got, err := a.Analyze(nil, analyzer.AnalysisInput{
FilePath: tt.inputFile,
Content: f,
})
if got != nil {
for _, app := range got.Applications {
sort.Slice(app.Libraries, func(i, j int) bool {
return app.Libraries[i].ID < app.Libraries[j].ID
})
}
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1 @@
COCOAPODS: 1.11.2

View File

@@ -0,0 +1,12 @@
PODS:
- AppCenter (4.2.0):
- AppCenter/Analytics (= 4.2.0)
- AppCenter/Crashes (= 4.2.0)
- AppCenter/Analytics (4.2.0):
- AppCenter/Core
- AppCenter/Core (4.2.0)
- AppCenter/Crashes (4.2.0):
- AppCenter/Core
- KeychainAccess (4.2.1)
COCOAPODS: 1.11.2

View File

@@ -29,6 +29,7 @@ const (
JavaScript = "javascript" JavaScript = "javascript"
RustBinary = "rustbinary" RustBinary = "rustbinary"
Conan = "conan" Conan = "conan"
Cocoapods = "cocoapods"
// Config files // Config files
YAML = "yaml" YAML = "yaml"
@@ -70,4 +71,6 @@ const (
CargoLock = "Cargo.lock" CargoLock = "Cargo.lock"
ConanLock = "conan.lock" ConanLock = "conan.lock"
CocoaPodsLock = "Podfile.lock"
) )

View File

@@ -95,6 +95,8 @@ func (p *PackageURL) AppType() string {
return ftypes.Cargo return ftypes.Cargo
case packageurl.TypeNuget: case packageurl.TypeNuget:
return ftypes.NuGet return ftypes.NuGet
case packageurl.TypeSwift:
return ftypes.Cocoapods
} }
return p.Type return p.Type
} }
@@ -301,6 +303,8 @@ func purlType(t string) string {
return packageurl.TypeGolang return packageurl.TypeGolang
case ftypes.Npm, ftypes.NodePkg, ftypes.Yarn, ftypes.Pnpm: case ftypes.Npm, ftypes.NodePkg, ftypes.Yarn, ftypes.Pnpm:
return packageurl.TypeNPM return packageurl.TypeNPM
case ftypes.Cocoapods:
return packageurl.TypeSwift
case os.Alpine: case os.Alpine:
return string(analyzer.TypeApk) return string(analyzer.TypeApk)
case os.Debian, os.Ubuntu: case os.Debian, os.Ubuntu: