Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement blackbox in Golang #250

Merged
merged 76 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
ff792cf
vendor github.com/pkg/errors vendor/github.com/urfave/cli
tlimoncelli Apr 21, 2018
57702a1
New command: blackbox: Parses subcommands and calls bash scripts.
tlimoncelli Apr 21, 2018
3a5e4b6
Implement status and nlist commands
tlimoncelli Apr 22, 2018
5681903
Add "blackbox admin nlist" command.
tlimoncelli Apr 22, 2018
d393d7e
First draft of ndecrypt
tlimoncelli Apr 22, 2018
a6b9c17
Merge branch 'master' into golang
tlimoncelli Jul 3, 2018
3cc7e46
Partial commit
tlimoncelli Sep 10, 2018
e1e6dee
Merge branch 'master' into golang
tlimoncelli Sep 10, 2018
b62ec37
Merge branch 'master' into golang
tlimoncelli Dec 4, 2018
5671b71
Merge branch 'master' into golang
tlimoncelli Dec 9, 2018
ffde3ae
Restructure
tlimoncelli Dec 9, 2018
a5cd829
wip!
tlimoncelli Dec 18, 2018
a47f038
Merge branch 'master' into golang
tlimoncelli Mar 3, 2019
36e1774
Merge branch 'master' into golang
Apr 19, 2020
1fb5701
NEW: DESIGN.md
Apr 19, 2020
b3e052b
Merge branch 'master' into golang
tlimoncelli Jun 6, 2020
c9bd587
tmp
tlimoncelli Jun 6, 2020
a630c6b
flags.go and parse.go complete
tlimoncelli Jun 6, 2020
0bdffb3
Draft
tlimoncelli Jun 6, 2020
a6ec716
Box discovers admins and files.
tlimoncelli Jun 6, 2020
50fa2e5
Snapshot
tlimoncelli Jun 7, 2020
d7bdd49
Fixed the signature mismatch problem!!!
tlimoncelli Jun 7, 2020
ac8105c
linting
tlimoncelli Jun 7, 2020
4979113
Implement Discover()
tlimoncelli Jun 7, 2020
bc5dab7
Implement: files list
tlimoncelli Jun 7, 2020
2425c0f
"blackbox status" works.
tlimoncelli Jun 7, 2020
bf1f1e2
Unvendor
tlimoncelli Jun 7, 2020
148aa3d
Unvendor
tlimoncelli Jun 7, 2020
6c260d1
Cleanup
tlimoncelli Jun 7, 2020
126f60f
cleanup
tlimoncelli Jun 7, 2020
edad8a8
snapshot
tlimoncelli Jun 7, 2020
66eeea2
snapshot!
tlimoncelli Jun 7, 2020
9998758
Rename pkgs
tlimoncelli Jun 7, 2020
8c329b1
linting
tlimoncelli Jun 7, 2020
56665df
Implement decrypt --askagent and --umask
tlimoncelli Jun 8, 2020
f860c73
Random fixes
tlimoncelli Jun 8, 2020
b3aa045
snapshot
tlimoncelli Jun 8, 2020
df2bb78
integration test framework works
tlimoncelli Jun 9, 2020
164376f
Integration tests work all the way through "init".
tlimoncelli Jun 10, 2020
2c8400c
init completed
tlimoncelli Jun 11, 2020
b544a02
Mostly working
tlimoncelli Jun 12, 2020
e831a5a
logging works
tlimoncelli Jun 12, 2020
0a0068d
fix logging
tlimoncelli Jun 12, 2020
a189a4a
wip!
tlimoncelli Jun 14, 2020
e0e61f5
wip!
tlimoncelli Jun 15, 2020
7276fae
Now working: init, admin add, file add, encrypt, decrypt, shred
tlimoncelli Jun 15, 2020
b17a11a
Cat works!
tlimoncelli Jun 16, 2020
a47bc5d
cat and other commands now work. shell commands replaced
tlimoncelli Jun 16, 2020
fa2a643
create binv2
tlimoncelli Jun 19, 2020
0518006
integration tests working
tlimoncelli Jun 19, 2020
b963da0
Now works on CentOS 7
TomOnTime Jun 19, 2020
b3e9e52
aRough out Docs
tlimoncelli Jun 23, 2020
eccea8d
fix comment
tlimoncelli Jul 2, 2020
ac66afd
linting
tlimoncelli Jul 2, 2020
bea3af3
Refactor how NeedsCommit is done
tlimoncelli Jul 2, 2020
4f79084
add missing files
tlimoncelli Jul 3, 2020
56ecdc8
git commit messages are more pretty
tlimoncelli Jul 3, 2020
433b357
cleanup
tlimoncelli Jul 3, 2020
c0d1883
Fixing commits
tlimoncelli Jul 3, 2020
0eb9fd1
bx.* dirs are now relative
tlimoncelli Jul 4, 2020
afcec7d
linting
tlimoncelli Jul 4, 2020
6b8deb4
Clean up redact
tlimoncelli Jul 4, 2020
7ab79e0
Fix bug: filenames with spaces dont work
tlimoncelli Jul 4, 2020
6ff8d0d
macos doesn't need gpg-agent
tlimoncelli Jul 4, 2020
68a17b5
Fix umask display. All --all commands now work from any directory
tlimoncelli Jul 5, 2020
fdb2559
Fix --debug. Fix --group
tlimoncelli Jul 5, 2020
fa53022
Fix links
tlimoncelli Jul 5, 2020
b3b54fa
Refactor makesafe
tlimoncelli Jul 5, 2020
259ba65
cleanig up code
tlimoncelli Jul 6, 2020
b6d4fee
linting
tlimoncelli Jul 6, 2020
f886ea5
mDocs
tlimoncelli Jul 14, 2020
ccc2439
fix reencrypt commit messages
tlimoncelli Jul 17, 2020
92e791a
Fix
tlimoncelli Jul 18, 2020
9e088db
Rewrite shred detector
tlimoncelli Jul 18, 2020
4c47af8
do some integration tests from a funny subdir
tlimoncelli Jul 18, 2020
053b8a6
Rewrite shred detector
tlimoncelli Jul 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement decrypt --askagent and --umask
  • Loading branch information
