Skip to content

Commit 95ee457

Browse files
authored
[allow-insecure] Allow insecure packages with --allow-insecure flag (#1265)
## Summary This allows installing insecure packages using the `--allow-insecure` flag: `devbox add nodejs@16 --allow-insecure` This saves the allow insecure state to lock file. If user tries to do add/shell/run/install and there are insecure pacakges that are not in marked in lock file, they will see an error indicating they should use flag. I used flag (instead of prompt) to limit the size of this already massive PR. TODO (in follow up): * When installing an insecure package, ask the user if they want to allow it, update the devbox.json, and install it. ## How was it tested? ```bash devbox add nodejs@16 devbox add nodejs@16 --allow-insecure devbox run run_test # edited lockfile to remove allow_insecure devbox run run_test # error devbox install # error devbox shell # error ``` See examples/insecure
1 parent 89b6055 commit 95ee457

File tree

20 files changed

+316
-78
lines changed

20 files changed

+316
-78
lines changed

devbox.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
"nixpkgs": {
2323
"commit": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3"
2424
}
25-
}
25+
}

examples/insecure/devbox.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"packages": [
3+
"nodejs@16"
4+
],
5+
"shell": {
6+
"init_hook": [
7+
"echo 'Welcome to devbox!' > /dev/null"
8+
],
9+
"scripts": {
10+
"run_test": [
11+
"node --version"
12+
]
13+
}
14+
}
15+
}

examples/insecure/devbox.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"lockfile_version": "1",
3+
"packages": {
4+
"nodejs@16": {
5+
"allow_insecure": true,
6+
"last_modified": "2023-06-29T16:20:38Z",
7+
"resolved": "github:NixOS/nixpkgs/3c614fbc76fc152f3e1bc4b2263da6d90adf80fb#nodejs_16",
8+
"source": "devbox-search",
9+
"version": "16.20.1"
10+
}
11+
}
12+
}

