22
cmd/blackbox/blackbox.go
Normal file
22
cmd/blackbox/blackbox.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/crypters"
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/crypters/_all"
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/vcs"
|
||||
_ "github.com/StackExchange/blackbox/v2/pkg/vcs/_all"
|
||||
)
|
||||
|
||||
var dryRun bool
|
||||
|
||||
func main() {
|
||||
app := flags()
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
226
cmd/blackbox/cli.go
Normal file
226
cmd/blackbox/cli.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package main
|
||||
|
||||
// cli.go -- Create urfave/cli datastructures and apply them.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func flags() *cli.App {
|
||||
app := cli.NewApp()
|
||||
app.Version = "2.0.0"
|
||||
app.Usage = "Maintain encrypted files in a VCS (Git, Hg, Svn)"
|
||||
|
||||
defUmask := syscall.Umask(0)
|
||||
syscall.Umask(defUmask)
|
||||
defUmaskS := fmt.Sprintf("%04o", defUmask)
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
// &cli.BoolFlag{
|
||||
// Name: "dry-run",
|
||||
// Aliases: []string{"n"},
|
||||
// Usage: "show what would have been done",
|
||||
// Destination: &dryRun,
|
||||
// },
|
||||
&cli.StringFlag{
|
||||
Name: "vcs",
|
||||
Usage: "Use this VCS (GIT, NONE) rather than autodetect",
|
||||
EnvVars: []string{"BLACKBOX_VCS"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "crypto",
|
||||
Usage: "Crypto back-end plugin",
|
||||
Value: "GnuPG",
|
||||
EnvVars: []string{"BLACKBOX_CRYPTO"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "Path to config",
|
||||
//Value: ".blackbox",
|
||||
EnvVars: []string{"BLACKBOX_CONFIGDIR", "BLACKBOXDATA"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "team",
|
||||
Usage: "Use .blackbox-$TEAM as the configdir",
|
||||
EnvVars: []string{"BLACKBOX_TEAM"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "editor",
|
||||
Usage: "editor to use",
|
||||
Value: "vi",
|
||||
EnvVars: []string{"EDITOR", "BLACKBOX_EDITOR"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "umask",
|
||||
Usage: "umask to set when decrypting",
|
||||
Value: defUmaskS,
|
||||
EnvVars: []string{"BLACKBOX_UMASK", "DECRYPT_UMASK"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "Show debug output",
|
||||
EnvVars: []string{"BLACKBOX_DEBUG"},
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []*cli.Command{
|
||||
|
||||
// List items in the order they appear in the help menu.
|
||||
|
||||
{
|
||||
Name: "decrypt",
|
||||
Aliases: []string{"de", "start"},
|
||||
Usage: "Decrypt file(s)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "all", Usage: "All registered files"},
|
||||
&cli.BoolFlag{Name: "agentcheck", Usage: "Do not check for gpg-agent when using --all"},
|
||||
&cli.StringFlag{Name: "group", Usage: "Set group ownership"},
|
||||
&cli.BoolFlag{Name: "overwrite", Usage: "Overwrite plaintext if it exists"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdDecrypt(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "encrypt",
|
||||
Aliases: []string{"en", "end"},
|
||||
Usage: "Encrypts file(s)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "shred", Usage: "Remove plaintext afterwords"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdEncrypt(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "edit",
|
||||
Aliases: []string{"vi"},
|
||||
Usage: "Runs $EDITOR on file(s) (decrypt if needed)",
|
||||
Action: func(c *cli.Context) error { return cmdEdit(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "cat",
|
||||
Usage: "Output plaintext to stderr (decrypt if needed)",
|
||||
Action: func(c *cli.Context) error { return cmdCat(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "diff",
|
||||
Usage: "Diffs against encrypted version",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "all", Usage: "all files"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdDiff(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "init",
|
||||
Category: "ADMINISTRATIVE",
|
||||
Usage: "Initialized blackbox for this repo",
|
||||
Action: func(c *cli.Context) error { return cmdInit(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "admin",
|
||||
Category: "ADMINISTRATIVE",
|
||||
Usage: "Add/list/remove administrators",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "Adds admin(s)",
|
||||
Action: func(c *cli.Context) error { return cmdAdminAdd(c) },
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "Lists admins",
|
||||
Action: func(c *cli.Context) error { return cmdAdminList(c) },
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "Remove admin(s)",
|
||||
Action: func(c *cli.Context) error { return cmdAdminRemove(c) },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "file",
|
||||
Category: "ADMINISTRATIVE",
|
||||
Usage: "Add/list/remove files from the registry",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "Registers file with the system",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "shred", Usage: "Remove plaintext afterwords"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdFileAdd(c) },
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "Lists the registered files",
|
||||
Action: func(c *cli.Context) error { return cmdFileList(c) },
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "Deregister file from the system",
|
||||
Action: func(c *cli.Context) error { return cmdFileRemove(c) },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "info",
|
||||
Category: "DEBUG",
|
||||
Usage: "Report what we know about this repo",
|
||||
Action: func(c *cli.Context) error { return cmdInfo(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "shred",
|
||||
Usage: "Shred files, or --all for all registered files",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "all", Usage: "All registered files"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdShred(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "status",
|
||||
Category: "ADMINISTRATIVE",
|
||||
Usage: "Print status of files",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "name-only", Usage: "Show only names of the files"},
|
||||
&cli.BoolFlag{Name: "all", Usage: "All registered files"},
|
||||
&cli.StringFlag{Name: "type", Usage: "only list if status matching this string"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdStatus(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "reencrypt",
|
||||
Usage: "Decrypt then re-encrypt files (erases any plaintext)",
|
||||
Category: "ADMINISTRATIVE",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{Name: "all", Usage: "All registered files"},
|
||||
&cli.BoolFlag{Name: "overwrite", Usage: "Overwrite plaintext if it exists"},
|
||||
&cli.BoolFlag{Name: "agentcheck", Usage: "Do not check for gpg-agent when using --all"},
|
||||
},
|
||||
Action: func(c *cli.Context) error { return cmdReencrypt(c) },
|
||||
},
|
||||
|
||||
{
|
||||
Name: "testing_init",
|
||||
Usage: "For use with integration test",
|
||||
Category: "INTEGRATION TEST",
|
||||
Action: func(c *cli.Context) error { return testingInit(c) },
|
||||
},
|
||||
|
||||
//
|
||||
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
296
cmd/blackbox/drive.go
Normal file
296
cmd/blackbox/drive.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package main
|
||||
|
||||
// Now that cli.go has processed the flags, validate there are no
|
||||
// conflicts and drive to the business logic.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/StackExchange/blackbox/v2/pkg/bblog"
|
||||
"github.com/StackExchange/blackbox/v2/pkg/box"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var logErr *log.Logger
|
||||
|
||||
func init() {
|
||||
if logErr == nil {
|
||||
logErr = log.New(os.Stderr, "", 0)
|
||||
}
|
||||
}
|
||||
|
||||
func allOrSomeFiles(c *cli.Context) error {
|
||||
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
|
||||
}
|
||||
|
||||
const roError = `This command is disabled due to --config flag being used.
|
||||
We can not determine if the flag's value is in or out of the repo, and
|
||||
Blackbox can only work on one repo at a time. If the value is inside the
|
||||
repo, and you'd like to suggest an algorithm that would let us determine that
|
||||
automatically, please file a bug. We'd love to have this work better. In the
|
||||
meanwhile, run this command without the --config flag, perhaps after cd'ing
|
||||
to the base of the repo.`
|
||||
|
||||
// Keep these functions in alphabetical order.
|
||||
|
||||
func cmdAdminAdd(c *cli.Context) error {
|
||||
if c.NArg() == 0 || c.NArg() > 2 {
|
||||
return fmt.Errorf(
|
||||
"Must specify one admin's GnuPG user-id (i.e. email address) and optionally the directory of the pubkey data (default ~/.GnuPG)")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
if bx.ConfigRO {
|
||||
return fmt.Errorf(roError)
|
||||
}
|
||||
err := bx.AdminAdd(c.Args().Get(0), c.Args().Get(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdAdminList(c *cli.Context) error {
|
||||
if c.Args().Present() {
|
||||
return fmt.Errorf("This command takes zero arguments")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.AdminList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdAdminRemove(c *cli.Context) error {
|
||||
if !c.Args().Present() {
|
||||
return fmt.Errorf("Must specify at least one admin's GnuPG user-id (i.e. email address)")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
if bx.ConfigRO {
|
||||
return fmt.Errorf(roError)
|
||||
}
|
||||
err := bx.AdminRemove(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdCat(c *cli.Context) error {
|
||||
if !c.Args().Present() {
|
||||
return fmt.Errorf("Must specify at least one file name")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Cat(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdDecrypt(c *cli.Context) error {
|
||||
if err := allOrSomeFiles(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
err := bx.Decrypt(c.Args().Slice(),
|
||||
c.Bool("overwrite"),
|
||||
pauseNeeded,
|
||||
c.String("group"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdDiff(c *cli.Context) error {
|
||||
if err := allOrSomeFiles(c); err != nil {
|
||||
return err
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Diff(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdEdit(c *cli.Context) error {
|
||||
if !c.Args().Present() {
|
||||
return fmt.Errorf("Must specify at least one file name")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Edit(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdEncrypt(c *cli.Context) error {
|
||||
if err := allOrSomeFiles(c); err != nil {
|
||||
return err
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Encrypt(c.Args().Slice(), c.Bool("shred"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdFileAdd(c *cli.Context) error {
|
||||
if !c.Args().Present() {
|
||||
return fmt.Errorf("Must specify at least one file name")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
if bx.ConfigRO {
|
||||
return fmt.Errorf(roError)
|
||||
}
|
||||
err := bx.FileAdd(c.Args().Slice(), c.Bool("shred"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdFileList(c *cli.Context) error {
|
||||
if c.Args().Present() {
|
||||
return fmt.Errorf("This command takes zero arguments")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.FileList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdFileRemove(c *cli.Context) error {
|
||||
if !c.Args().Present() {
|
||||
return fmt.Errorf("Must specify at least one file name")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
if bx.ConfigRO {
|
||||
return fmt.Errorf(roError)
|
||||
}
|
||||
err := bx.FileRemove(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdInfo(c *cli.Context) error {
|
||||
if c.Args().Present() {
|
||||
return fmt.Errorf("This command takes zero arguments")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdInit(c *cli.Context) error {
|
||||
if c.Args().Len() > 1 {
|
||||
return fmt.Errorf("This command takes one or two arguments")
|
||||
}
|
||||
bx := box.NewUninitialized(c)
|
||||
if bx.ConfigRO {
|
||||
return fmt.Errorf(roError)
|
||||
}
|
||||
err := bx.Init(c.Args().First(), c.String("vcs"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdReencrypt(c *cli.Context) error {
|
||||
if err := allOrSomeFiles(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
err := bx.Reencrypt(c.Args().Slice(),
|
||||
c.Bool("overwrite"),
|
||||
pauseNeeded,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdShred(c *cli.Context) error {
|
||||
if err := allOrSomeFiles(c); err != nil {
|
||||
return err
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Shred(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
func cmdStatus(c *cli.Context) error {
|
||||
if c.Bool("all") && c.Args().Present() {
|
||||
return fmt.Errorf("Can not specify filenames and --all")
|
||||
}
|
||||
bx := box.NewFromFlags(c)
|
||||
err := bx.Status(c.Args().Slice(), c.Bool("name-only"), c.String("type"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
|
||||
// These are "secret" commands used by the integration tests.
|
||||
|
||||
func testingInit(c *cli.Context) error {
|
||||
if c.Args().Present() {
|
||||
return fmt.Errorf("No args required")
|
||||
}
|
||||
|
||||
logDebug := bblog.GetDebug(c.Bool("debug"))
|
||||
logDebug.Printf(
|
||||
"c.String(vcs) reports %q\n",
|
||||
c.String("vcs"),
|
||||
)
|
||||
bx := box.NewForTestingInit(c.String("vcs"))
|
||||
if bx.ConfigRO {
|
||||
return fmt.Errorf(roError)
|
||||
}
|
||||
err := bx.TestingInitRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bx.Vcs.FlushCommits()
|
||||
}
|
||||
Reference in New Issue
Block a user