mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-05-28 10:40:49 +00:00
Merge pull request #46 from cloudflare/zi/simple-client
A simple client package for redoctober
This commit is contained in:
220
client/client.go
Normal file
220
client/client.go
Normal file
@@ -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)
|
||||
}
|
||||
59
core/core.go
59
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user