internal/boxcli/add.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import (
1818
const toSearchForPackages = "To search for packages, use the `devbox search` command"
1919

2020
type addCmdFlags struct {
21-
config configFlags
21+
config configFlags
22+
allowInsecure bool
2223
}
2324

2425
func addCmd() *cobra.Command {
@@ -47,13 +48,18 @@ func addCmd() *cobra.Command {
4748
}
4849

4950
flags.config.register(command)
51+
command.Flags().BoolVar(
52+
&flags.allowInsecure, "allow-insecure", false,
53+
"Allow adding packages marked as insecure.")
54+
5055
return command
5156
}
5257

5358
func addCmdFunc(cmd *cobra.Command, args []string, flags addCmdFlags) error {
5459
box, err := devbox.Open(&devopt.Opts{
55-
Dir: flags.config.path,
56-
Writer: cmd.ErrOrStderr(),
60+
Dir: flags.config.path,
61+
Writer: cmd.ErrOrStderr(),
62+
AllowInsecureAdds: flags.allowInsecure,
5763
})
5864
if err != nil {
5965
return errors.WithStack(err)

internal/devpkg/package.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,23 @@ func (p *Package) ContentAddressedPath() (string, error) {
542542
}
543543
return localPath, err
544544
}
545+
546+
func (p *Package) AllowInsecure() bool {
547+
return p.lockfile.Get(p.Raw).IsAllowInsecure()
548+
}
549+
550+
// StoreName returns the last section of the store path. Example:
551+
// /nix/store/abc123-foo-1.0.0 -> foo-1.0.0
552+
// Warning, this is probably slowish. If you need to call this multiple times,
553+
// consider caching the result.
554+
func (p *Package) StoreName() (string, error) {
555+
u, err := p.urlForInstall()
556+
if err != nil {
557+
return "", err
558+
}
559+
name, err := nix.EvalPackageName(u)
560+
if err != nil {
561+
return "", err
562+
}
563+
return name, nil
564+
}

internal/impl/devbox.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ const (
5252
)
5353

5454
type Devbox struct {
55-
cfg *devconfig.Config
56-
lockfile *lock.File
57-
nix nix.Nixer
58-
projectDir string
59-
pluginManager *plugin.Manager
60-
pure bool
55+
cfg *devconfig.Config
56+
lockfile *lock.File
57+
nix nix.Nixer
58+
projectDir string
59+
pluginManager *plugin.Manager
60+
pure bool
61+
allowInsecureAdds bool
6162

6263
// Possible TODO: hardcode this to stderr. Allowing the caller to specify the
6364
// writer is error prone. Since it is almost always stderr, we should default
@@ -81,18 +82,24 @@ func Open(opts *devopt.Opts) (*Devbox, error) {
8182
}
8283

8384
box := &Devbox{
84-
cfg: cfg,
85-
nix: &nix.Nix{},
86-
projectDir: projectDir,
87-
pluginManager: plugin.NewManager(),
88-
writer: opts.Writer,
89-
pure: opts.Pure,
85+
cfg: cfg,
86+
nix: &nix.Nix{},
87+
projectDir: projectDir,
88+
pluginManager: plugin.NewManager(),
89+
writer: opts.Writer,
90+
pure: opts.Pure,
91+
allowInsecureAdds: opts.AllowInsecureAdds,
9092
}
9193

9294
lock, err := lock.GetFile(box)
9395
if err != nil {
9496
return nil, err
9597
}
98+
// if lockfile has any allow insecure, we need to set the env var to ensure
99+
// all nix commands work.
100+
if opts.AllowInsecureAdds || lock.HasAllowInsecurePackages() {
101+
nix.AllowInsecurePackages()
102+
}
96103
box.pluginManager.ApplyOptions(
97104
plugin.WithDevbox(box),
98105
plugin.WithLockfile(lock),

internal/impl/devopt/devboxopts.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import (
55
)
66

77
type Opts struct {
8-
Dir string
9-
Pure bool
10-
IgnoreWarnings bool
11-
Writer io.Writer
8+
AllowInsecureAdds bool
9+
Dir string
10+
Pure bool
11+
IgnoreWarnings bool
12+
Writer io.Writer
1213
}

internal/impl/packages.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,16 @@ func (d *Devbox) Add(ctx context.Context, pkgsNames ...string) error {
4040

4141
// Only add packages that are not already in config. If same canonical exists,
4242
// replace it.
43-
pkgs := []*devpkg.Package{}
44-
for _, pkg := range devpkg.PackageFromStrings(lo.Uniq(pkgsNames), d.lockfile) {
43+
pkgs := devpkg.PackageFromStrings(lo.Uniq(pkgsNames), d.lockfile)
44+
45+
// addedPackageNames keeps track of the possibly transformed (versioned)
46+
// names of added packages (even if they are already in config). We use this
47+
// to know the exact name to mark as allowed insecure later on.
48+
addedPackageNames := []string{}
49+
for _, pkg := range pkgs {
4550
// If exact versioned package is already in the config, skip.
4651
if slices.Contains(d.cfg.Packages, pkg.Versioned()) {
52+
addedPackageNames = append(addedPackageNames, pkg.Versioned())
4753
continue
4854
}
4955

@@ -61,11 +67,30 @@ func (d *Devbox) Add(ctx context.Context, pkgsNames ...string) error {
6167
// if not, fallback to legacy vanilla nix.
6268
versionedPkg := devpkg.PackageFromString(pkg.Versioned(), d.lockfile)
6369
ok, err := versionedPkg.ValidateExists()
70+
packageNameForConfig := pkg.Raw
6471
if err == nil && ok {
65-
d.cfg.Packages = append(d.cfg.Packages, pkg.Versioned())
66-
} else {
67-
// fallthrough and treat package as a legacy package.
68-
d.cfg.Packages = append(d.cfg.Packages, pkg.Raw)
72+
// Only use versioned if it exists in search.
73+
packageNameForConfig = pkg.Versioned()
74+
}
75+
// else {
76+
// // TODO (landau): use nix.Search to check if this package exists
77+
// // fallthrough and treat package as a legacy package.
78+
// }
79+
80+
d.cfg.Packages = append(d.cfg.Packages, packageNameForConfig)
81+
addedPackageNames = append(addedPackageNames, packageNameForConfig)
82+
}
83+
84+
// Resolving here ensures we allow insecure before running ensurePackagesAreInstalled
85+
// which will call print-dev-env. Resolving does not save the lockfile, we
86+
// save at the end when everything has succeeded.
87+
if d.allowInsecureAdds {
88+
for _, name := range addedPackageNames {
89+
p, err := d.lockfile.Resolve(name)
90+
if err != nil {
91+
return err
92+
}
93+
p.AllowInsecure = true
6994
}
7095
}
7196

@@ -88,9 +113,7 @@ func (d *Devbox) Add(ctx context.Context, pkgsNames ...string) error {
88113
}
89114
}
90115

91-
if err := d.lockfile.Add(
92-
lo.Map(pkgs, func(pkg *devpkg.Package, _ int) string { return pkg.Raw })...,
93-
); err != nil {
116+
if err := d.lockfile.Save(); err != nil {
94117
return err
95118
}
96119

internal/lock/lockfile.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ func (l *File) Get(pkg string) *Package {
113113
return entry
114114
}
115115

116+
func (l *File) HasAllowInsecurePackages() bool {
117+
for _, pkg := range l.Packages {
118+
if pkg.AllowInsecure {
119+
return true
120+
}
121+
}
122+
return false
123+
}
124+
116125
// This probably belongs in input.go but can't add it there because it will
117126
// create a circular dependency. We could move Input into own package.
118127
func IsLegacyPackage(pkg string) bool {

internal/lock/package.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const (
99
)
1010

1111
type Package struct {
12+
AllowInsecure bool `json:"allow_insecure,omitempty"`
1213
LastModified string `json:"last_modified,omitempty"`
1314
PluginVersion string `json:"plugin_version,omitempty"`
1415
Resolved string `json:"resolved,omitempty"`
@@ -35,3 +36,10 @@ func (p *Package) GetSource() string {
3536
}
3637
return p.Source
3738
}
39+
40+
func (p *Package) IsAllowInsecure() bool {
41+
if p == nil {
42+
return false
43+
}
44+
return p.AllowInsecure
45+
}

internal/nix/command.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package nix
22

33
import (
4-
"os"
54
"os/exec"
65
)
76

@@ -12,6 +11,10 @@ func command(args ...string) *exec.Cmd {
1211
return cmd
1312
}
1413

15-
func allowUnfreeEnv() []string {
16-
return append(os.Environ(), "NIXPKGS_ALLOW_UNFREE=1")
14+
func allowUnfreeEnv(curEnv []string) []string {
15+
return append(curEnv, "NIXPKGS_ALLOW_UNFREE=1")
16+
}
17+
18+
func allowInsecureEnv(curEnv []string) []string {
19+
return append(curEnv, "NIXPKGS_ALLOW_INSECURE=1")
1720
}

internal/nix/eval.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package nix
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"strconv"
7+
)
8+
9+
func EvalPackageName(path string) (string, error) {
10+
cmd := command("eval", "--raw", path+".name")
11+
out, err := cmd.Output()
12+
if err != nil {
13+
return "", err
14+
}
15+
return string(out), nil
16+
}
17+
18+
// PackageIsInsecure is a fun little nix eval that maybe works.
19+
func PackageIsInsecure(path string) bool {
20+
cmd := command("eval", path+".meta.insecure")
21+
out, err := cmd.Output()
22+
if err != nil {
23+
// We can't know for sure, but probably not.
24+
return false
25+
}
26+
var insecure bool
27+
if err := json.Unmarshal(out, &insecure); err != nil {
28+
// We can't know for sure, but probably not.
29+
return false
30+
}
31+
return insecure
32+
}
33+
34+
func PackageKnownVulnerabilities(path string) []string {
35+
cmd := command("eval", path+".meta.knownVulnerabilities")
36+
out, err := cmd.Output()
37+
if err != nil {
38+
// We can't know for sure, but probably not.
39+
return nil
40+
}
41+
var vulnerabilities []string
42+
if err := json.Unmarshal(out, &vulnerabilities); err != nil {
43+
// We can't know for sure, but probably not.
44+
return nil
45+
}
46+
return vulnerabilities
47+
}
48+
49+
func AllowInsecurePackages() {
50+
os.Setenv("NIXPKGS_ALLOW_INSECURE", "1")
51+
}
52+
53+
func IsInsecureAllowed() bool {
54+
allowed, _ := strconv.ParseBool(os.Getenv("NIXPKGS_ALLOW_INSECURE"))
55+
return allowed
56+
}

internal/nix/nix.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13+
"regexp"
1314
"runtime/trace"
1415
"strings"
1516

1617
"github.com/pkg/errors"
1718
"go.jetpack.io/devbox/internal/boxcli/featureflag"
19+
"go.jetpack.io/devbox/internal/boxcli/usererr"
1820

1921
"go.jetpack.io/devbox/internal/debug"
2022
)
@@ -69,7 +71,9 @@ func (*Nix) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*PrintDevEn
6971
cmd.Args = append(cmd.Args, "--json")
7072
debug.Log("Running print-dev-env cmd: %s\n", cmd)
7173
data, err = cmd.Output()
72-
if err != nil {
74+
if insecure, insecureErr := isExitErrorInsecurePackage(err); insecure {
75+
return nil, insecureErr
76+
} else if err != nil {
7377
return nil, errors.Wrapf(err, "Command: %s", cmd)
7478
}
7579

@@ -120,6 +124,7 @@ func System() (string, error) {
120124
// For Savil to debug "remove nixpkgs" feature. The Search api lacks x86-darwin info.
121125
// So, I need to fake that I am x86-linux and inspect the output in generated devbox.lock
122126
// and flake.nix files.
127+
// This is also used by unit tests.
123128
override := os.Getenv("__DEVBOX_NIX_SYSTEM")
124129
if override != "" {
125130
return override, nil
@@ -144,3 +149,19 @@ func System() (string, error) {
144149
func ProfileBinPath(projectDir string) string {
145150
return filepath.Join(projectDir, ProfilePath, "bin")
146151
}
152+
153+
func isExitErrorInsecurePackage(err error) (bool, error) {
154+
var exitErr *exec.ExitError
155+
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
156+
if strings.Contains(string(exitErr.Stderr), "is marked as insecure") {
157+
re := regexp.MustCompile(`Package ([^ ]+)`)
158+
match := re.FindStringSubmatch(string(exitErr.Stderr))
159+
return true, usererr.New(
160+
"Package %s is insecure. \n\n"+
161+
"To override use `devbox add <pkg> --allow-insecure`",
162+
match[0],
163+
)
164+
}
165+
}
166+
return false, nil
167+
}

0 commit comments

Comments
 (0)