package main import ( "encoding/base64" "encoding/json" "flag" "fmt" "io/ioutil" "log" "os" "strings" "time" "github.com/cloudflare/redoctober/client" "github.com/cloudflare/redoctober/cmd/ro/gopass" "github.com/cloudflare/redoctober/core" "github.com/cloudflare/redoctober/order" ) var action, user, pswd, userEnv, pswdEnv, server, caPath string var owners, lefters, righters, inPath, labels, outPath, outEnv string var uses, minUsers int var duration, users string var pollInterval time.Duration 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"}, "re-encrypt": command{Run: runReEncrypt, Desc: "re-encrypt a file"}, "order": command{Run: runOrder, Desc: "place an order for delegations"}, "owners": command{Run: runOwner, Desc: "show owners list"}, "status": command{Run: runStatus, Desc: "show Red October persistent delegation state"}, } 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.IntVar(&minUsers, "minUsers", 2, "minimum number of delegations") flag.StringVar(&duration, "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") flag.DurationVar(&pollInterval, "poll-interval", time.Second, "interval for polling an outstanding order (set 0 to disable polling)") } 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: duration, Users: processCSL(users), Labels: processCSL(labels), } resp, err := roServer.Delegate(req) processError(err) fmt.Println(resp.Status) } // TODO: summary response needs better formatting 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, Minimum: minUsers, Owners: processCSL(owners), LeftOwners: processCSL(lefters), RightOwners: processCSL(righters), Labels: processCSL(labels), Data: inBytes, } resp, err := roServer.Encrypt(req) processError(err) if resp.Status != "ok" { log.Fatal("response status error:", resp.Status) return } fmt.Println("Response Status:", resp.Status) outBytes := []byte(base64.StdEncoding.EncodeToString(resp.Response)) ioutil.WriteFile(outPath, outBytes, 0644) } func runReEncrypt() { 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.ReEncryptRequest{ Name: user, Password: pswd, Owners: processCSL(owners), Minimum: minUsers, LeftOwners: processCSL(lefters), RightOwners: processCSL(righters), Labels: processCSL(labels), Data: encBytes, } resp, err := roServer.ReEncrypt(req) processError(err) if resp.Status != "ok" { log.Fatal("response status error:", resp.Status) return } 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) if resp.Status != "ok" { log.Fatal("response status error:", resp.Status) return } fmt.Println("Response Status:", resp.Status) var msg core.DecryptWithDelegates err = json.Unmarshal(resp.Response, &msg) processError(err) fmt.Println("Secure:", msg.Secure) fmt.Println("Delegates:", msg.Delegates) ioutil.WriteFile(outPath, msg.Data, 0644) } func runOrder() { req := core.OrderRequest{ Name: user, Password: pswd, Uses: uses, Duration: duration, Labels: processCSL(labels), Users: processCSL(users), } resp, err := roServer.Order(req) processError(err) var o order.Order err = json.Unmarshal(resp.Response, &o) processError(err) if pollInterval > 0 { for o.Delegated < 2 { time.Sleep(pollInterval) resp, err = roServer.OrderInfo(core.OrderInfoRequest{Name: user, Password: pswd, OrderNum: o.Num}) processError(err) err = json.Unmarshal(resp.Response, &o) processError(err) } } fmt.Println(resp.Status) } func runOwner() { inBytes, err := ioutil.ReadFile(inPath) processError(err) req := core.OwnersRequest{ Data: inBytes, } resp, err := roServer.Owners(req) processError(err) fmt.Println(resp.Status) fmt.Println(resp) } func runStatus() { req := core.StatusRequest{ Name: user, Password: pswd, } resp, err := roServer.Status(req) processError(err) fmt.Println(resp.Status) fmt.Println(resp) } 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() } }