tlimoncelli committed Jun 8, 2020
commit 56665df1ff59c0396acf1d2621f1738e50e35a5f
33 changes: 33 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,36 @@ They layers look like this:
| Interfaces | `pkg/*` | Interfaces and their implementations |

At least that's the goal. We'll see how well we can achieve this.


Version 2.0
===========

Software architecture.

We try to keep the command-line parsing separate from the business
logic and all plug-ins. This keeps things clean and easy to refactor.
In fact layer 2 could be used as a stand-alone module for projects
that want to embed blackbox actions.

Layer 1: The command itself

* cmd/blackbox/blackbox.go -- main() not much more
* cmd/blackbox/cli.go -- Set up and call the ufave/cli flag parser
* cmd/blackbox/drive.go -- Check # of arguments, conflicting flags, and then call the businss logic layer

Layer 2: The business logic

* pkg/box/box.go -- The interface to accessing .blackbox (admins, files, etc.)
* pkg/box/verbs.go -- Verbs called by Layer 1. Just the verbs
* pkg/box/boxutils.go -- Functions needed by the verbs

Layer 3: The plug-ins

* pkg/vcs/... -- Plug-ins for Git, (Mercurial, Subversion, Perforce,) and None
* pkg/crypters/... -- Plug-ins for PGP access: GnuPG, (go-openpgp, others in the future)

Layer 4: Support functions for use by Layer 3

* pkg/bbutil/filestats.go -- File manipulations
* pkg/bbutil/runbash.go -- Safely run external Linux commands
19 changes: 14 additions & 5 deletions cmd/blackbox/flags.go → cmd/blackbox/cli.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package main

// cli.go -- Create urfave/cli datastructures and apply them.

import (
"github.com/urfave/cli/v2"
)
Expand All @@ -17,9 +19,16 @@ func flags() *cli.App {
// Destination: &dryRun,
// },
&cli.StringFlag{
Name: "crypto",
Usage: "Crypto back-end plugin",
Value: "GnuPG",
Name: "crypto",
Usage: "Crypto back-end plugin",
Value: "GnuPG",
EnvVars: []string{"BLACKBOX_FLAG_CRYPTO"},
},
&cli.IntFlag{
Name: "umask",
Usage: "umask to set when decrypting",
Value: 0o027,
EnvVars: []string{"BLACKBOX_FLAG_UMASK", "DECRYPT_UMASK"},
},
}

