From d26d22f682c485b80364251c0d04a66bb445bb1a Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 17 Jul 2024 14:05:07 +0900 Subject: [PATCH] refactor: verifier --- pkg/controller/exec/exec_test.go | 5 +- pkg/controller/install/install_test.go | 3 +- pkg/controller/wire.go | 6 + pkg/download/util.go | 2 + pkg/installpackage/aqua_test.go | 3 +- pkg/installpackage/installer.go | 18 ++- pkg/installpackage/installer_test.go | 5 +- pkg/installpackage/proxy_test.go | 3 +- pkg/installpackage/verify_minisign.go | 1 + pkg/verify/cosign/verifiy.go | 70 ++++++++ pkg/verify/cosign/version.go | 13 ++ pkg/verify/minisign/verify.go | 100 ++++++++++++ pkg/verify/minisign/version.go | 11 ++ pkg/verify/mock.go | 15 ++ pkg/verify/verify.go | 212 +++++++++++++++++++++++++ 15 files changed, 454 insertions(+), 13 deletions(-) create mode 100644 pkg/verify/cosign/verifiy.go create mode 100644 pkg/verify/cosign/version.go create mode 100644 pkg/verify/minisign/verify.go create mode 100644 pkg/verify/minisign/version.go create mode 100644 pkg/verify/mock.go create mode 100644 pkg/verify/verify.go diff --git a/pkg/controller/exec/exec_test.go b/pkg/controller/exec/exec_test.go index af103653b..fae031f85 100644 --- a/pkg/controller/exec/exec_test.go +++ b/pkg/controller/exec/exec_test.go @@ -26,6 +26,7 @@ import ( "github.com/aquaproj/aqua/v2/pkg/slsa" "github.com/aquaproj/aqua/v2/pkg/testutil" "github.com/aquaproj/aqua/v2/pkg/unarchive" + "github.com/aquaproj/aqua/v2/pkg/verify" "github.com/sirupsen/logrus" "github.com/spf13/afero" "github.com/suzuki-shunsuke/go-osenv/osenv" @@ -151,7 +152,7 @@ packages: whichCtrl := which.New(d.param, finder.NewConfigFinder(fs), reader.New(fs, d.param), registry.New(d.param, ghDownloader, fs, d.rt, &cosign.MockVerifier{}, &slsa.MockVerifier{}), d.rt, osEnv, fs, linker) downloader := download.NewDownloader(nil, download.NewHTTPDownloader(http.DefaultClient)) executor := &exec.Mock{} - pkgInstaller := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + pkgInstaller := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) policyFinder := policy.NewConfigFinder(fs) ctrl := execCtrl.New(pkgInstaller, whichCtrl, executor, osEnv, fs, policy.NewReader(fs, policy.NewValidator(d.param, fs), policyFinder, policy.NewConfigReader(fs))) if err := ctrl.Exec(ctx, logE, d.param, d.exeName, d.args...); err != nil { @@ -246,7 +247,7 @@ packages: whichCtrl := which.New(d.param, finder.NewConfigFinder(fs), reader.New(fs, d.param), registry.New(d.param, ghDownloader, afero.NewOsFs(), d.rt, &cosign.MockVerifier{}, &slsa.MockVerifier{}), d.rt, osEnv, fs, linker) downloader := download.NewDownloader(nil, download.NewHTTPDownloader(http.DefaultClient)) executor := &exec.Mock{} - pkgInstaller := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + pkgInstaller := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) ctrl := execCtrl.New(pkgInstaller, whichCtrl, executor, osEnv, fs, &policy.MockReader{}) b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/pkg/controller/install/install_test.go b/pkg/controller/install/install_test.go index ee44cad89..f479b6537 100644 --- a/pkg/controller/install/install_test.go +++ b/pkg/controller/install/install_test.go @@ -22,6 +22,7 @@ import ( "github.com/aquaproj/aqua/v2/pkg/slsa" "github.com/aquaproj/aqua/v2/pkg/testutil" "github.com/aquaproj/aqua/v2/pkg/unarchive" + "github.com/aquaproj/aqua/v2/pkg/verify" "github.com/sirupsen/logrus" ) @@ -102,7 +103,7 @@ packages: } downloader := download.NewDownloader(nil, download.NewHTTPDownloader(http.DefaultClient)) executor := &exec.Mock{} - pkgInstaller := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + pkgInstaller := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) policyFinder := policy.NewConfigFinder(fs) policyReader := policy.NewReader(fs, &policy.MockValidator{}, policyFinder, policy.NewConfigReader(fs)) ctrl := install.New(d.param, finder.NewConfigFinder(fs), reader.New(fs, d.param), registry.New(d.param, registryDownloader, fs, d.rt, &cosign.MockVerifier{}, &slsa.MockVerifier{}), pkgInstaller, fs, d.rt, policyReader) diff --git a/pkg/controller/wire.go b/pkg/controller/wire.go index ba9df0bb0..70a89f906 100644 --- a/pkg/controller/wire.go +++ b/pkg/controller/wire.go @@ -44,6 +44,7 @@ import ( "github.com/aquaproj/aqua/v2/pkg/runtime" "github.com/aquaproj/aqua/v2/pkg/slsa" "github.com/aquaproj/aqua/v2/pkg/unarchive" + "github.com/aquaproj/aqua/v2/pkg/verify" "github.com/aquaproj/aqua/v2/pkg/versiongetter" "github.com/google/wire" @@ -248,6 +249,7 @@ func InitializeInstallCommandController(ctx context.Context, param *config.Param wire.NewSet( installpackage.New, wire.Bind(new(install.Installer), new(*installpackage.Installer)), + wire.Bind(new(verify.Installer), new(*installpackage.Installer)), ), wire.NewSet( download.NewDownloader, @@ -332,6 +334,10 @@ func InitializeInstallCommandController(ctx context.Context, param *config.Param installpackage.NewCargoPackageInstallerImpl, wire.Bind(new(installpackage.CargoPackageInstaller), new(*installpackage.CargoPackageInstallerImpl)), ), + wire.NewSet( + verify.New, + wire.Bind(new(installpackage.Verifier), new(*verify.Verifier)), + ), ) return &install.Controller{}, nil } diff --git a/pkg/download/util.go b/pkg/download/util.go index 7a2bbe55d..6956b1832 100644 --- a/pkg/download/util.go +++ b/pkg/download/util.go @@ -15,6 +15,8 @@ import ( ) func ConvertDownloadedFileToFile(file *registry.DownloadedFile, art *File, rt *runtime.Runtime, tplParam *template.Artifact) (*File, error) { + // art has the version and the default value of RepoOwner and RepoName. + // tplParam has parameters to render asset and URL. f := &File{ Type: file.Type, RepoOwner: file.RepoOwner, diff --git a/pkg/installpackage/aqua_test.go b/pkg/installpackage/aqua_test.go index ff014fd7e..57564958e 100644 --- a/pkg/installpackage/aqua_test.go +++ b/pkg/installpackage/aqua_test.go @@ -15,6 +15,7 @@ import ( "github.com/aquaproj/aqua/v2/pkg/slsa" "github.com/aquaproj/aqua/v2/pkg/testutil" "github.com/aquaproj/aqua/v2/pkg/unarchive" + "github.com/aquaproj/aqua/v2/pkg/verify" "github.com/sirupsen/logrus" ) @@ -67,7 +68,7 @@ e922723678f493216c2398f3f23fb027c9a98808b49f6fce401ef82ee2c22b03 aqua_linux_arm } ctrl := installpackage.New(d.param, &download.Mock{ RC: io.NopCloser(strings.NewReader("xxx")), - }, d.rt, fs, installpackage.NewMockLinker(fs), d.checksumDownloader, d.checksumCalculator, &unarchive.MockUnarchiver{}, &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + }, d.rt, fs, installpackage.NewMockLinker(fs), d.checksumDownloader, d.checksumCalculator, &unarchive.MockUnarchiver{}, &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) if err := ctrl.InstallAqua(ctx, logE, d.version); err != nil { if d.isErr { return diff --git a/pkg/installpackage/installer.go b/pkg/installpackage/installer.go index d814327bb..cdc488420 100644 --- a/pkg/installpackage/installer.go +++ b/pkg/installpackage/installer.go @@ -51,26 +51,31 @@ type Installer struct { onlyLink bool cosignDisabled bool slsaDisabled bool + verifier Verifier } -func New(param *config.Param, downloader download.ClientAPI, rt *runtime.Runtime, fs afero.Fs, linker Linker, chkDL download.ChecksumDownloader, chkCalc ChecksumCalculator, unarchiver Unarchiver, cosignVerifier CosignVerifier, slsaVerifier SLSAVerifier, minisignVerifier MinisignVerifier, goInstallInstaller GoInstallInstaller, goBuildInstaller GoBuildInstaller, cargoPackageInstaller CargoPackageInstaller) *Installer { - installer := newInstaller(param, downloader, rt, fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller) +type Verifier interface { + Verify(ctx context.Context, logE *logrus.Entry, pkg *config.Package, bodyFile *download.DownloadedFile) error +} + +func New(param *config.Param, downloader download.ClientAPI, rt *runtime.Runtime, fs afero.Fs, linker Linker, chkDL download.ChecksumDownloader, chkCalc ChecksumCalculator, unarchiver Unarchiver, cosignVerifier CosignVerifier, slsaVerifier SLSAVerifier, minisignVerifier MinisignVerifier, goInstallInstaller GoInstallInstaller, goBuildInstaller GoBuildInstaller, cargoPackageInstaller CargoPackageInstaller, verifier Verifier) *Installer { + installer := newInstaller(param, downloader, rt, fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller, verifier) installer.cosignInstaller = &Cosign{ - installer: newInstaller(param, downloader, runtime.NewR(), fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller), + installer: newInstaller(param, downloader, runtime.NewR(), fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller, verifier), mutex: &sync.Mutex{}, } installer.slsaVerifierInstaller = &SLSAVerifierInstaller{ - installer: newInstaller(param, downloader, runtime.NewR(), fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller), + installer: newInstaller(param, downloader, runtime.NewR(), fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller, verifier), mutex: &sync.Mutex{}, } installer.minisignInstaller = &MinisignInstaller{ - installer: newInstaller(param, downloader, runtime.NewR(), fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller), + installer: newInstaller(param, downloader, runtime.NewR(), fs, linker, chkDL, chkCalc, unarchiver, cosignVerifier, slsaVerifier, minisignVerifier, goInstallInstaller, goBuildInstaller, cargoPackageInstaller, verifier), mutex: &sync.Mutex{}, } return installer } -func newInstaller(param *config.Param, downloader download.ClientAPI, rt *runtime.Runtime, fs afero.Fs, linker Linker, chkDL download.ChecksumDownloader, chkCalc ChecksumCalculator, unarchiver Unarchiver, cosignVerifier CosignVerifier, slsaVerifier SLSAVerifier, minisignVerifier MinisignVerifier, goInstallInstaller GoInstallInstaller, goBuildInstaller GoBuildInstaller, cargoPackageInstaller CargoPackageInstaller) *Installer { +func newInstaller(param *config.Param, downloader download.ClientAPI, rt *runtime.Runtime, fs afero.Fs, linker Linker, chkDL download.ChecksumDownloader, chkCalc ChecksumCalculator, unarchiver Unarchiver, cosignVerifier CosignVerifier, slsaVerifier SLSAVerifier, minisignVerifier MinisignVerifier, goInstallInstaller GoInstallInstaller, goBuildInstaller GoBuildInstaller, cargoPackageInstaller CargoPackageInstaller, verifier Verifier) *Installer { return &Installer{ rootDir: param.RootDir, maxParallelism: param.MaxParallelism, @@ -92,6 +97,7 @@ func newInstaller(param *config.Param, downloader download.ClientAPI, rt *runtim goInstallInstaller: goInstallInstaller, goBuildInstaller: goBuildInstaller, cargoPackageInstaller: cargoPackageInstaller, + verifier: verifier, } } diff --git a/pkg/installpackage/installer_test.go b/pkg/installpackage/installer_test.go index d14fe57cf..6efd84054 100644 --- a/pkg/installpackage/installer_test.go +++ b/pkg/installpackage/installer_test.go @@ -17,6 +17,7 @@ import ( "github.com/aquaproj/aqua/v2/pkg/slsa" "github.com/aquaproj/aqua/v2/pkg/testutil" "github.com/aquaproj/aqua/v2/pkg/unarchive" + "github.com/aquaproj/aqua/v2/pkg/verify" "github.com/sirupsen/logrus" "github.com/spf13/afero" ) @@ -187,7 +188,7 @@ func Test_installer_InstallPackages(t *testing.T) { //nolint:funlen } } downloader := download.NewDownloader(nil, download.NewHTTPDownloader(http.DefaultClient)) - ctrl := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(d.executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + ctrl := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(d.executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) if err := ctrl.InstallPackages(ctx, logE, &installpackage.ParamInstallPackages{ Config: d.cfg, Registries: d.registries, @@ -262,7 +263,7 @@ func Test_installer_InstallPackage(t *testing.T) { //nolint:funlen t.Fatal(err) } downloader := download.NewDownloader(nil, download.NewHTTPDownloader(http.DefaultClient)) - ctrl := installpackage.New(d.param, downloader, d.rt, fs, nil, nil, &checksum.Calculator{}, unarchive.New(d.executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + ctrl := installpackage.New(d.param, downloader, d.rt, fs, nil, nil, &checksum.Calculator{}, unarchive.New(d.executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) if err := ctrl.InstallPackage(ctx, logE, &installpackage.ParamInstallPackage{ Pkg: d.pkg, }); err != nil { diff --git a/pkg/installpackage/proxy_test.go b/pkg/installpackage/proxy_test.go index 3cc174e41..8feb4c0ee 100644 --- a/pkg/installpackage/proxy_test.go +++ b/pkg/installpackage/proxy_test.go @@ -16,6 +16,7 @@ import ( "github.com/aquaproj/aqua/v2/pkg/slsa" "github.com/aquaproj/aqua/v2/pkg/testutil" "github.com/aquaproj/aqua/v2/pkg/unarchive" + "github.com/aquaproj/aqua/v2/pkg/verify" "github.com/sirupsen/logrus" ) @@ -63,7 +64,7 @@ func Test_installer_InstallProxy(t *testing.T) { } } downloader := download.NewDownloader(nil, download.NewHTTPDownloader(http.DefaultClient)) - ctrl := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(d.executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}) + ctrl := installpackage.New(d.param, downloader, d.rt, fs, linker, nil, &checksum.Calculator{}, unarchive.New(d.executor, fs), &cosign.MockVerifier{}, &slsa.MockVerifier{}, &minisign.MockVerifier{}, &installpackage.MockGoInstallInstaller{}, &installpackage.MockGoBuildInstaller{}, &installpackage.MockCargoPackageInstaller{}, &verify.Mock{}) if err := ctrl.InstallProxy(ctx, logE); err != nil { if d.isErr { return diff --git a/pkg/installpackage/verify_minisign.go b/pkg/installpackage/verify_minisign.go index 839af7278..5cc4f2bf8 100644 --- a/pkg/installpackage/verify_minisign.go +++ b/pkg/installpackage/verify_minisign.go @@ -15,6 +15,7 @@ func (is *Installer) verifyWithMinisign(ctx context.Context, logE *logrus.Entry, if !m.GetEnabled() { return nil } + art := ppkg.TemplateArtifact(is.runtime, param.Asset) logE.Info("verify a package with minisign") if err := is.minisignInstaller.installMinisign(ctx, logE); err != nil { diff --git a/pkg/verify/cosign/verifiy.go b/pkg/verify/cosign/verifiy.go new file mode 100644 index 000000000..215515cd2 --- /dev/null +++ b/pkg/verify/cosign/verifiy.go @@ -0,0 +1,70 @@ +package cosign + +import ( + "context" + "sync" + + "github.com/aquaproj/aqua/v2/pkg/config" + "github.com/aquaproj/aqua/v2/pkg/config/aqua" + "github.com/aquaproj/aqua/v2/pkg/config/registry" + "github.com/sirupsen/logrus" +) + +type Verifier struct{} + +func NewVerifier() *Verifier { + return &Verifier{} +} + +func (v *Verifier) Package() *config.Package { + return &config.Package{ + Package: &aqua.Package{ + Name: "sigstore/cosign", + Version: Version, + }, + PackageInfo: ®istry.PackageInfo{ + Type: "github_release", + RepoOwner: "sigstore", + RepoName: "cosign", + Asset: "cosign-{{.OS}}-{{.Arch}}", + SupportedEnvs: []string{ + "darwin", + "linux", + "amd64", + }, + }, + } +} + +func (v *Verifier) Checksums() map[string]string { + return Checksums() +} + +func (v *Verifier) Enabled(pkg *registry.PackageInfo) bool { + if pkg.Minisign == nil { + return false + } + if pkg.Minisign.Enabled == nil { + return true + } + return *pkg.Minisign.Enabled +} + +func (v *Verifier) SupportedConfig() bool { + return true +} + +func (v *Verifier) Signature(ctx context.Context, logE *logrus.Entry) (*registry.DownloadedFile, string, error) { + return v.Package().PackageInfo.Minisign.ToDownloadedFile(), "", nil +} + +func (v *Verifier) Command(verifiedFilePath, sigPath string) (*sync.Mutex, int, []string) { + return nil, 1, []string{ + "-Vm", + verifiedFilePath, + "-P", + v.Package().PackageInfo.Minisign.PublicKey, + "-x", + sigPath, + } +} diff --git a/pkg/verify/cosign/version.go b/pkg/verify/cosign/version.go new file mode 100644 index 000000000..7cb9a1c8b --- /dev/null +++ b/pkg/verify/cosign/version.go @@ -0,0 +1,13 @@ +package cosign + +const Version = "v2.2.4" + +func Checksums() map[string]string { + return map[string]string{ + "darwin/amd64": "0E5A77A86115E4C00BA4243DB01ABCEACB13CC06981C45E53EE71F2E1DB8CE25", + "darwin/arm64": "FCD310E64ECDDC1EAA13FE814AC1C9FC02F6F9EACD9A58480AB8160EB8CA381E", + "linux/amd64": "97A6A1E15668A75FC4FF7A4DC4CB2F098F929CBEA2F12FAA9DE31DB6B42B17D7", + "linux/arm64": "658087351E1D4F9C396B5F59EE5437461C06128F4CE80BA899CCAA1C0B6A8A62", + "windows/amd64": "9E9B71BD3FA2A6ABFA903B5F784D9CA0FBC29C563D2B084C1A82C593C2BAB001", + } +} diff --git a/pkg/verify/minisign/verify.go b/pkg/verify/minisign/verify.go new file mode 100644 index 000000000..b5e7bacb4 --- /dev/null +++ b/pkg/verify/minisign/verify.go @@ -0,0 +1,100 @@ +package minisign + +import ( + "context" + "sync" + + "github.com/aquaproj/aqua/v2/pkg/config" + "github.com/aquaproj/aqua/v2/pkg/config/aqua" + "github.com/aquaproj/aqua/v2/pkg/config/registry" + "github.com/sirupsen/logrus" +) + +type Verifier struct{} + +func NewVerifier() *Verifier { + return &Verifier{} +} + +func (v *Verifier) Package() *config.Package { + return &config.Package{ + Package: &aqua.Package{ + Name: "jedisct1/minisign", + Version: Version, + }, + PackageInfo: ®istry.PackageInfo{ + Type: "github_release", + RepoOwner: "jedisct1", + RepoName: "minisign", + Asset: "minisign-{{.Version}}-{{.OS}}.{{.Format}}", + Format: "zip", + Rosetta2: true, + WindowsARMEmulation: true, + Replacements: map[string]string{ + "darwin": "macos", + "windows": "win64", + "amd64": "x86_64", + "arm64": "aarch64", + }, + Overrides: []*registry.Override{ + { + GOOS: "linux", + Format: "tar.gz", + Files: []*registry.File{ + { + Name: "minisign", + Src: "minisign-{{.OS}}/{{.Arch}}/minisign", + }, + }, + }, + { + GOOS: "windows", + Files: []*registry.File{ + { + Name: "minisign", + Src: "minisign-win64/minisign.exe", + }, + }, + }, + }, + SupportedEnvs: []string{ + "darwin", + "windows", + "amd64", + }, + }, + } +} + +func (v *Verifier) Checksums() map[string]string { + return Checksums() +} + +func (v *Verifier) Enabled(pkg *registry.PackageInfo) bool { + if pkg.Minisign == nil { + return false + } + if pkg.Minisign.Enabled == nil { + return true + } + return *pkg.Minisign.Enabled +} + +func (v *Verifier) SupportedConfig() bool { + return true +} + +func (v *Verifier) Signature(ctx context.Context, logE *logrus.Entry) (*registry.DownloadedFile, string, error) { + return v.Package().PackageInfo.Minisign.ToDownloadedFile(), "", nil +} + +func (v *Verifier) Command(verifiedFilePath, sigPath string) (*sync.Mutex, int, []string) { + return nil, 1, []string{ + "-Vm", + verifiedFilePath, + "-P", + v.Package().PackageInfo.Minisign.PublicKey, + "-x", + sigPath, + } +} diff --git a/pkg/verify/minisign/version.go b/pkg/verify/minisign/version.go new file mode 100644 index 000000000..2794d9304 --- /dev/null +++ b/pkg/verify/minisign/version.go @@ -0,0 +1,11 @@ +package minisign + +const Version = "0.11" + +func Checksums() map[string]string { + return map[string]string{ + "darwin/amd64": "e7c410ae8b8960d7087392472b040bda9b2f307c76df0384ac37f9ad103fc893", + "linux/amd64": "f0a0954413df8531befed169e447a66da6868d79052ed7e892e50a4291af7ae0", + "windows/amd64": "b9c31c2c3034f81f0e5f5d92cbcc20e67a9671b6e5455661588638848dc58031", + } +} diff --git a/pkg/verify/mock.go b/pkg/verify/mock.go new file mode 100644 index 000000000..3b205eb9a --- /dev/null +++ b/pkg/verify/mock.go @@ -0,0 +1,15 @@ +package verify + +import ( + "context" + + "github.com/aquaproj/aqua/v2/pkg/config" + "github.com/aquaproj/aqua/v2/pkg/download" + "github.com/sirupsen/logrus" +) + +type Mock struct{} + +func (m *Mock) Verify(ctx context.Context, logE *logrus.Entry, pkg *config.Package, bodyFile *download.DownloadedFile) error { + return nil +} diff --git a/pkg/verify/verify.go b/pkg/verify/verify.go new file mode 100644 index 000000000..fda39d17c --- /dev/null +++ b/pkg/verify/verify.go @@ -0,0 +1,212 @@ +package verify + +import ( + "context" + "errors" + "fmt" + "io" + "math/rand" + "sync" + "time" + + "github.com/aquaproj/aqua/v2/pkg/checksum" + "github.com/aquaproj/aqua/v2/pkg/config" + "github.com/aquaproj/aqua/v2/pkg/config/registry" + "github.com/aquaproj/aqua/v2/pkg/download" + "github.com/aquaproj/aqua/v2/pkg/installpackage" + "github.com/aquaproj/aqua/v2/pkg/runtime" + "github.com/aquaproj/aqua/v2/pkg/timer" + "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "github.com/suzuki-shunsuke/logrus-error/logerr" +) + +type Tool interface { + Package() *config.Package + Checksums() map[string]string + Enabled(pkg *registry.PackageInfo) bool + SupportedConfig() bool + Signature(ctx context.Context, logE *logrus.Entry) (*registry.DownloadedFile, string, error) + Command(verifiedFilePath, sigPath string) (*sync.Mutex, int, []string) +} + +type Installer interface { + InstallPackage(ctx context.Context, logE *logrus.Entry, param *installpackage.ParamInstallPackage) error +} + +type Downloader interface { + ReadCloser(ctx context.Context, logE *logrus.Entry, file *download.File) (io.ReadCloser, int64, error) +} + +type Executor interface { + Exec(ctx context.Context, exePath string, args ...string) (int, error) +} + +func New(fs afero.Fs, inst Installer, dl Downloader, exe Executor, rt *runtime.Runtime, param *config.Param) *Verifier { + return &Verifier{ + fs: fs, + inst: inst, + dl: dl, + exe: exe, + rt: rt, + rootDir: param.RootDir, + } +} + +func (v *Verifier) AddTools(tools ...Tool) { + v.tools = append(v.tools, tools...) +} + +type Verifier struct { + fs afero.Fs + tools []Tool + inst Installer + dl Downloader + exe Executor + rt *runtime.Runtime + rootDir string +} + +func (v *Verifier) Verify(ctx context.Context, logE *logrus.Entry, pkg *config.Package, bodyFile *download.DownloadedFile) error { + for _, tool := range v.tools { + if err := v.verifyTool(ctx, logE, tool, pkg, bodyFile); err != nil { + return logerr.WithFields(err, logrus.Fields{ + "verifier": tool.Package().Package.Name, + }) + } + } + return nil +} + +func (v *Verifier) verifyTool(ctx context.Context, logE *logrus.Entry, tool Tool, pkg *config.Package, bodyFile *download.DownloadedFile) error { //nolint:funlen + // Check if the installed package enables the verifier + if !tool.Enabled(pkg.PackageInfo) { + return nil + } + + tp := tool.Package() + env := v.rt.Env() + chksum := tool.Checksums()[env] + + // Check if the runtime (GOOS, GOARCH) supports the verifier. + if !tp.PackageInfo.CheckSupportedEnvs(v.rt.GOOS, v.rt.GOARCH, v.rt.Env()) { + return nil + } + + // Check if the configuration (configuration file, environment variable, command line option) supports the verifier. + if !tool.SupportedConfig() { + return nil + } + + // Install the verification tool + if err := v.inst.InstallPackage(ctx, logE, &installpackage.ParamInstallPackage{ + Checksums: checksum.New(), // Check minisign's checksum but not update aqua-checksums.json + Pkg: tp, + Checksum: &checksum.Checksum{ + Algorithm: "sha256", + Checksum: chksum, + }, + DisablePolicy: true, + }); err != nil { + return err + } + + // Download the signature + sig, sigPath, err := tool.Signature(ctx, logE) + if err != nil { + return fmt.Errorf("get the signature: %w", err) + } + if sig != nil { + s, err := v.downloadSignature(ctx, logE, tp, sig, sigPath) + if err != nil { + return err + } + defer v.fs.Remove(s) //nolint:errcheck + sigPath = s + } + + tp.PackageInfo.OverrideByRuntime(v.rt) + exePath, err := pkg.ExePath(v.rootDir, pkg.PackageInfo.GetFiles()[0], v.rt) + if err != nil { + return fmt.Errorf("get an executable file path of minisign: %w", err) + } + + verifiedFilePath, err := bodyFile.Path() + if err != nil { + return fmt.Errorf("get a temporary file path: %w", err) + } + + // Execute the verification tool + mutex, retryCount, args := tool.Command(verifiedFilePath, sigPath) + for i := range retryCount { + mutex.Lock() + defer mutex.Unlock() + _, err := v.exe.Exec(ctx, exePath, args...) + if err == nil { + return nil + } + if i == 4 { //nolint:mnd + break + } + if err := wait(ctx, logE, i+1); err != nil { + return err + } + } + return errors.New("verify the package") +} + +func (v *Verifier) getSignatureReader(ctx context.Context, logE *logrus.Entry, tool *config.Package, sig *registry.DownloadedFile) (io.ReadCloser, error) { + f, err := download.ConvertDownloadedFileToFile(sig, &download.File{ + RepoOwner: tool.PackageInfo.RepoOwner, + RepoName: tool.PackageInfo.RepoName, + Version: tool.Package.Version, + }, v.rt, tool.TemplateArtifact(v.rt, tool.PackageInfo.Asset)) + if err != nil { + return nil, err //nolint:wrapcheck + } + rc, _, err := v.dl.ReadCloser(ctx, logE, f) + if err != nil { + return nil, fmt.Errorf("download a signature: %w", err) + } + return rc, err +} + +func (v *Verifier) getSignatureWriter(sigPath string) (afero.File, string, error) { + if sigPath != "" { + f, err := v.fs.Create(sigPath) + return f, sigPath, err + } + s, err := afero.TempFile(v.fs, "", "") + return s, s.Name(), err +} + +func (v *Verifier) downloadSignature(ctx context.Context, logE *logrus.Entry, tool *config.Package, sig *registry.DownloadedFile, sigPath string) (string, error) { + rc, err := v.getSignatureReader(ctx, logE, tool, sig) + if err != nil { + return "", fmt.Errorf("download a signature: %w", err) + } + defer rc.Close() + + signatureFile, sigPath, err := v.getSignatureWriter(sigPath) + if err != nil { + return "", err + } + defer signatureFile.Close() + if _, err := io.Copy(signatureFile, rc); err != nil { + return "", fmt.Errorf("copy a signature to a temporary file: %w", err) + } + return sigPath, nil +} + +func wait(ctx context.Context, logE *logrus.Entry, retryCount int) error { + randGenerator := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec + waitTime := time.Duration(randGenerator.Intn(1000)) * time.Millisecond //nolint:mnd + logE.WithFields(logrus.Fields{ + "retry_count": retryCount, + "wait_time": waitTime, + }).Info("Verification failed temporarily, retring") + if err := timer.Wait(ctx, waitTime); err != nil { + return fmt.Errorf("wait verification: %w", err) + } + return nil +}