From 4702aab061abe1a56b3445257819b6b561862076 Mon Sep 17 00:00:00 2001 From: Zi Lin Date: Thu, 16 Apr 2015 17:51:59 -0700 Subject: [PATCH] A simple client package for redoctober - Generalize core.status to core.responseData - Export core request types so the client can marshal/unmarshal requests/responses - Tested with a test script against a local redoctober server --- client/client.go | 220 ++++++++++++++++++++++++++++++++++++++++++++++ core/core.go | 59 ++++++------- core/core_test.go | 28 +++--- 3 files changed, 262 insertions(+), 45 deletions(-) create mode 100644 client/client.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..0977a2c --- /dev/null +++ b/client/client.go @@ -0,0 +1,220 @@ +package client + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + + "github.com/cloudflare/redoctober/core" +) + +// RemoteServer represents a remote RedOctober server. +type RemoteServer struct { + client *http.Client + serverAddress string +} + +// NewRemoteServer generates a RemoteServer with the server address and +// the root CA the server uses to authenticate itself. +func NewRemoteServer(serverAddress, CAFile string) (*RemoteServer, error) { + + // populate a root CA pool from file + rootCAs := x509.NewCertPool() + pemBytes, err := ioutil.ReadFile(CAFile) + if err != nil { + return nil, errors.New("fail to read CA file: " + err.Error()) + } + ok := rootCAs.AppendCertsFromPEM(pemBytes) + if !ok { + return nil, errors.New("fail to populate CA root pool.") + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{RootCAs: rootCAs}, + DisableCompression: true, + } + server := &RemoteServer{ + client: &http.Client{Transport: tr}, + serverAddress: serverAddress, + } + return server, nil +} + +// getURL creates URL for a specific path of the RemoteServer +func (c *RemoteServer) getURL(path string) string { + return fmt.Sprintf("https://%s%s", c.serverAddress, path) + +} + +// doAction sends req to the remote server and returns the response +func (c *RemoteServer) doAction(action string, req []byte) ([]byte, error) { + buf := bytes.NewBuffer(req) + url := c.getURL("/" + action) + resp, err := c.client.Post(url, "application/json", buf) + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body.Close() + + return body, nil + +} + +// unmarshalResponseData is a helper function that unmarshal response bytes +// into ResponseData object. +func unmarshalResponseData(respBytes []byte) (*core.ResponseData, error) { + response := new(core.ResponseData) + err := json.Unmarshal(respBytes, response) + if err != nil { + return nil, err + } + + if response.Status != "ok" { + return nil, errors.New(response.Status) + } + + return response, nil +} + +// Create creates an admin account at the remote server +func (c *RemoteServer) Create(req core.CreateRequest) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("create", reqBytes) + if err != nil { + return nil, err + } + + return unmarshalResponseData(respBytes) +} + +// Summary returns the summary reported by the remote server +func (c *RemoteServer) Summary(req core.SummaryRequest) (*core.SummaryData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("summary", reqBytes) + if err != nil { + return nil, err + } + + response := new(core.SummaryData) + err = json.Unmarshal(respBytes, response) + if err != nil { + return nil, err + } + + if response.Status != "ok" { + return nil, errors.New(response.Status) + } + return response, nil +} + +// Delegate issues a delegate request to the remote server +func (c *RemoteServer) Delegate(req core.DelegateRequest) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("delegate", reqBytes) + if err != nil { + return nil, err + } + + return unmarshalResponseData(respBytes) +} + +// Modify issues a modify request to the remote server +func (c *RemoteServer) Modify(req core.ModifyRequest) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("modify", reqBytes) + if err != nil { + return nil, err + } + + return unmarshalResponseData(respBytes) +} + +// Encrypt issues an encrypt request to the remote server +func (c *RemoteServer) Encrypt(req core.EncryptRequest) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("encrypt", reqBytes) + if err != nil { + return nil, err + } + + return unmarshalResponseData(respBytes) +} + +// Decrypt issues an decrypt request to the remote server +func (c *RemoteServer) Decrypt(req core.DecryptRequest) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("decrypt", 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) { + responseData, err := c.Decrypt(req) + if err != nil { + return nil, err + } + + d := new(core.DecryptWithDelegates) + err = json.Unmarshal(responseData.Response, d) + if err != nil { + return nil, err + } + + return d.Data, nil + +} + +// Password issues an password request to the remote server +func (c *RemoteServer) Password(req []byte) (*core.ResponseData, error) { + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + respBytes, err := c.doAction("delegate", reqBytes) + if err != nil { + return nil, err + } + + return unmarshalResponseData(respBytes) +} diff --git a/core/core.go b/core/core.go index d19e149..1aac20e 100644 --- a/core/core.go +++ b/core/core.go @@ -20,17 +20,17 @@ import ( // JSON that should be sent on the /delegate URI and it is handled by // the Delegate function below). -type create struct { +type CreateRequest struct { Name string Password string } -type summary struct { +type SummaryRequest struct { Name string Password string } -type delegate struct { +type DelegateRequest struct { Name string Password string @@ -40,14 +40,14 @@ type delegate struct { Labels []string } -type password struct { +type PasswordRequest struct { Name string Password string NewPassword string } -type encrypt struct { +type EncryptRequest struct { Name string Password string @@ -60,19 +60,14 @@ type encrypt struct { Labels []string } -type decrypt struct { +type DecryptRequest struct { Name string Password string Data []byte } -type decryptWithDelegates struct { - Data []byte - Delegates []string -} - -type modify struct { +type ModifyRequest struct { Name string Password string @@ -82,34 +77,35 @@ type modify struct { // These structures map the JSON responses that will be sent from the API -type status struct { - Status string -} - -type responseData struct { +type ResponseData struct { Status string - Response []byte + Response []byte `json:",omitempty"` } -type summaryData struct { +type SummaryData struct { Status string Live map[string]keycache.ActiveUser All map[string]passvault.Summary } +type DecryptWithDelegates struct { + Data []byte + Delegates []string +} + // Helper functions that create JSON responses sent by core func jsonStatusOk() ([]byte, error) { - return json.Marshal(status{Status: "ok"}) + return json.Marshal(ResponseData{Status: "ok"}) } func jsonStatusError(err error) ([]byte, error) { - return json.Marshal(status{Status: err.Error()}) + return json.Marshal(ResponseData{Status: err.Error()}) } func jsonSummary() ([]byte, error) { - return json.Marshal(summaryData{Status: "ok", Live: keycache.GetSummary(), All: passvault.GetSummary()}) + return json.Marshal(SummaryData{Status: "ok", Live: keycache.GetSummary(), All: passvault.GetSummary()}) } func jsonResponse(resp []byte) ([]byte, error) { - return json.Marshal(responseData{Status: "ok", Response: resp}) + return json.Marshal(ResponseData{Status: "ok", Response: resp}) } // validateAdmin checks that the username and password passed in are @@ -156,7 +152,7 @@ func Init(path string) (err error) { // Create processes a create request. func Create(jsonIn []byte) ([]byte, error) { - var s create + var s CreateRequest if err := json.Unmarshal(jsonIn, &s); err != nil { return jsonStatusError(err) } @@ -180,7 +176,7 @@ func Create(jsonIn []byte) ([]byte, error) { // Summary processes a summary request. func Summary(jsonIn []byte) ([]byte, error) { - var s summary + var s SummaryRequest keycache.Refresh() if err := json.Unmarshal(jsonIn, &s); err != nil { @@ -201,7 +197,7 @@ func Summary(jsonIn []byte) ([]byte, error) { // Delegate processes a delegation request. func Delegate(jsonIn []byte) ([]byte, error) { - var s delegate + var s DelegateRequest if err := json.Unmarshal(jsonIn, &s); err != nil { return jsonStatusError(err) } @@ -242,7 +238,7 @@ func Delegate(jsonIn []byte) ([]byte, error) { // Password processes a password change request. func Password(jsonIn []byte) ([]byte, error) { - var s password + var s PasswordRequest if err := json.Unmarshal(jsonIn, &s); err != nil { return jsonStatusError(err) } @@ -262,7 +258,7 @@ func Password(jsonIn []byte) ([]byte, error) { // Encrypt processes an encrypt request. func Encrypt(jsonIn []byte) ([]byte, error) { - var s encrypt + var s EncryptRequest if err := json.Unmarshal(jsonIn, &s); err != nil { return jsonStatusError(err) } @@ -288,9 +284,10 @@ func Encrypt(jsonIn []byte) ([]byte, error) { // Decrypt processes a decrypt request. func Decrypt(jsonIn []byte) ([]byte, error) { - var s decrypt + var s DecryptRequest err := json.Unmarshal(jsonIn, &s) if err != nil { + log.Println("Error unmarshaling input:", err) return jsonStatusError(err) } @@ -305,7 +302,7 @@ func Decrypt(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - resp := &decryptWithDelegates{ + resp := &DecryptWithDelegates{ Data: data, Delegates: names, } @@ -320,7 +317,7 @@ func Decrypt(jsonIn []byte) ([]byte, error) { // Modify processes a modify request. func Modify(jsonIn []byte) ([]byte, error) { - var s modify + var s ModifyRequest if err := json.Unmarshal(jsonIn, &s); err != nil { return jsonStatusError(err) diff --git a/core/core_test.go b/core/core_test.go index 66c65fb..4acadcc 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -25,7 +25,7 @@ func TestCreate(t *testing.T) { t.Fatalf("Error in creating account, %v", err) } - var s responseData + var s ResponseData err = json.Unmarshal(respJson, &s) if err != nil { t.Fatalf("Error in creating account, %v", err) @@ -60,7 +60,7 @@ func TestSummary(t *testing.T) { if err != nil { t.Fatalf("Error in summary of account with no vault, %v", err) } - var s summaryData + var s SummaryData err = json.Unmarshal(respJson, &s) if err != nil { t.Fatalf("Error in summary of account with no vault, %v", err) @@ -181,7 +181,7 @@ func TestPassword(t *testing.T) { Init("/tmp/db1.json") // check for summary of initialized vault with new member - var s responseData + var s ResponseData respJson, err := Create(createJson) if err != nil { t.Fatalf("Error in creating account, %v", err) @@ -297,7 +297,7 @@ func TestEncryptDecrypt(t *testing.T) { Init("/tmp/db1.json") // check for summary of initialized vault with new member - var s responseData + var s ResponseData respJson, err := Create(delegateJson) if err != nil { t.Fatalf("Error in creating account, %v", err) @@ -340,7 +340,7 @@ func TestEncryptDecrypt(t *testing.T) { if err != nil { t.Fatalf("Error in summary, %v", err) } - var sum summaryData + var sum SummaryData err = json.Unmarshal(respJson, &sum) if err != nil { t.Fatalf("Error in summary, %v", err) @@ -379,7 +379,7 @@ func TestEncryptDecrypt(t *testing.T) { } // decrypt file - decryptJson, err := json.Marshal(decrypt{Name: "Alice", Password: "Hello", Data: s.Response}) + decryptJson, err := json.Marshal(DecryptRequest{Name: "Alice", Password: "Hello", Data: s.Response}) if err != nil { t.Fatalf("Error in marshalling decryption, %v", err) } @@ -423,7 +423,7 @@ func TestEncryptDecrypt(t *testing.T) { // verify the presence of the two delgations keycache.Refresh() - var sum2 summaryData + var sum2 SummaryData respJson, err = Summary(summaryJson) if err != nil { t.Fatalf("Error in summary, %v", err) @@ -451,7 +451,7 @@ func TestEncryptDecrypt(t *testing.T) { t.Fatalf("Error in decrypt, %v", s.Status) } - var d decryptWithDelegates + var d DecryptWithDelegates err = json.Unmarshal(s.Response, &d) if err != nil { t.Fatalf("Error in decrypt, %v", err) @@ -488,7 +488,7 @@ func TestModify(t *testing.T) { Init("/tmp/db1.json") // check for summary of initialized vault with new member - var s responseData + var s ResponseData respJson, err := Create(delegateJson) if err != nil { t.Fatalf("Error in creating account, %v", err) @@ -531,7 +531,7 @@ func TestModify(t *testing.T) { if err != nil { t.Fatalf("Error in summary, %v", err) } - var sum summaryData + var sum SummaryData err = json.Unmarshal(respJson, &sum) if err != nil { t.Fatalf("Error in summary, %v", err) @@ -638,7 +638,7 @@ func TestModify(t *testing.T) { t.Fatalf("Error in modify, %v", s.Status) } - var sum3 summaryData + var sum3 SummaryData respJson, err = Summary(summaryJson2) if err != nil { t.Fatalf("Error in summary, %v", err) @@ -683,7 +683,7 @@ func TestStatic(t *testing.T) { Init("/tmp/db1.json") // check for summary of initialized vault with new member - var s responseData + var s ResponseData respJson, err := Delegate(delegateJson2) if err != nil { t.Fatalf("Error in delegating account, %v", err) @@ -708,7 +708,7 @@ func TestStatic(t *testing.T) { t.Fatalf("Error in delegating account, %v", s.Status) } - var r responseData + var r ResponseData respJson, err = Decrypt(decryptJson) if err != nil { t.Fatalf("Error in decrypt, %v", err) @@ -721,7 +721,7 @@ func TestStatic(t *testing.T) { t.Fatalf("Error in summary, %v", r.Status) } - var d decryptWithDelegates + var d DecryptWithDelegates err = json.Unmarshal(r.Response, &d) if err != nil { t.Fatalf("Error in decrypt, %v", err)