Skip to content

Commit

Permalink
Added option to disable integrity checks in core install (for devel…
Browse files Browse the repository at this point in the history
…opment purposes) (#2740)

* Added 'enable_unsafe_install' option for platforms.

* Updated documentation

* Do not skip platform index loading if size is invalid or missing

* Added integration test
  • Loading branch information
cmaglie authored Jan 14, 2025
1 parent 2fba555 commit c78921a
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 35 deletions.
8 changes: 4 additions & 4 deletions commands/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
"google.golang.org/grpc/status"
)

func installTool(ctx context.Context, pm *packagemanager.PackageManager, tool *cores.ToolRelease, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
func installTool(ctx context.Context, pm *packagemanager.PackageManager, tool *cores.ToolRelease, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, checks resources.IntegrityCheckMode) error {
pme, release := pm.NewExplorer()
defer release()

Expand All @@ -56,7 +56,7 @@ func installTool(ctx context.Context, pm *packagemanager.PackageManager, tool *c
return errors.New(i18n.Tr("downloading %[1]s tool: %[2]s", tool, err))
}
taskCB(&rpc.TaskProgress{Completed: true})
if err := pme.InstallTool(tool, taskCB, true); err != nil {
if err := pme.InstallTool(tool, taskCB, true, checks); err != nil {
return errors.New(i18n.Tr("installing %[1]s tool: %[2]s", tool, err))
}
return nil
Expand Down Expand Up @@ -282,7 +282,7 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor
// Install builtin tools if necessary
if len(builtinToolsToInstall) > 0 {
for _, toolRelease := range builtinToolsToInstall {
if err := installTool(ctx, pmb.Build(), toolRelease, downloadCallback, taskCallback); err != nil {
if err := installTool(ctx, pmb.Build(), toolRelease, downloadCallback, taskCallback, resources.IntegrityCheckFull); err != nil {
e := &cmderrors.InitFailedError{
Code: codes.Internal,
Cause: err,
Expand Down Expand Up @@ -394,7 +394,7 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor

// Install library
taskCallback(&rpc.TaskProgress{Name: i18n.Tr("Installing library %s", libraryRef)})
if err := libRelease.Resource.Install(pme.DownloadDir, libRoot, libDir); err != nil {
if err := libRelease.Resource.Install(pme.DownloadDir, libRoot, libDir, resources.IntegrityCheckFull); err != nil {
taskCallback(&rpc.TaskProgress{Name: i18n.Tr("Error installing library %s", libraryRef)})
e := &cmderrors.FailedLibraryInstallError{Cause: err}
responseError(e.GRPCStatus())
Expand Down
7 changes: 4 additions & 3 deletions commands/service_library_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/arduino/arduino-cli/internal/arduino/libraries"
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesmanager"
"github.com/arduino/arduino-cli/internal/arduino/resources"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
Expand Down Expand Up @@ -159,7 +160,7 @@ func (s *arduinoCoreServerImpl) LibraryInstall(req *rpc.LibraryInstallRequest, s
if err := downloadLibrary(ctx, downloadsDir, libRelease, downloadCB, taskCB, downloadReason, s.settings); err != nil {
return err
}
if err := installLibrary(lmi, downloadsDir, libRelease, installTask, taskCB); err != nil {
if err := installLibrary(lmi, downloadsDir, libRelease, installTask, taskCB, resources.IntegrityCheckFull); err != nil {
return err
}
}
Expand All @@ -179,7 +180,7 @@ func (s *arduinoCoreServerImpl) LibraryInstall(req *rpc.LibraryInstallRequest, s
return nil
}

func installLibrary(lmi *librariesmanager.Installer, downloadsDir *paths.Path, libRelease *librariesindex.Release, installTask *librariesmanager.LibraryInstallPlan, taskCB rpc.TaskProgressCB) error {
func installLibrary(lmi *librariesmanager.Installer, downloadsDir *paths.Path, libRelease *librariesindex.Release, installTask *librariesmanager.LibraryInstallPlan, taskCB rpc.TaskProgressCB, checks resources.IntegrityCheckMode) error {
taskCB(&rpc.TaskProgress{Name: i18n.Tr("Installing %s", libRelease)})
logrus.WithField("library", libRelease).Info("Installing library")

Expand All @@ -193,7 +194,7 @@ func installLibrary(lmi *librariesmanager.Installer, downloadsDir *paths.Path, l

installPath := installTask.TargetPath
tmpDirPath := installPath.Parent()
if err := libRelease.Resource.Install(downloadsDir, tmpDirPath, installPath); err != nil {
if err := libRelease.Resource.Install(downloadsDir, tmpDirPath, installPath, checks); err != nil {
return &cmderrors.FailedLibraryInstallError{Cause: err}
}

Expand Down
7 changes: 6 additions & 1 deletion commands/service_platform_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/internal/arduino/resources"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)
Expand Down Expand Up @@ -95,7 +96,11 @@ func (s *arduinoCoreServerImpl) PlatformInstall(req *rpc.PlatformInstallRequest,
}
}

if err := pme.DownloadAndInstallPlatformAndTools(ctx, platformRelease, tools, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall()); err != nil {
checks := resources.IntegrityCheckFull
if s.settings.BoardManagerEnableUnsafeInstall() {
checks = resources.IntegrityCheckNone
}
if err := pme.DownloadAndInstallPlatformAndTools(ctx, platformRelease, tools, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall(), checks); err != nil {
return err
}

Expand Down
7 changes: 6 additions & 1 deletion commands/service_platform_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/cores"
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/internal/arduino/resources"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)

Expand Down Expand Up @@ -75,7 +76,11 @@ func (s *arduinoCoreServerImpl) PlatformUpgrade(req *rpc.PlatformUpgradeRequest,
Package: req.GetPlatformPackage(),
PlatformArchitecture: req.GetArchitecture(),
}
platform, err := pme.DownloadAndInstallPlatformUpgrades(ctx, ref, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall())
checks := resources.IntegrityCheckFull
if s.settings.BoardManagerEnableUnsafeInstall() {
checks = resources.IntegrityCheckNone
}
platform, err := pme.DownloadAndInstallPlatformUpgrades(ctx, ref, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall(), checks)
if err != nil {
return platform, err
}
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- `board_manager`
- `additional_urls` - the URLs to any additional Boards Manager package index files needed for your boards platforms.
- `enable_unsafe_install` - set to `true` to allow installation of packages that do not pass the checksum test. This
is considered an unsafe installation method and should be used only for development purposes.
- `daemon` - options related to running Arduino CLI as a [gRPC] server.
- `port` - TCP port used for gRPC client connections.
- `directories` - directories used by Arduino CLI.
Expand Down
11 changes: 5 additions & 6 deletions internal/arduino/cores/packageindex/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ package packageindex

import (
"encoding/json"
"errors"
"fmt"
"slices"

"github.com/arduino/arduino-cli/internal/arduino/cores"
"github.com/arduino/arduino-cli/internal/arduino/resources"
"github.com/arduino/arduino-cli/internal/arduino/security"
"github.com/arduino/arduino-cli/internal/i18n"
"github.com/arduino/go-paths-helper"
easyjson "github.com/mailru/easyjson"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -273,14 +271,15 @@ func (inPlatformRelease indexPlatformRelease) extractPlatformIn(outPackage *core
outPlatform.Deprecated = inPlatformRelease.Deprecated
}

size, err := inPlatformRelease.Size.Int64()
if err != nil {
return errors.New(i18n.Tr("invalid platform archive size: %s", err))
}
outPlatformRelease := outPlatform.GetOrCreateRelease(inPlatformRelease.Version)
outPlatformRelease.Name = inPlatformRelease.Name
outPlatformRelease.Category = inPlatformRelease.Category
outPlatformRelease.IsTrusted = trusted
size, err := inPlatformRelease.Size.Int64()
if err != nil {
logrus.Warningf("invalid platform %s archive size: %s", outPlatformRelease, err)
size = 0
}
outPlatformRelease.Resource = &resources.DownloadResource{
ArchiveFileName: inPlatformRelease.ArchiveFileName,
Checksum: inPlatformRelease.Checksum,
Expand Down
22 changes: 12 additions & 10 deletions internal/arduino/cores/packagemanager/install_uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/internal/arduino/cores"
"github.com/arduino/arduino-cli/internal/arduino/cores/packageindex"
"github.com/arduino/arduino-cli/internal/arduino/resources"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
Expand All @@ -40,6 +41,7 @@ func (pme *Explorer) DownloadAndInstallPlatformUpgrades(
taskCB rpc.TaskProgressCB,
skipPostInstall bool,
skipPreUninstall bool,
checks resources.IntegrityCheckMode,
) (*cores.PlatformRelease, error) {
if platformRef.PlatformVersion != nil {
return nil, &cmderrors.InvalidArgumentError{Message: i18n.Tr("Upgrade doesn't accept parameters with version")}
Expand All @@ -64,7 +66,7 @@ func (pme *Explorer) DownloadAndInstallPlatformUpgrades(
if err != nil {
return nil, &cmderrors.PlatformNotFoundError{Platform: platformRef.String()}
}
if err := pme.DownloadAndInstallPlatformAndTools(ctx, platformRelease, tools, downloadCB, taskCB, skipPostInstall, skipPreUninstall); err != nil {
if err := pme.DownloadAndInstallPlatformAndTools(ctx, platformRelease, tools, downloadCB, taskCB, skipPostInstall, skipPreUninstall, checks); err != nil {
return nil, err
}

Expand All @@ -78,7 +80,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
ctx context.Context,
platformRelease *cores.PlatformRelease, requiredTools []*cores.ToolRelease,
downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB,
skipPostInstall bool, skipPreUninstall bool) error {
skipPostInstall bool, skipPreUninstall bool, checks resources.IntegrityCheckMode) error {
log := pme.log.WithField("platform", platformRelease)

// Prerequisite checks before install
Expand Down Expand Up @@ -106,7 +108,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(

// Install tools first
for _, tool := range toolsToInstall {
if err := pme.InstallTool(tool, taskCB, skipPostInstall); err != nil {
if err := pme.InstallTool(tool, taskCB, skipPostInstall, checks); err != nil {
return err
}
}
Expand Down Expand Up @@ -138,7 +140,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
}

// Install
if err := pme.InstallPlatform(platformRelease); err != nil {
if err := pme.InstallPlatform(platformRelease, checks); err != nil {
log.WithError(err).Error("Cannot install platform")
return &cmderrors.FailedInstallError{Message: i18n.Tr("Cannot install platform"), Cause: err}
}
Expand Down Expand Up @@ -196,18 +198,18 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
}

// InstallPlatform installs a specific release of a platform.
func (pme *Explorer) InstallPlatform(platformRelease *cores.PlatformRelease) error {
func (pme *Explorer) InstallPlatform(platformRelease *cores.PlatformRelease, checks resources.IntegrityCheckMode) error {
destDir := pme.PackagesDir.Join(
platformRelease.Platform.Package.Name,
"hardware",
platformRelease.Platform.Architecture,
platformRelease.Version.String())
return pme.InstallPlatformInDirectory(platformRelease, destDir)
return pme.InstallPlatformInDirectory(platformRelease, destDir, checks)
}

// InstallPlatformInDirectory installs a specific release of a platform in a specific directory.
func (pme *Explorer) InstallPlatformInDirectory(platformRelease *cores.PlatformRelease, destDir *paths.Path) error {
if err := platformRelease.Resource.Install(pme.DownloadDir, pme.tempDir, destDir); err != nil {
func (pme *Explorer) InstallPlatformInDirectory(platformRelease *cores.PlatformRelease, destDir *paths.Path, checks resources.IntegrityCheckMode) error {
if err := platformRelease.Resource.Install(pme.DownloadDir, pme.tempDir, destDir, checks); err != nil {
return errors.New(i18n.Tr("installing platform %[1]s: %[2]s", platformRelease, err))
}
if d, err := destDir.Abs(); err == nil {
Expand Down Expand Up @@ -320,7 +322,7 @@ func (pme *Explorer) UninstallPlatform(platformRelease *cores.PlatformRelease, t
}

// InstallTool installs a specific release of a tool.
func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB, skipPostInstall bool) error {
func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB, skipPostInstall bool, checks resources.IntegrityCheckMode) error {
log := pme.log.WithField("Tool", toolRelease)

if toolRelease.IsInstalled() {
Expand All @@ -343,7 +345,7 @@ func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.Task
"tools",
toolRelease.Tool.Name,
toolRelease.Version.String())
err := toolResource.Install(pme.DownloadDir, pme.tempDir, destDir)
err := toolResource.Install(pme.DownloadDir, pme.tempDir, destDir, checks)
if err != nil {
log.WithError(err).Warn("Cannot install tool")
return &cmderrors.FailedInstallError{Message: i18n.Tr("Cannot install tool %s", toolRelease), Cause: err}
Expand Down
4 changes: 2 additions & 2 deletions internal/arduino/cores/packagemanager/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (pmb *Builder) installMissingProfilePlatform(ctx context.Context, platformR

// Perform install
taskCB(&rpc.TaskProgress{Name: i18n.Tr("Installing platform %s", tmpPlatformRelease)})
if err := tmpPme.InstallPlatformInDirectory(tmpPlatformRelease, destDir); err != nil {
if err := tmpPme.InstallPlatformInDirectory(tmpPlatformRelease, destDir, resources.IntegrityCheckFull); err != nil {
taskCB(&rpc.TaskProgress{Name: i18n.Tr("Error installing platform %s", tmpPlatformRelease)})
return &cmderrors.FailedInstallError{Message: i18n.Tr("Error installing platform %s", tmpPlatformRelease), Cause: err}
}
Expand Down Expand Up @@ -183,7 +183,7 @@ func (pmb *Builder) installMissingProfileTool(ctx context.Context, toolRelease *

// Install tool
taskCB(&rpc.TaskProgress{Name: i18n.Tr("Installing tool %s", toolRelease)})
if err := toolResource.Install(pmb.DownloadDir, tmp, destDir); err != nil {
if err := toolResource.Install(pmb.DownloadDir, tmp, destDir, resources.IntegrityCheckFull); err != nil {
taskCB(&rpc.TaskProgress{Name: i18n.Tr("Error installing tool %s", toolRelease)})
return &cmderrors.FailedInstallError{Message: i18n.Tr("Error installing tool %s", toolRelease), Cause: err}
}
Expand Down
21 changes: 15 additions & 6 deletions internal/arduino/resources/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,27 @@ import (
"go.bug.st/cleanup"
)

type IntegrityCheckMode int

const (
IntegrityCheckFull IntegrityCheckMode = iota
IntegrityCheckNone
)

// Install installs the resource in three steps:
// - the archive is unpacked in a temporary subdir of tempPath
// - there should be only one root dir in the unpacked content
// - the only root dir is moved/renamed to/as the destination directory
// Note that tempPath and destDir must be on the same filesystem partition
// otherwise the last step will fail.
func (release *DownloadResource) Install(downloadDir, tempPath, destDir *paths.Path) error {
// Check the integrity of the package
if ok, err := release.TestLocalArchiveIntegrity(downloadDir); err != nil {
return errors.New(i18n.Tr("testing local archive integrity: %s", err))
} else if !ok {
return errors.New(i18n.Tr("checking local archive integrity"))
func (release *DownloadResource) Install(downloadDir, tempPath, destDir *paths.Path, checks IntegrityCheckMode) error {
if checks != IntegrityCheckNone {
// Check the integrity of the package
if ok, err := release.TestLocalArchiveIntegrity(downloadDir); err != nil {
return errors.New(i18n.Tr("testing local archive integrity: %s", err))
} else if !ok {
return errors.New(i18n.Tr("checking local archive integrity"))
}
}

// Create a temporary dir to extract package
Expand Down
4 changes: 2 additions & 2 deletions internal/arduino/resources/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestInstallPlatform(t *testing.T) {
Size: 157,
}

require.NoError(t, r.Install(downloadDir, tempPath, destDir))
require.NoError(t, r.Install(downloadDir, tempPath, destDir, IntegrityCheckFull))
})

tests := []struct {
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestInstallPlatform(t *testing.T) {
require.NoError(t, err)
require.NoError(t, os.WriteFile(path.Join(downloadDir.String(), testFileName), origin, 0644))

err = test.downloadResource.Install(downloadDir, tempPath, destDir)
err = test.downloadResource.Install(downloadDir, tempPath, destDir, IntegrityCheckFull)
require.Error(t, err)
require.Contains(t, err.Error(), test.error)
})
Expand Down
7 changes: 7 additions & 0 deletions internal/cli/configuration/board_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ func (settings *Settings) BoardManagerAdditionalUrls() []string {
}
return settings.Defaults.GetStringSlice("board_manager.additional_urls")
}

func (settings *Settings) BoardManagerEnableUnsafeInstall() bool {
if v, ok, _ := settings.GetBoolOk("board_manager.enable_unsafe_install"); ok {
return v
}
return settings.Defaults.GetBool("board_manager.enable_unsafe_install")
}
4 changes: 4 additions & 0 deletions internal/cli/configuration/configuration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
"type": "string",
"format": "uri"
}
},
"enable_unsafe_install": {
"description": "set to `true` to allow installation of packages that do not pass the checksum test. This is considered an unsafe installation method and should be used only for development purposes.",
"type": "boolean"
}
},
"type": "object"
Expand Down
1 change: 1 addition & 0 deletions internal/cli/configuration/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func SetDefaults(settings *Settings) {

// Boards Manager
setDefaultValueAndKeyTypeSchema("board_manager.additional_urls", []string{})
setDefaultValueAndKeyTypeSchema("board_manager.enable_unsafe_install", false)

// arduino directories
setDefaultValueAndKeyTypeSchema("directories.data", getDefaultArduinoDataDir())
Expand Down
17 changes: 17 additions & 0 deletions internal/integrationtest/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1366,3 +1366,20 @@ func TestCoreInstallWithWrongArchiveSize(t *testing.T) {
_, _, err = cli.Run("--additional-urls", "https://raw.githubusercontent.com/geolink/opentracker-arduino-board/bf6158ebab0402db217bfb02ea61461ddc6f2940/package_opentracker_index.json", "core", "install", "opentracker:sam@1.0.5")
require.NoError(t, err)
}

func TestCoreInstallWithMissingOrInvalidChecksumAndUnsafeInstallEnabled(t *testing.T) {
// See: https://github.com/arduino/arduino-cli/issues/1468
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()

_, _, err := cli.Run("--additional-urls", "https://raw.githubusercontent.com/keyboardio/ArduinoCore-GD32-Keyboardio/refs/heads/main/package_gd32_index.json", "core", "update-index")
require.NoError(t, err)

_, _, err = cli.Run("--additional-urls", "https://raw.githubusercontent.com/keyboardio/ArduinoCore-GD32-Keyboardio/refs/heads/main/package_gd32_index.json", "core", "install", "GD32Community:gd32")
require.Error(t, err)

_, _, err = cli.RunWithCustomEnv(
map[string]string{"ARDUINO_BOARD_MANAGER_ENABLE_UNSAFE_INSTALL": "true"},
"--additional-urls", "https://raw.githubusercontent.com/keyboardio/ArduinoCore-GD32-Keyboardio/refs/heads/main/package_gd32_index.json", "core", "install", "GD32Community:gd32")
require.NoError(t, err)
}

0 comments on commit c78921a

Please sign in to comment.