mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-07 22:15:34 +00:00
Add a CLI client
* vendored code.google.com/p/gopass as package cmd/ro/gopass
This commit is contained in:
18
cmd/ro/README.md
Normal file
18
cmd/ro/README.md
Normal 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
111
cmd/ro/gopass/gopass.go
Normal 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
203
cmd/ro/main.go
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user