mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-08 07:11:48 +00:00
Working prototype using RO as a remote Signer for SSH authentication
This commit is contained in:
@@ -254,6 +254,22 @@ func (c *RemoteServer) Decrypt(req core.DecryptRequest) (*core.ResponseData, err
|
||||
|
||||
}
|
||||
|
||||
// Decrypt issues an decrypt request to the remote server
|
||||
func (c *RemoteServer) DecryptSign(req core.DecryptSignRequest) (*core.ResponseData, error) {
|
||||
reqBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes, err := c.doAction("decrypt-sign", reqBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalResponseData(respBytes)
|
||||
|
||||
}
|
||||
|
||||
// DecryptIntoData issues an decrypt request to the remote server and extract
|
||||
// the decrypted data from the response
|
||||
func (c *RemoteServer) DecryptIntoData(req core.DecryptRequest) ([]byte, error) {
|
||||
|
||||
102
cmd/ro/main.go
102
cmd/ro/main.go
@@ -6,25 +6,32 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/redoctober/client"
|
||||
"github.com/cloudflare/redoctober/cmd/ro/gopass"
|
||||
"github.com/cloudflare/redoctober/cmd/ro/roagent"
|
||||
"github.com/cloudflare/redoctober/core"
|
||||
"github.com/cloudflare/redoctober/cryptor"
|
||||
"github.com/cloudflare/redoctober/msp"
|
||||
"github.com/cloudflare/redoctober/order"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var action, user, pswd, userEnv, pswdEnv, server, caPath string
|
||||
var action, user, pswd, userEnv, pswdEnv, server, caPath, pubKeyPath string
|
||||
|
||||
var owners, lefters, righters, inPath, labels, outPath, outEnv string
|
||||
var owners, lefters, righters, inPath, labels, usages, outPath, outEnv string
|
||||
|
||||
var uses, minUsers int
|
||||
|
||||
@@ -46,6 +53,8 @@ var commandSet = map[string]command{
|
||||
"delegate": command{Run: runDelegate, Desc: "do decryption delegation"},
|
||||
"encrypt": command{Run: runEncrypt, Desc: "encrypt a file"},
|
||||
"decrypt": command{Run: runDecrypt, Desc: "decrypt a file"},
|
||||
"ssh": command{Run: runSSH, Desc: "a wrapper for SSH using an RO-encrypted private key"},
|
||||
"ssh-agent": command{Run: runSSHAgent, Desc: "act as an SSH agent"},
|
||||
"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"},
|
||||
@@ -65,6 +74,7 @@ func registerFlags() {
|
||||
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(&usages, "usages", "", "comma separated usages")
|
||||
flag.StringVar(&inPath, "in", "", "input data file")
|
||||
flag.StringVar(&outPath, "out", "", "output data file")
|
||||
flag.StringVar(&outEnv, "outenv", "", "env variable for output data")
|
||||
@@ -75,6 +85,7 @@ func registerFlags() {
|
||||
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)")
|
||||
flag.StringVar(&pubKeyPath, "pubkey", "id_rsa.pub", "path to SSH public key")
|
||||
}
|
||||
|
||||
func readLine(prompt string) (line string, err error) {
|
||||
@@ -201,6 +212,7 @@ func runEncrypt() {
|
||||
Password: pswd,
|
||||
Minimum: minUsers,
|
||||
Owners: processCSL(owners),
|
||||
Usages: processCSL(usages),
|
||||
LeftOwners: processCSL(lefters),
|
||||
RightOwners: processCSL(righters),
|
||||
Labels: processCSL(labels),
|
||||
@@ -386,6 +398,86 @@ func runResetPersisted() {
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func runSSHAgent() {
|
||||
inBytes, err := ioutil.ReadFile(inPath)
|
||||
processError(err)
|
||||
|
||||
// base64 decode the input
|
||||
encBytes, err := base64.StdEncoding.DecodeString(string(inBytes))
|
||||
if err != nil {
|
||||
log.Println("failed to base64 decode the data, proceeding with raw data")
|
||||
encBytes = inBytes
|
||||
}
|
||||
|
||||
inBytes, err = ioutil.ReadFile(pubKeyPath)
|
||||
processError(err)
|
||||
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(inBytes)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("failed to parse SSH public key", err)
|
||||
}
|
||||
|
||||
roagent := roagent.NewROAgent(roServer, pubKey, encBytes, user, pswd)
|
||||
|
||||
authSockPath := os.Getenv("SSH_AUTH_SOCK")
|
||||
|
||||
if authSockPath == "" {
|
||||
log.Fatal("SSH_AUTH_SOCK not set")
|
||||
}
|
||||
|
||||
listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: authSockPath, Net: "unix"})
|
||||
if err != nil {
|
||||
log.Fatal("error listening on $SSH_AUTH_SOCK", err)
|
||||
}
|
||||
defer os.Remove(authSockPath)
|
||||
|
||||
conn, err := listener.AcceptUnix()
|
||||
if err != nil {
|
||||
log.Fatal("error accepting socket connection", err)
|
||||
}
|
||||
|
||||
err = agent.ServeAgent(roagent, conn)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatal("error serving socket protocol", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runSSH() {
|
||||
// First pick a path for our socket
|
||||
// TempDir will ensure that the directory is created with the correct permissions
|
||||
dir, err := ioutil.TempDir("", "ro_ssh_")
|
||||
if err != nil {
|
||||
log.Fatal("error getting temporary directory for SSH auth socket ", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
os.Setenv("SSH_AUTH_SOCK", path.Join(dir, "roagent.sock"))
|
||||
go runSSHAgent()
|
||||
|
||||
var sshPath string
|
||||
sshPath, err = exec.LookPath("ssh")
|
||||
if err != nil {
|
||||
log.Fatal("error finding path to ssh binary ", err)
|
||||
}
|
||||
|
||||
var p *os.Process
|
||||
p, err = os.StartProcess(sshPath, flag.Args(),
|
||||
&os.ProcAttr{
|
||||
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("error starting ssh ", err)
|
||||
}
|
||||
|
||||
_, err = p.Wait()
|
||||
if err != nil {
|
||||
log.Fatal("error waiting on ssh ", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Usage: ro [options] subcommand")
|
||||
@@ -401,13 +493,13 @@ func main() {
|
||||
registerFlags()
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() != 1 {
|
||||
action := flag.Arg(0)
|
||||
|
||||
if flag.NArg() != 1 && action != "ssh" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
action := flag.Arg(0)
|
||||
|
||||
cmd, found := commandSet[action]
|
||||
if !found {
|
||||
fmt.Println("Unsupported subcommand:", action)
|
||||
|
||||
128
cmd/ro/roagent/roagent.go
Normal file
128
cmd/ro/roagent/roagent.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package roagent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/cloudflare/redoctober/client"
|
||||
"github.com/cloudflare/redoctober/core"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
type ROSigner struct {
|
||||
server *client.RemoteServer
|
||||
pub ssh.PublicKey
|
||||
encryptedKey []byte
|
||||
user string
|
||||
pswd string
|
||||
}
|
||||
|
||||
func (signer ROSigner) PublicKey() ssh.PublicKey {
|
||||
return signer.pub
|
||||
}
|
||||
|
||||
func (signer ROSigner) Sign(rand io.Reader, msg []byte) (signature *ssh.Signature, err error) {
|
||||
req := core.DecryptSignRequest{
|
||||
Name: signer.user,
|
||||
Password: signer.pswd,
|
||||
Data: signer.encryptedKey,
|
||||
TBSData: msg,
|
||||
}
|
||||
|
||||
resp, err := signer.server.DecryptSign(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Status != "ok" {
|
||||
log.Fatal("response status error:", resp.Status)
|
||||
return nil, errors.New("response status error")
|
||||
}
|
||||
fmt.Println("Response Status:", resp.Status)
|
||||
|
||||
var respMsg core.DecryptSignWithDelegates
|
||||
err = json.Unmarshal(resp.Response, &respMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respSignature ssh.Signature
|
||||
err = json.Unmarshal(resp.Response, &respSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &respSignature, nil
|
||||
}
|
||||
|
||||
type ROAgent struct {
|
||||
signer ROSigner
|
||||
}
|
||||
|
||||
func NewROAgent(server *client.RemoteServer, pubKey ssh.PublicKey, encryptedPrivKey []byte, user, pswd string) agent.Agent {
|
||||
return &ROAgent{
|
||||
ROSigner{
|
||||
server,
|
||||
pubKey,
|
||||
encryptedPrivKey,
|
||||
user,
|
||||
pswd,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ROAgent) RemoveAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
func (r *ROAgent) Remove(key ssh.PublicKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||
func (r *ROAgent) Lock(passphrase []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
func (r *ROAgent) Unlock(passphrase []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (r *ROAgent) List() ([]*agent.Key, error) {
|
||||
return []*agent.Key{
|
||||
{
|
||||
Format: r.signer.PublicKey().Type(),
|
||||
Blob: r.signer.PublicKey().Marshal(),
|
||||
Comment: "",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Insert adds a private key to the ROAgent. If a certificate
|
||||
// is given, that certificate is added as public key. Note that
|
||||
// any constraints given are ignored.
|
||||
func (r *ROAgent) Add(key agent.AddedKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign returns a signature for the data.
|
||||
func (r *ROAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
wanted := key.Marshal()
|
||||
if bytes.Equal(r.signer.PublicKey().Marshal(), wanted) {
|
||||
return r.signer.Sign(rand.Reader, data)
|
||||
}
|
||||
return nil, errors.New("wrong key requested")
|
||||
}
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
func (r *ROAgent) Signers() ([]ssh.Signer, error) {
|
||||
return []ssh.Signer{r.signer}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user