Do two parsing passes when building a package, and prompt user to view script after the first
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
5ed538c2c4
commit
c51248793e
40
build.go
40
build.go
|
@ -151,10 +151,18 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||||
|
|
||||||
env := genBuildEnv(info)
|
env := genBuildEnv(info)
|
||||||
|
|
||||||
|
scriptDir := filepath.Dir(script)
|
||||||
|
|
||||||
|
// The first pass is just used to get variable values and runs before
|
||||||
|
// the script is displayed, so it is restricted so as to prevent malicious
|
||||||
|
// code from executing.
|
||||||
runner, err := interp.New(
|
runner, err := interp.New(
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
interp.ExecHandler(helpers.ExecHandler),
|
interp.ExecHandler(rHelpers.ExecHandler(shutils.RestrictedExec("source"))),
|
||||||
|
interp.ReadDirHandler(shutils.RestrictedReadDir(scriptDir)),
|
||||||
|
interp.StatHandler(shutils.RestrictedStat(scriptDir)),
|
||||||
|
interp.OpenHandler(shutils.RestrictedOpen(scriptDir)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -179,6 +187,11 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = promptViewScript(script, vars.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
if !archMatches(vars.Architectures) {
|
if !archMatches(vars.Architectures) {
|
||||||
buildAnyway, err := yesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true)
|
buildAnyway, err := yesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -192,6 +205,31 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||||
|
|
||||||
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||||
|
|
||||||
|
// The second pass will be used to execute the actual functions,
|
||||||
|
// so it cannot be restricted. The script has already been displayed
|
||||||
|
// to the user by this point, so it should be safe
|
||||||
|
runner, err = interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.ExecHandler(nil)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec = decoder.New(info, runner)
|
||||||
|
|
||||||
|
// If distro was changed, the list of like distros
|
||||||
|
// no longer applies, so disable its use
|
||||||
|
if distroChanged {
|
||||||
|
dec.LikeDistros = false
|
||||||
|
}
|
||||||
|
|
||||||
baseDir := filepath.Join(config.PkgsDir, vars.Name)
|
baseDir := filepath.Join(config.PkgsDir, vars.Name)
|
||||||
srcdir := filepath.Join(baseDir, "src")
|
srcdir := filepath.Join(baseDir, "src")
|
||||||
pkgdir := filepath.Join(baseDir, "pkg")
|
pkgdir := filepath.Join(baseDir, "pkg")
|
||||||
|
|
11
cli.go
11
cli.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"go.arsenm.dev/logger/log"
|
"go.arsenm.dev/logger/log"
|
||||||
|
@ -50,16 +49,14 @@ func yesNoPrompt(msg string, def bool) (bool, error) {
|
||||||
return answer, err
|
return answer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptViewScript(script string) error {
|
func promptViewScript(script string, name string) error {
|
||||||
name := filepath.Base(filepath.Dir(script))
|
|
||||||
|
|
||||||
view, err := yesNoPrompt("Would you like to view the build script for "+name, false)
|
view, err := yesNoPrompt("Would you like to view the build script for "+name, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if view {
|
if view {
|
||||||
err = showScript(script)
|
err = showScript(script, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -77,7 +74,7 @@ func promptViewScript(script string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func showScript(path string) error {
|
func showScript(path, name string) error {
|
||||||
scriptFl, err := os.Open(path)
|
scriptFl, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -89,6 +86,6 @@ func showScript(path string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pgr := pager.New(filepath.Base(filepath.Dir(path)), str)
|
pgr := pager.New(name, str)
|
||||||
return pgr.Run()
|
return pgr.Run()
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,11 @@ var helpers = shutils.ExecFuncs{
|
||||||
"git-version": gitVersionCmd,
|
"git-version": gitVersionCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rHelpers contains restricted read-only helpers that don't modify any state
|
||||||
|
var rHelpers = shutils.ExecFuncs{
|
||||||
|
"git-version": gitVersionCmd,
|
||||||
|
}
|
||||||
|
|
||||||
func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
|
func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
|
||||||
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
|
|
|
@ -101,11 +101,6 @@ func flattenFoundPkgs(found map[string][]db.Package, verb string) []db.Package {
|
||||||
// installScripts builds and installs LURE build scripts
|
// installScripts builds and installs LURE build scripts
|
||||||
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
|
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
|
||||||
for _, script := range scripts {
|
for _, script := range scripts {
|
||||||
err := promptViewScript(script)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
builtPkgs, _, err := buildPackage(ctx, script, mgr)
|
builtPkgs, _, err := buildPackage(ctx, script, mgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error building package").Err(err).Send()
|
log.Fatal("Error building package").Err(err).Send()
|
||||||
|
|
|
@ -39,7 +39,11 @@ type ExecFunc func(hc interp.HandlerContext, name string, args []string) error
|
||||||
|
|
||||||
type ExecFuncs map[string]ExecFunc
|
type ExecFuncs map[string]ExecFunc
|
||||||
|
|
||||||
func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error {
|
// ExecHandler returns a new ExecHandlerFunc that falls back to fallback
|
||||||
|
// if the command cannot be found in the map. If fallback is nil, the default
|
||||||
|
// handler is used.
|
||||||
|
func (ef ExecFuncs) ExecHandler(fallback interp.ExecHandlerFunc) interp.ExecHandlerFunc {
|
||||||
|
return func(ctx context.Context, args []string) error {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
if fn, ok := ef[name]; ok {
|
if fn, ok := ef[name]; ok {
|
||||||
|
@ -51,6 +55,9 @@ func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defExec := interp.DefaultExecHandler(2 * time.Second)
|
if fallback == nil {
|
||||||
return defExec(ctx, args)
|
fallback = interp.DefaultExecHandler(2 * time.Second)
|
||||||
|
}
|
||||||
|
return fallback(ctx, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package shutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
|
||||||
|
return func(ctx context.Context, s string) ([]os.FileInfo, error) {
|
||||||
|
for _, allowedPrefix := range allowedPrefixes {
|
||||||
|
if strings.HasPrefix(s, allowedPrefix) {
|
||||||
|
return interp.DefaultReadDirHandler()(ctx, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc {
|
||||||
|
return func(ctx context.Context, s string, b bool) (os.FileInfo, error) {
|
||||||
|
for _, allowedPrefix := range allowedPrefixes {
|
||||||
|
if strings.HasPrefix(s, allowedPrefix) {
|
||||||
|
return interp.DefaultStatHandler()(ctx, s, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc {
|
||||||
|
return func(ctx context.Context, s string, i int, fm os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
|
for _, allowedPrefix := range allowedPrefixes {
|
||||||
|
if strings.HasPrefix(s, allowedPrefix) {
|
||||||
|
return interp.DefaultOpenHandler()(ctx, s, i, fm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NopRWC{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestrictedExec(allowedCmds ...string) interp.ExecHandlerFunc {
|
||||||
|
return func(ctx context.Context, args []string) error {
|
||||||
|
if slices.Contains(allowedCmds, args[0]) {
|
||||||
|
return interp.DefaultExecHandler(2*time.Second)(ctx, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue