Add a CLI client

* vendored code.google.com/p/gopass as package cmd/ro/gopass
This commit is contained in:
Zi Lin
2015-07-30 10:28:34 -07:00
parent c3a9c83f9c
commit 4de443946c
3 changed files with 332 additions and 0 deletions

18
cmd/ro/README.md Normal file
View File

@@ -0,0 +1,18 @@
# ro
This is a command line based Red October client. It is still under development.
## Usage
See
$ ro -h
## Example
Assume username and password is stored at RO\_USER and RO\_PASS env variables.
1. To see the current user and delegation summary:
$ ro -server HOSTNAME:PORT summary
2. To decrypt a RO encrypted file:
$ ro -server HOSTNAME:PORT -in FILE -out FILE decrypt

111
cmd/ro/gopass/gopass.go Normal file
View File

@@ -0,0 +1,111 @@
// Author: johnsiilver@gmail.com (John Doak)
/*
gopass is a library for getting hidden input from a terminal.
This library's main use is to allow a user to enter a password at the
command line without having it echoed to the screen.
The libary currently supports unix systems by manipulating stty.
This code is based upon code by RogerV in the golang-nuts thread:
https://groups.google.com/group/golang-nuts/browse_thread/thread/40cc41e9d9fc9247
*/
package gopass
import (
"bufio"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
)
const (
sttyArg0 = "/bin/stty"
exec_cwdir = ""
)
// Tells the terminal to turn echo off.
var sttyArgvEOff []string = []string{"stty", "-echo"}
// Tells the terminal to turn echo on.
var sttyArgvEOn []string = []string{"stty", "echo"}
var ws syscall.WaitStatus = 0
// GetPass gets input hidden from the terminal from a user.
// This is accomplished by turning off terminal echo,
// reading input from the user and finally turning on terminal echo.
// prompt is a string to display before the user's input.
func GetPass(prompt string) (passwd string, err error) {
sig := make(chan os.Signal, 10)
brk := make(chan bool)
// Display the prompt.
fmt.Print(prompt)
// File descriptors for stdin, stdout, and stderr.
fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
// Setup notifications of termination signals to channel sig, create a process to
// watch for these signals so we can turn back on echo if need be.
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT,
syscall.SIGTERM)
go catchSignal(fd, sig, brk)
// Turn off the terminal echo.
pid, err := echoOff(fd)
if err != nil {
return "", err
}
// Turn on the terminal echo and stop listening for signals.
defer close(brk)
defer echoOn(fd)
rd := bufio.NewReader(os.Stdin)
syscall.Wait4(pid, &ws, 0, nil)
line, err := rd.ReadString('\n')
if err == nil {
passwd = strings.TrimSpace(line)
} else {
err = fmt.Errorf("failed during password entry: %s", err)
}
// Carraige return after the user input.
fmt.Println("")
return passwd, err
}
func echoOff(fd []uintptr) (int, error) {
pid, err := syscall.ForkExec(sttyArg0, sttyArgvEOff, &syscall.ProcAttr{Dir: exec_cwdir, Files: fd})
if err != nil {
return 0, fmt.Errorf("failed turning off console echo for password entry:\n\t%s", err)
}
return pid, nil
}
// echoOn turns back on the terminal echo.
func echoOn(fd []uintptr) {
// Turn on the terminal echo.
pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: exec_cwdir, Files: fd})
if e == nil {
syscall.Wait4(pid, &ws, 0, nil)
}
}
// catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn terminal
// echo back on before the program ends. Otherwise the user is left with echo off on
// their terminal.
func catchSignal(fd []uintptr, sig chan os.Signal, brk chan bool) {
select {
case <-sig:
echoOn(fd)
os.Exit(-1)
case <-brk:
}
}

203
cmd/ro/main.go Normal file
View File