Expand All @@ -33,9 +42,9 @@ func flags() *cli.App {
Usage: "Decrypt file(s)",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "all", Usage: "All registered files"},
&cli.BoolFlag{Name: "bulk", Usage: "Do not prompt to start gpg-agent"},
&cli.BoolFlag{Name: "agentcheck", Usage: "Do not check for gpg-agent when using --all"},
&cli.StringFlag{Name: "group", Usage: "Set group ownership"},
&cli.StringFlag{Name: "overwrite", Usage: "Overwrite plaintext if it exists"},
&cli.BoolFlag{Name: "overwrite", Usage: "Overwrite plaintext if it exists"},
},
Action: func(c *cli.Context) error { return cmdDecrypt(c) },
},
Expand Down
52 changes: 15 additions & 37 deletions cmd/blackbox/parse.go → cmd/blackbox/drive.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

// Processes the flags and arguments and calls the appropriate
// business logic.
// Now that cli.go has processed the flags, validate there are no
// conflicts and drive to the business logic.

import (
"fmt"
Expand All @@ -21,14 +21,11 @@ func init() {
}

func allOrSomeFiles(c *cli.Context) error {
if c.Bool("all") {
if c.Args().Present() {
return fmt.Errorf("Can not specify filenames and --all")
}
} else {
if !c.Args().Present() {
return fmt.Errorf("Must specify at least one file name or --all")
}
if c.Bool("all") && c.Args().Present() {
return fmt.Errorf("Can not specify filenames and --all")
}
if (!c.Args().Present()) && (!c.Bool("all")) {
return fmt.Errorf("Must specify at least one file name or --all")
}
return nil
}
Expand Down Expand Up @@ -72,14 +69,18 @@ func cmdDecrypt(c *cli.Context) error {
if err := allOrSomeFiles(c); err != nil {
return err
}
bulk := false
if c.Bool("all") {
bulk = c.Bool("bulk") // Only applies to --all

// The default for --agentcheck is off normally, and on when using --all.
pauseNeeded := c.Bool("all")
// If the user used the flag, abide by it.
if c.IsSet("agentcheck") {
pauseNeeded = c.Bool("agentcheck")
}

bx := box.NewFromFlags(c)
return bx.Decrypt(c.Args().Slice(),
c.Bool("overwrite"),
bulk,
pauseNeeded,
c.String("group"),
)
}
Expand Down Expand Up @@ -177,29 +178,6 @@ func cmdStatus(c *cli.Context) error {
return bx.Status(c.Args().Slice(), c.Bool("name-only"), c.String("type"))
}

// func cmdDecrypt(allFiles bool, filenames []string, group string) error {
// bbu, err := bbutil.New()
// if err != nil {
// return err
// }
//
// // prepare_keychain
//
// fnames, valid, err := bbu.FileIterator(allFiles, filenames)
// if err != nil {
// return errors.Wrap(err, "decrypt")
// }
// for i, filename := range fnames {
// if valid[i] {
// bbu.DecryptFile(filename, group, true)
// } else {
// fmt.Fprintf(os.Stderr, "SKIPPING: %q\n", filename)
// }
// }
//
// return nil
// }

// func cmdRegList(c *cli.Context) error {
// if len(c.Args()) != 0 {
// fmt.Fprintf(c.App.Writer, "ERROR: Command does not take any arguments\n")
Expand Down
2 changes: 1 addition & 1 deletion models/crypters.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package models
// Crypter is gpg binaries, go-opengpg, etc.
type Crypter interface {
// Decrypt name+".gpg", possibly overwriting name.
Decrypt(filename string, overwrite bool) error
Decrypt(filename string, overwrite bool, umask int) error
}
5 changes: 4 additions & 1 deletion pkg/bbutil/runbash.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ func RunBash(command string, args ...string) error {
log.Fatal(err)
}
err = cmd.Wait()
return fmt.Errorf("run_bash: %w", err)
if err != nil {
return fmt.Errorf("run_bash: %w", err)
}
return nil
}
10 changes: 6 additions & 4 deletions pkg/box/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ type Box struct {
RepoBaseDir string // Base directory of the repo.
ConfigDir string // Path to the .blackbox config directory.
//
Admins []string // If non-empty, the list of admins.
Files []string // If non-empty, the list of files.
FilesSet map[string]bool // If non-nil, a set of Files.
IsRegistered map[string]bool //
Admins []string // If non-empty, the list of admins.
Files []string // If non-empty, the list of files.
FilesSet map[string]bool // If non-nil, a set of Files.
//
Vcs vcs.Vcs // Interface access to the VCS.
VcsName string // name of the VCS
Crypter crypters.Crypter // Inteface access to GPG.
CrypterName string // Name of the crypter in use.
//
Umask int // umask to set when decrypting
}

// StatusMode is a type of query.
Expand Down Expand Up @@ -64,6 +65,7 @@ func NewFromFlags(c *cli.Context) *Box {
}
bx.RepoBaseDir = repoBaseDir
bx.ConfigDir = configDir
bx.Umask = c.Int("umask")

// Discover which kind of VCS is in use.
var h vcs.Vcs
Expand Down
18 changes: 18 additions & 0 deletions pkg/box/boxutils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package box

import (
"bufio"
"fmt"
"os"
"os/user"
Expand Down Expand Up @@ -114,3 +115,20 @@ func parseGroup(userinput string) (int, error) {
// Give up.
return -1, err
}

func gpgAgentNotice() {
// Is gpg-agent configured?
if os.Getenv("GPG_AGENT_INFO") != "" {
return
}

// TODO(tlim): v1 verifies that "gpg-agent --version" outputs a version
// string that is 2.1.0 or higher. It seems that 1.x is incompatible.

fmt.Println("WARNING: You probably want to run gpg-agent as")
fmt.Println("you will be asked for your passphrase many times.")
fmt.Println("Example: $ eval $(gpg-agent --daemon)")
fmt.Print("Press CTRL-C now to stop. ENTER to continue: ")
input := bufio.NewScanner(os.Stdin)
input.Scan()
}
18 changes: 13 additions & 5 deletions pkg/box/verbs.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,39 @@ func (bx *Box) Decrypt(names []string, overwrite bool, bulkpause bool, setgroup
}
}

if bulkpause {
gpgAgentNotice()
}

if len(names) == 0 {
names = bx.Files
}
for _, name := range names {
fmt.Printf("========== DECRYPTING %q\n", name)
if !bx.FilesSet[name] {
logErr.Printf("Skipping %q: File not registered with Blackbox", name)
}
if (!overwrite) && bbutil.FileExistsOrProblem(name) {
logErr.Printf("Skipping %q: Will not overwrite existing file", name)
continue
}
if bx.Crypter == nil {
fmt.Printf("NO CRYPTER!!!\n")
}
err := bx.Crypter.Decrypt(name, overwrite)

// TODO(tlim) v1 detects zero-length files and removes them, even
// if overwrite is disabled. I don't think anyone has ever used that
// feature. That said, we could immplement that here.

err := bx.Crypter.Decrypt(name, overwrite, bx.Umask)
if err != nil {
logErr.Printf("%q: %v", name, err)
continue
}

if groupchange {
os.Chown(name, -1, gid)
}
}

return fmt.Errorf("NOT IMPLEMENTED: Decrypt")
return nil
}

// Diff ...
Expand Down
41 changes: 36 additions & 5 deletions pkg/crypters/gnupg/gnupg.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package gnupg

import (
"fmt"
"os"
"os/exec"

"github.com/StackExchange/blackbox/v2/pkg/bbutil"
"github.com/StackExchange/blackbox/v2/pkg/crypters"
)

Expand All @@ -12,14 +14,43 @@ func init() {

// CrypterHandle is the handle
type CrypterHandle struct {
GPGCmd string // "gpg2" or "gpg"
}

func registerNew() (crypters.Crypter, error) {
return &CrypterHandle{}, nil

crypt := &CrypterHandle{}

// Which binary to use?
path, err := exec.LookPath("gpg2")
if err != nil {
path, err = exec.LookPath("gpg")
if err != nil {
path = "gpg2"
}
}
crypt.GPGCmd = path

return crypt, nil
}

// Decrypt decrypts a file, possibly overwriting the plaintext.
func (crypt CrypterHandle) Decrypt(name string, overwrite bool) error {
fmt.Printf("WOULD decrypt %v (overwrite=%v)\n", name, overwrite)
return nil
func (crypt CrypterHandle) Decrypt(name string, overwrite bool, umask int) error {

if overwrite {
_ = os.Remove(name)
}

crypt.prepareKeyChain()

//oldumask := syscall.Umask(umask)
err := bbutil.RunBash(crypt.GPGCmd,
"--use-agent",
"-q",
"--decrypt",
"-o", name,
name+".gpg",
)
//syscall.Umask(oldumask)
return err
}