deprecate using GOPRIVATE in favor of GOGARBLE (#427)

Piggybacking off of GOPRIVATE is great for a number of reasons:

* People tend to obfuscate private code, whose package paths will
  generally be in GOPRIVATE already

* Its meaning and syntax are well understood

* It allows all the flexibility we need without adding our own env var
  or config option

However, using GOPRIVATE directly has one main drawback.
It's fairly common to also want to obfuscate public dependencies,
to make the code in private packages even harder to follow.
However, using "GOPRIVATE=*" will result in two main downsides:

* GONOPROXY defaults to GOPRIVATE, so the proxy would be entirely disabled.
  Downloading modules, such as when adding or updating dependencies,
  or when the local cache is cold, can be less reliable.

* GONOSUMDB defaults to GOPRIVATE, so the sumdb would be entirely disabled.
  Adding entries to go.sum, such as when adding or updating dependencies,
  can be less secure.

We will continue to consume GOPRIVATE as a fallback,
but we now expect users to set GOGARBLE instead.
The new logic is documented in the README.

While here, rewrite some uses of "private" with "to obfuscate",
to make the code easier to follow and harder to misunderstand.

Fixes #276.
pull/428/head
Daniel Martí 3 years ago committed by GitHub
parent a645929151
commit fceb19f6da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,9 +34,11 @@ order to:
* [Obfuscate literals](#literal-obfuscation), if the `-literals` flag is given * [Obfuscate literals](#literal-obfuscation), if the `-literals` flag is given
* Remove [extra information](#tiny-mode), if the `-tiny` flag is given * Remove [extra information](#tiny-mode), if the `-tiny` flag is given
By default, the tool obfuscates the packages under the current module. If not The tool obfuscates the packages matching `GOGARBLE`, a comma-separated list of
running in module mode, then only the main package is obfuscated. To specify glob patterns of module path prefixes, as documented in `go help private`.
what packages to obfuscate, set `GOPRIVATE`, documented at `go help private`. When `GOGARBLE` is empty, it assumes the value of `GOPRIVATE`.
When `GOPRIVATE` is also empty, then `GOGARBLE` assumes the value of the current
module path, to obfuscate all packages under the current module.
Note that commands like `garble build` will use the `go` version found in your Note that commands like `garble build` will use the `go` version found in your
`$PATH`. To use different versions of Go, you can `$PATH`. To use different versions of Go, you can

@ -80,7 +80,7 @@ func alterToolVersion(tool string, args []string) error {
// and returns a new hash which also contains garble's own deterministic inputs. // and returns a new hash which also contains garble's own deterministic inputs.
// //
// This includes garble's own version, obtained via its own binary's content ID, // This includes garble's own version, obtained via its own binary's content ID,
// as well as any other options which affect a build, such as GOPRIVATE and -tiny. // as well as any other options which affect a build, such as GOGARBLE and -tiny.
func addGarbleToHash(inputHash []byte) []byte { func addGarbleToHash(inputHash []byte) []byte {
// Join the two content IDs together into a single base64-encoded sha256 // Join the two content IDs together into a single base64-encoded sha256
// sum. This includes the original tool's content ID, and garble's own // sum. This includes the original tool's content ID, and garble's own
@ -95,8 +95,8 @@ func addGarbleToHash(inputHash []byte) []byte {
// We also need to add the selected options to the full version string, // We also need to add the selected options to the full version string,
// because all of them result in different output. We use spaces to // because all of them result in different output. We use spaces to
// separate the env vars and flags, to reduce the chances of collisions. // separate the env vars and flags, to reduce the chances of collisions.
if cache.GoEnv.GOPRIVATE != "" { if cache.GOGARBLE != "" {
fmt.Fprintf(h, " GOPRIVATE=%s", cache.GoEnv.GOPRIVATE) fmt.Fprintf(h, " GOGARBLE=%s", cache.GOGARBLE)
} }
if opts.ObfuscateLiterals { if opts.ObfuscateLiterals {
fmt.Fprintf(h, " -literals") fmt.Fprintf(h, " -literals")

@ -399,16 +399,15 @@ var transformFuncs = map[string]func([]string) (args []string, _ error){
} }
func transformAsm(args []string) ([]string, error) { func transformAsm(args []string) ([]string, error) {
// If the current package isn't private, we have nothing to do. if !curPkg.ToObfuscate {
if !curPkg.Private { return args, nil // we're not obfuscating this package
return args, nil
} }
flags, paths := splitFlagsFromFiles(args, ".s") flags, paths := splitFlagsFromFiles(args, ".s")
// When assembling, the import path can make its way into the output // When assembling, the import path can make its way into the output
// object file. // object file.
if curPkg.Name != "main" && curPkg.Private { if curPkg.Name != "main" && curPkg.ToObfuscate {
flags = flagSetValue(flags, "-p", curPkg.obfuscatedImportPath()) flags = flagSetValue(flags, "-p", curPkg.obfuscatedImportPath())
} }
@ -509,7 +508,7 @@ func transformAsm(args []string) ([]string, error) {
} }
// Uncomment for some quick debugging. Do not delete. // Uncomment for some quick debugging. Do not delete.
// if curPkg.Private { // if curPkg.ToObfuscate {
// fmt.Fprintf(os.Stderr, "\n-- %s --\n%s", path, buf.Bytes()) // fmt.Fprintf(os.Stderr, "\n-- %s --\n%s", path, buf.Bytes())
// } // }
@ -613,7 +612,7 @@ func transformCompile(args []string) ([]string, error) {
// If this is a package to obfuscate, swap the -p flag with the new // If this is a package to obfuscate, swap the -p flag with the new
// package path. // package path.
newPkgPath := "" newPkgPath := ""
if curPkg.Name != "main" && curPkg.Private { if curPkg.Name != "main" && curPkg.ToObfuscate {
newPkgPath = curPkg.obfuscatedImportPath() newPkgPath = curPkg.obfuscatedImportPath()
flags = flagSetValue(flags, "-p", newPkgPath) flags = flagSetValue(flags, "-p", newPkgPath)
} }
@ -642,7 +641,7 @@ func transformCompile(args []string) ([]string, error) {
} }
// Uncomment for some quick debugging. Do not delete. // Uncomment for some quick debugging. Do not delete.
// if curPkg.Private { // if curPkg.ToObfuscate {
// fmt.Fprintf(os.Stderr, "\n-- %s/%s --\n%s", curPkg.ImportPath, name, src) // fmt.Fprintf(os.Stderr, "\n-- %s/%s --\n%s", curPkg.ImportPath, name, src)
// } // }
@ -688,7 +687,7 @@ func (tf *transformer) handleDirectives(comments []*ast.CommentGroup) {
// This directive has two arguments: "go:linkname localName newName" // This directive has two arguments: "go:linkname localName newName"
// obfuscate the local name, if the current package is obfuscated // obfuscate the local name, if the current package is obfuscated
if curPkg.Private { if curPkg.ToObfuscate {
fields[1] = hashWith(curPkg.GarbleActionID, fields[1]) fields[1] = hashWith(curPkg.GarbleActionID, fields[1])
} }
@ -714,7 +713,7 @@ func (tf *transformer) handleDirectives(comments []*ast.CommentGroup) {
comment.Text = strings.Join(fields, " ") comment.Text = strings.Join(fields, " ")
continue continue
} }
if lpkg.Private { if lpkg.ToObfuscate {
// The name exists and was obfuscated; obfuscate // The name exists and was obfuscated; obfuscate
// the new name. // the new name.
newName := hashWith(lpkg.GarbleActionID, name) newName := hashWith(lpkg.GarbleActionID, name)
@ -781,9 +780,9 @@ var runtimeAndDeps = map[string]bool{
"runtime": true, "runtime": true,
} }
// isPrivate checks if a package import path should be considered private, // toObfuscate checks if a package should be obfuscated given its import path.
// meaning that it should be obfuscated. // If you are holding a listedPackage, reuse its ToObfuscate field instead.
func isPrivate(path string) bool { func toObfuscate(path string) bool {
// We don't support obfuscating these yet. // We don't support obfuscating these yet.
if cannotObfuscate[path] || runtimeAndDeps[path] { if cannotObfuscate[path] || runtimeAndDeps[path] {
return false return false
@ -792,7 +791,7 @@ func isPrivate(path string) bool {
if path == "command-line-arguments" || strings.HasPrefix(path, "plugin/unnamed") { if path == "command-line-arguments" || strings.HasPrefix(path, "plugin/unnamed") {
return true return true
} }
return module.MatchPrefixPatterns(cache.GoEnv.GOPRIVATE, path) return module.MatchPrefixPatterns(cache.GOGARBLE, path)
} }
// processImportCfg parses the importcfg file passed to a compile or link step, // processImportCfg parses the importcfg file passed to a compile or link step,
@ -866,7 +865,7 @@ func processImportCfg(flags []string) (newImportCfg string, _ error) {
} }
for _, pair := range importmaps { for _, pair := range importmaps {
beforePath, afterPath := pair[0], pair[1] beforePath, afterPath := pair[0], pair[1]
if isPrivate(afterPath) { if toObfuscate(afterPath) {
lpkg, err := listPackage(beforePath) lpkg, err := listPackage(beforePath)
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
@ -884,7 +883,7 @@ func processImportCfg(flags []string) (newImportCfg string, _ error) {
} }
for _, pair := range packagefiles { for _, pair := range packagefiles {
impPath, pkgfile := pair[0], pair[1] impPath, pkgfile := pair[0], pair[1]
if isPrivate(impPath) { if toObfuscate(impPath) {
lpkg, err := listPackage(impPath) lpkg, err := listPackage(impPath)
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
@ -913,7 +912,7 @@ type (
var cachedOutput = struct { var cachedOutput = struct {
// KnownObjectFiles is filled from -importcfg in the current obfuscated build. // KnownObjectFiles is filled from -importcfg in the current obfuscated build.
// As such, it records export data for the dependencies which might be // As such, it records export data for the dependencies which might be
// themselves obfuscated, depending on GOPRIVATE. // themselves obfuscated, depending on GOGARBLE.
// //
// TODO: We rely on obfuscated type information to know what names we didn't // TODO: We rely on obfuscated type information to know what names we didn't
// obfuscate. Instead, directly record what names we chose not to obfuscate, // obfuscate. Instead, directly record what names we chose not to obfuscate,
@ -1337,8 +1336,8 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File {
if err != nil { if err != nil {
panic(err) // shouldn't happen panic(err) // shouldn't happen
} }
if !lpkg.Private { if !lpkg.ToObfuscate {
return true // only private packages are transformed return true // we're not obfuscating this package
} }
hashToUse := lpkg.GarbleActionID hashToUse := lpkg.GarbleActionID
@ -1449,7 +1448,7 @@ func (tf *transformer) transformGo(file *ast.File) *ast.File {
if err != nil { if err != nil {
panic(err) // should never happen panic(err) // should never happen
} }
if !lpkg.Private { if !lpkg.ToObfuscate {
return true return true
} }
newPath := lpkg.obfuscatedImportPath() newPath := lpkg.obfuscatedImportPath()
@ -1829,15 +1828,22 @@ How to install Go: https://golang.org/doc/install
if err := json.Unmarshal(out, &cache.GoEnv); err != nil { if err := json.Unmarshal(out, &cache.GoEnv); err != nil {
return err return err
} }
cache.GOGARBLE = os.Getenv("GOGARBLE")
if cache.GOGARBLE != "" {
// GOGARBLE is non-empty; nothing to do.
} else if cache.GoEnv.GOPRIVATE != "" {
// GOGARBLE is empty and GOPRIVATE is non-empty.
// Set GOGARBLE to GOPRIVATE's value.
cache.GOGARBLE = cache.GoEnv.GOPRIVATE
} else {
// If GOPRIVATE isn't set and we're in a module, use its module // If GOPRIVATE isn't set and we're in a module, use its module
// path as a GOPRIVATE default. Include a _test variant too. // path as a GOPRIVATE default. Include a _test variant too.
// TODO(mvdan): we shouldn't need the _test variant here, // TODO(mvdan): we shouldn't need the _test variant here,
// as the import path should not include it; only the package name. // as the import path should not include it; only the package name.
if cache.GoEnv.GOPRIVATE == "" {
if mod, err := ioutil.ReadFile(cache.GoEnv.GOMOD); err == nil { if mod, err := ioutil.ReadFile(cache.GoEnv.GOMOD); err == nil {
modpath := modfile.ModulePath(mod) modpath := modfile.ModulePath(mod)
if modpath != "" { if modpath != "" {
cache.GoEnv.GOPRIVATE = modpath + "," + modpath + "_test" cache.GOGARBLE = modpath + "," + modpath + "_test"
} }
} }
} }

@ -30,8 +30,8 @@ func printFile(file1 *ast.File) ([]byte, error) {
} }
src := buf1.Bytes() src := buf1.Bytes()
if !curPkg.Private { if !curPkg.ToObfuscate {
// TODO(mvdan): make transformCompile handle non-private // TODO(mvdan): make transformCompile handle untouched
// packages like runtime earlier on, to remove these checks. // packages like runtime earlier on, to remove these checks.
return src, nil return src, nil
} }

@ -65,7 +65,7 @@ One can reverse a captured panic stack trace as follows:
var replaces []string var replaces []string
for _, lpkg := range cache.ListedPackages { for _, lpkg := range cache.ListedPackages {
if !lpkg.Private { if !lpkg.ToObfuscate {
continue continue
} }
curPkg = lpkg curPkg = lpkg

@ -40,9 +40,11 @@ type sharedCache struct {
// we can likely just use that. // we can likely just use that.
BinaryContentID []byte BinaryContentID []byte
GOGARBLE string
// From "go env", primarily. // From "go env", primarily.
GoEnv struct { GoEnv struct {
GOPRIVATE string // Set to the module path as a fallback. GOPRIVATE string
GOMOD string GOMOD string
GOVERSION string GOVERSION string
GOCACHE string GOCACHE string
@ -207,11 +209,11 @@ type listedPackage struct {
GarbleActionID []byte GarbleActionID []byte
Private bool ToObfuscate bool
} }
func (p *listedPackage) obfuscatedImportPath() string { func (p *listedPackage) obfuscatedImportPath() string {
if p.Name == "main" || p.ImportPath == "embed" || !p.Private { if p.Name == "main" || p.ImportPath == "embed" || !p.ToObfuscate {
return p.ImportPath return p.ImportPath
} }
newPath := hashWith(p.GarbleActionID, p.ImportPath) newPath := hashWith(p.GarbleActionID, p.ImportPath)
@ -263,21 +265,22 @@ func setListedPackages(patterns []string) error {
return fmt.Errorf("go list error: %v: %s", err, stderr.Bytes()) return fmt.Errorf("go list error: %v: %s", err, stderr.Bytes())
} }
anyPrivate := false anyToObfuscate := false
for path, pkg := range cache.ListedPackages { for path, pkg := range cache.ListedPackages {
// If "GOPRIVATE=foo/bar", "foo/bar_test" is also private. // If "GOGARBLE=foo/bar", "foo/bar_test" should also match.
if pkg.ForTest != "" { if pkg.ForTest != "" {
path = pkg.ForTest path = pkg.ForTest
} }
// Test main packages like "foo/bar.test" are always private. // Test main packages like "foo/bar.test" are always obfuscated,
if (pkg.Name == "main" && strings.HasSuffix(path, ".test")) || isPrivate(path) { // just like main packages.
pkg.Private = true if (pkg.Name == "main" && strings.HasSuffix(path, ".test")) || toObfuscate(path) {
anyPrivate = true pkg.ToObfuscate = true
anyToObfuscate = true
} }
} }
if !anyPrivate { if !anyToObfuscate {
return fmt.Errorf("GOPRIVATE=%q does not match any packages to be built", os.Getenv("GOPRIVATE")) return fmt.Errorf("GOGARBLE=%q does not match any packages to be built", cache.GOGARBLE)
} }
return nil return nil

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build garble build
exec ./main exec ./main

@ -1,4 +1,4 @@
# Check that the simplest use of garble works. Note the lack of a module or GOPRIVATE. # Check that the simplest use of garble works. Note the lack of a module or GOGARBLE.
garble build -o=main$exe garble_main.go garble build -o=main$exe garble_main.go
exec ./main exec ./main
cmp stderr main.stderr cmp stderr main.stderr

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build garble build
exec ./main exec ./main
@ -7,12 +7,12 @@ binsubstr main$exe 'privateAdd'
[short] stop # no need to verify this with -short [short] stop # no need to verify this with -short
env GOPRIVATE=* env GOGARBLE=*
garble build garble build
exec ./main exec ./main
cmp stdout main.stdout cmp stdout main.stdout
binsubstr main$exe 'privateAdd' binsubstr main$exe 'privateAdd'
env GOPRIVATE=test/main env GOGARBLE=test/main
garble -tiny build garble -tiny build
exec ./main exec ./main

@ -8,7 +8,7 @@
[!arm] env GOARCH=arm [!arm] env GOARCH=arm
[arm] env GOARCH=arm64 [arm] env GOARCH=arm64
env GOPRIVATE='*' env GOGARBLE='*'
# Link a binary importing net/http, which will catch whether or not we # Link a binary importing net/http, which will catch whether or not we
# support ImportMap when linking. # support ImportMap when linking.

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble -debugdir ./debug1 build garble -debugdir ./debug1 build
exists 'debug1/test/main/imported/imported.go' 'debug1/test/main/main.go' exists 'debug1/test/main/imported/imported.go' 'debug1/test/main/main.go'

@ -1,4 +1,4 @@
env GOPRIVATE=* env GOGARBLE=*
garble build garble build

@ -1,19 +1,25 @@
# Ensure that "does not match any packages" works with GOPRIVATE and GOGARBLE.
env GOGARBLE=match-absolutely/nothing
! garble build -o=out ./standalone
stderr '^GOGARBLE="match-absolutely/nothing" does not match any packages to be built$'
env GOGARBLE=
env GOPRIVATE=match-absolutely/nothing env GOPRIVATE=match-absolutely/nothing
! garble build -o=out ./standalone ! garble build -o=out ./standalone
stderr '^GOPRIVATE="match-absolutely/nothing" does not match any packages to be built$' stderr '^GOGARBLE="match-absolutely/nothing" does not match any packages to be built$'
env GOPRIVATE=test/main/imported env GOGARBLE=test/main/imported
garble build ./importer garble build ./importer
# Obfuscated packages which import non-obfuscated std packages. # Obfuscated packages which import non-obfuscated std packages.
# Some of the imported std packages use "import maps" due to vendoring, # Some of the imported std packages use "import maps" due to vendoring,
# and a past bug made this case fail for "garble build". # and a past bug made this case fail for "garble build".
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build -o=out ./stdimporter garble build -o=out ./stdimporter
[short] stop # rebuilding std is slow [short] stop # rebuilding std is slow
env GOPRIVATE='*' env GOGARBLE='*'
# Try garbling all of std, given some std packages. # Try garbling all of std, given some std packages.
# No need for a main package here; building the std packages directly works the # No need for a main package here; building the std packages directly works the

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build garble build
exec ./main exec ./main

@ -1,5 +1,5 @@
# Note that this is the only test with a module where we rely on the detection # Note that this is the only test with a module where we rely on the detection
# of GOPRIVATE. # of GOGARBLE.
# Also note that, since this is the only test using "real" external modules # Also note that, since this is the only test using "real" external modules
# fetched via GOPROXY, go.mod and go.sum should declare the dependencies. # fetched via GOPROXY, go.mod and go.sum should declare the dependencies.

@ -1,5 +1,5 @@
# Note the proper domain, since the dot adds an edge case. # Note the proper domain, since the dot adds an edge case.
env GOPRIVATE=domain.test/main env GOGARBLE=domain.test/main
env LDFLAGS='-X=main.unexportedVersion=v1.0.0 -X=domain.test/main/imported.ExportedVar=replaced -X=domain.test/missing/path.missingVar=value' env LDFLAGS='-X=main.unexportedVersion=v1.0.0 -X=domain.test/main/imported.ExportedVar=replaced -X=domain.test/missing/path.missingVar=value'

@ -1,4 +1,4 @@
env GOPRIVATE=test/main,big.chungus/meme env GOGARBLE=test/main,big.chungus/meme
garble build garble build
exec ./main exec ./main

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble -literals build garble -literals build
exec ./main$exe exec ./main$exe
@ -51,7 +51,7 @@ grep '^\s+type \w+ func\(byte\) \w+$' debug1/test/main/extra_literals.go
# Finally, sanity check that we can build all of std with -literals. # Finally, sanity check that we can build all of std with -literals.
# Analogous to goprivate.txt. # Analogous to goprivate.txt.
env GOPRIVATE='*' env GOGARBLE='*'
garble -literals build std garble -literals build std
-- go.mod -- -- go.mod --

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
[exec:git] exec git init -q [exec:git] exec git init -q
[exec:git] exec git config user.name "name" [exec:git] exec git config user.name "name"

@ -2,7 +2,7 @@ skip # TODO: get plugins working properly. See issue #87
[windows] skip 'Go plugins are not supported on Windows' [windows] skip 'Go plugins are not supported on Windows'
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build -buildmode=plugin ./plugin garble build -buildmode=plugin ./plugin
binsubstr plugin.so 'PublicVar' 'PublicFunc' binsubstr plugin.so 'PublicVar' 'PublicFunc'

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build garble build
exec ./main exec ./main

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
garble build garble build
exec ./main exec ./main

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
# Unknown build flags should result in errors. # Unknown build flags should result in errors.
! garble reverse -badflag=foo . ! garble reverse -badflag=foo .

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
env SEED1=OQg9kACEECQ env SEED1=OQg9kACEECQ
env SEED2=NruiDmVz6/s env SEED2=NruiDmVz6/s

@ -1,4 +1,4 @@
env GOPRIVATE='test/main,private.source' env GOGARBLE='test/main,private.source'
garble build garble build
exec ./main$exe exec ./main$exe

@ -1,4 +1,4 @@
env GOPRIVATE=test/main env GOGARBLE=test/main
# Tiny mode # Tiny mode
garble -tiny build garble -tiny build

Loading…
Cancel
Save