@@ -0,0 +1,203 @@
package main
import (
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"github.com/cloudflare/redoctober/client"
"github.com/cloudflare/redoctober/cmd/ro/gopass"
"github.com/cloudflare/redoctober/core"
)
var action, user, pswd, userEnv, pswdEnv, server, caPath string
var owners, lefters, righters, inPath, labels, outPath, outEnv string
var uses int
var time, users string
type command struct {
Run func()
Desc string
}
var roServer *client.RemoteServer
var commandSet = map[string]command{
"create": command{Run: runCreate, Desc: "create a user account"},
"summary": command{Run: runSummary, Desc: "list the user and delegation summary"},
"delegate": command{Run: runDelegate, Desc: "do decryption delegation"},
"encrypt": command{Run: runEncrypt, Desc: "encrypt a file"},
"decrypt": command{Run: runDecrypt, Desc: "decrypt a file"},
}
func registerFlags() {
flag.StringVar(&server, "server", "localhost:8080", "server address")
flag.StringVar(&caPath, "ca", "", "ca file path")
flag.StringVar(&owners, "owners", "", "comma separated owner list")
flag.StringVar(&users, "users", "", "comma separated user list")
flag.IntVar(&uses, "uses", 0, "number of delegated key uses")
flag.StringVar(&time, "time", "0h", "duration of delegated key uses")
flag.StringVar(&lefters, "left", "", "comma separated left owners")
flag.StringVar(&righters, "right", "", "comma separated right owners")
flag.StringVar(&labels, "labels", "", "comma separated labels")
flag.StringVar(&inPath, "in", "", "input data file")
flag.StringVar(&outPath, "out", "", "output data file")
flag.StringVar(&outEnv, "outenv", "", "env variable for output data")
flag.StringVar(&user, "user", "", "username")
flag.StringVar(&pswd, "password", "", "password")
flag.StringVar(&userEnv, "userenv", "RO_USER", "env variable for user name")
flag.StringVar(&pswdEnv, "pswdenv", "RO_PASS", "env variable for user password")
}
func getUserCredentials() {
user = os.Getenv(userEnv)
pswd = os.Getenv(pswdEnv)
if user == "" || pswd == "" {
fmt.Print("Username:")
fmt.Scan(&user)
var err error
pswd, err = gopass.GetPass("Password:")
processError(err)
}
}
func processError(err error) {
if err != nil {
log.Fatal("error:", err)
}
}
func processCSL(s string) []string {
if s == "" {
return nil
}
return strings.Split(s, ",")
}
func runCreate() {
req := core.CreateRequest{
Name: user,
Password: pswd,
}
resp, err := roServer.Create(req)
processError(err)
fmt.Println(resp.Status)
}
func runDelegate() {
req := core.DelegateRequest{
Name: user,
Password: pswd,
Uses: uses,
Time: time,
Users: processCSL(users),
Labels: processCSL(labels),
}
resp, err := roServer.Delegate(req)
processError(err)
fmt.Println(resp.Status)
}
func runSummary() {
req := core.SummaryRequest{
Name: user,
Password: pswd,
}
resp, err := roServer.Summary(req)
processError(err)
fmt.Println(resp)
}
func runEncrypt() {
inBytes, err := ioutil.ReadFile(inPath)
processError(err)
req := core.EncryptRequest{
Name: user,
Password: pswd,
Owners: processCSL(owners),
LeftOwners: processCSL(lefters),
RightOwners: processCSL(righters),
Labels: processCSL(labels),
Data: inBytes,
}
resp, err := roServer.Encrypt(req)
processError(err)
fmt.Println("Response Status:", resp.Status)
outBytes := []byte(base64.StdEncoding.EncodeToString(resp.Response))
ioutil.WriteFile(outPath, outBytes, 0644)
}
func runDecrypt() {
inBytes, err := ioutil.ReadFile(inPath)
processError(err)
// base64 decode the input
encBytes, err := base64.StdEncoding.DecodeString(string(inBytes))
if err != nil {
log.Println("fail to base64 decode the data, proceed with raw data")
encBytes = inBytes
}
req := core.DecryptRequest{
Name: user,
Password: pswd,
Data: encBytes,
}
resp, err := roServer.Decrypt(req)
processError(err)
var msg core.DecryptWithDelegates
err = json.Unmarshal(resp.Response, &msg)
processError(err)
fmt.Println("Response Status:", resp.Status)
fmt.Println("Secure:", msg.Secure)
fmt.Println("Delegates:", msg.Delegates)
ioutil.WriteFile(outPath, msg.Data, 0644)
}
func main() {
flag.Usage = func() {
fmt.Println("Usage: ro [options] subcommand")
fmt.Println("Currently supported subcommands are:")
for key := range commandSet {
fmt.Println("\t", key, ":", commandSet[key].Desc)
}
fmt.Println("Options:")
flag.PrintDefaults()
}
registerFlags()
flag.Parse()
if flag.NArg() != 1 {
flag.Usage()
os.Exit(1)
}
action := flag.Arg(0)
cmd, found := commandSet[action]
if !found {
fmt.Println("Unsupported subcommand:", action)
flag.Usage()
os.Exit(1)
} else {
var err error
roServer, err = client.NewRemoteServer(server, caPath)
processError(err)
getUserCredentials()
cmd.Run()
}
}