Merge pull request #170 from cloudflare/kyle/delegation-restore

Properly restore delegations.
This commit is contained in:
Zi Lin
2016-08-24 13:47:43 -07:00
committed by GitHub
14 changed files with 405 additions and 49 deletions
+17
View File
@@ -404,3 +404,20 @@ func (c *RemoteServer) Restore(req core.DelegateRequest) (*core.ResponseData, er
return unmarshalResponseData(respBytes)
}
// ResetPersisted issues a persisted delegation reset request,
// clearing out any persisted delegations. This must be done by an
// admin user.
func (c *RemoteServer) ResetPersisted(req core.PurgeRequest) (*core.ResponseData, error) {
reqBytes, err := json.Marshal(req)
if err != nil {
return nil, err
}
respBytes, err := c.doAction("reset-persisted", reqBytes)
if err != nil {
return nil, err
}
return unmarshalResponseData(respBytes)
}
+25 -11
View File
@@ -35,17 +35,18 @@ type command struct {
var roServer *client.RemoteServer
var commandSet = map[string]command{
"create": command{Run: runCreate, Desc: "create the disk vault and admin account"},
"create-user": command{Run: runCreateUser, 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"},
"restore": command{Run: runRestore, Desc: "perform a restore delegation"},
"create": command{Run: runCreate, Desc: "create the disk vault and admin account"},
"create-user": command{Run: runCreateUser, 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"},
"restore": command{Run: runRestore, Desc: "perform a restore delegation"},
"reset-persisted": command{Run: runResetPersisted, Desc: "reset the persisted delegations"},
}
func registerFlags() {
@@ -313,6 +314,19 @@ func runStatus() {
fmt.Println(resp)
}
func runResetPersisted() {
req := core.PurgeRequest{
Name: user,
Password: pswd,
}
resp, err := roServer.ResetPersisted(req)
processError(err)
fmt.Println(resp.Status)
fmt.Println(resp)
}
func main() {
flag.Usage = func() {
fmt.Println("Usage: ro [options] subcommand")
+34
View File
@@ -995,3 +995,37 @@ func Restore(jsonIn []byte) (out []byte, err error) {
return jsonResponse(out)
}
// ResetPersisted clears the persisted user data from the server. This
// request requires an admin.
func ResetPersisted(jsonIn []byte) (out []byte, err error) {
var req PurgeRequest
defer func() {
if err != nil {
log.Printf("core.resetpersisted failed: user=%s %v", req.Name, err)
} else {
log.Printf("core.resetpersisted success: user=%s", req.Name)
}
}()
if err = json.Unmarshal(jsonIn, &req); err != nil {
return jsonStatusError(err)
}
if err := validateUser(req.Name, req.Password, true); err != nil {
return jsonStatusError(err)
}
st, err := crypt.ResetPersisted()
if err != nil {
return jsonStatusError(err)
}
resp := &StatusData{Status: st.State}
if out, err = json.Marshal(resp); err != nil {
return jsonStatusError(err)
}
return jsonResponse(out)
}
+18 -4
View File
@@ -1306,10 +1306,11 @@ func afterRestartRestore(t *testing.T) {
}
func restoreDelegateRestore(t *testing.T) {
delegateJsonBad := []byte("{\"Name\":\"Alice\",\"Password\":\"Hi\",\"Time\":\"5m\",\"Uses\":1}")
delegateJson1 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":1}")
delegateJson2 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":1}")
delegateJson3 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":1}")
adminUser := []byte("{\"Name\":\"Admin\",\"Password\":\"Admin\"}")
delegateJsonBad := []byte("{\"Name\":\"Alice\",\"Password\":\"Hi\",\"Time\":\"5m\",\"Uses\":3}")
delegateJson1 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":3}")
delegateJson2 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":3}")
delegateJson3 := []byte("{\"Name\":\"Carl\",\"Password\":\"Hello\",\"Time\":\"5m\",\"Uses\":3}")
var s ResponseData
respJson, err := Restore(delegateJsonBad)
@@ -1382,6 +1383,19 @@ func restoreDelegateRestore(t *testing.T) {
} else if _, ok = summary.Live["Bob"]; !ok {
t.Fatalf("Bob should be present in the active delegations.")
}
respJson, err = ResetPersisted(adminUser)
if err != nil {
t.Fatalf("Resetting persisted delegations failed: %s", err)
}
err = json.Unmarshal(respJson, &s)
if err != nil {
t.Fatalf("Error resetting persisted delegations: %s", err)
} else if s.Status != "ok" {
t.Fatalf("Error resetting persisted delegations: %s", s.Status)
}
restoreState(t, persist.Active)
}
func TestRestore(t *testing.T) {
+14 -1
View File
@@ -777,7 +777,13 @@ func (c *Cryptor) Restore(name, password string, uses int, slot, durationString
return err
}
c.cache = keycache.NewFrom(uk)
rcache := keycache.NewFrom(uk)
err = rcache.Restore()
if err != nil {
return err
}
c.cache = rcache
c.persist.Persist()
c.persist.Cache().Flush()
return nil
@@ -787,3 +793,10 @@ func (c *Cryptor) Restore(name, password string, uses int, slot, durationString
func (c *Cryptor) Status() *persist.Status {
return c.persist.Status()
}
// ResetPersisted clears any persisted delegations and returns the
// vault to an active delegation state if configured.
func (c *Cryptor) ResetPersisted() (*persist.Status, error) {
err := c.persist.Purge()
return c.persist.Status(), err
}
+38
View File
@@ -11,6 +11,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"errors"
"fmt"
"log"
@@ -44,6 +45,7 @@ type ActiveUser struct {
AltNames map[string]string
Admin bool
Type string
Key []byte
rsaKey rsa.PrivateKey
eccKey *ecdsa.PrivateKey
@@ -225,8 +227,16 @@ func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, pass
switch record.Type {
case passvault.RSARecord:
current.rsaKey, err = record.GetKeyRSA(password)
if err != nil {
return
}
current.Key = x509.MarshalPKCS1PrivateKey(&current.rsaKey)
case passvault.ECCRecord:
current.eccKey, err = record.GetKeyECC(password)
if err != nil {
return
}
current.Key, err = x509.MarshalECPrivateKey(current.eccKey)
default:
err = errors.New("Unknown record type")
}
@@ -370,3 +380,31 @@ func (cache *Cache) DelegateStatus(name string, labels, admins []string) (admins
}
return
}
// Restore unmarshals the private key stored in the delegator to the
// appropriate private structure.
func (cache *Cache) Restore() (err error) {
for index, uk := range cache.UserKeys {
if len(uk.Key) == 0 {
return errors.New("keycache: no private key in active user")
}
rsaPriv, err := x509.ParsePKCS1PrivateKey(uk.Key)
if err == nil {
uk.rsaKey = *rsaPriv
cache.UserKeys[index] = uk
continue
}
ecPriv, err := x509.ParseECPrivateKey(uk.Key)
if err == nil {
uk.eccKey = ecPriv
cache.UserKeys[index] = uk
continue
}
return err
}
return nil
}
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"container/heap"
"crypto/rand"
"errors"
"fmt"
"strings"
)
@@ -191,7 +192,7 @@ func (m MSP) DistributeShares(sec []byte, db UserDatabase) (map[string][][]byte,
case Name:
name := cond.string
if !db.ValidUser(name) {
return nil, errors.New("Unknown user in predicate.")
return nil, fmt.Errorf("Unknown user '%s' in predicate.", name)
}
out[name] = append(out[name], share)
+9
View File
@@ -127,3 +127,12 @@ func (f *File) Status() *Status {
Summary: f.cache.GetSummary(),
}
}
func (f *File) Purge() error {
f.state = Active
f.blob = nil
if err := os.Remove(f.config.Location); err != nil {
return err
}
return nil
}
+98
View File
@@ -8,6 +8,11 @@ import (
"github.com/cloudflare/redoctober/config"
)
func fexists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func TestFileConfig(t *testing.T) {
cfg := &config.Delegations{
Persist: false,
@@ -165,3 +170,96 @@ func TestNewFilePersists(t *testing.T) {
t.Fatalf("fresh store should be persisting")
}
}
func TestActivePurge(t *testing.T) {
sf, err := tempName()
if err != nil {
t.Fatal(err)
}
defer os.Remove(sf)
cfg := &config.Delegations{
Persist: true,
Mechanism: FileMechanism,
Policy: "alice & bob",
Users: []string{"alice", "bob"},
Location: sf,
}
f, err := New(cfg)
if err != nil {
t.Fatal(err)
}
_, ok := f.(*File)
if !ok {
t.Fatalf("persist: expected to get a *File but have %T", f)
}
const expected = "test data"
if err = f.Store([]byte(expected)); err != nil {
t.Fatal(err)
}
if !fexists(sf) {
t.Fatalf("persist: file store wasn't written to disk")
}
err = f.Purge()
if err != nil {
t.Fatalf("%s", err)
}
if fexists(sf) {
t.Fatalf("persist: store should have been removed during purge")
}
}
func TestInactivePurge(t *testing.T) {
sf, err := tempName()
if err != nil {
t.Fatal(err)
}
defer os.Remove(sf)
cfg := &config.Delegations{
Persist: true,
Mechanism: FileMechanism,
Policy: "alice & bob",
Users: []string{"alice", "bob"},
Location: sf,
}
const expected = "test data"
err = ioutil.WriteFile(sf, []byte(expected), 0644)
if err != nil {
t.Fatalf("%s", err)
}
f, err := New(cfg)
if err != nil {
t.Fatal(err)
}
file, ok := f.(*File)
if !ok {
t.Fatalf("persist: expected to get a *File but have %T", f)
}
if err = f.Store([]byte(expected)); err != nil {
t.Fatal(err)
}
err = f.Purge()
if err != nil {
t.Fatalf("%s", err)
}
if fexists(sf) {
t.Fatalf("persist: store should have been removed during purge")
}
if file.Status().State != Active {
t.Fatalf("fresh store should be persisting")
}
}
+4
View File
@@ -57,3 +57,7 @@ func (n *Null) Cache() *keycache.Cache {
cache := keycache.NewCache()
return &cache
}
func (n *Null) Purge() error {
return nil
}
+5 -1
View File
@@ -55,6 +55,10 @@ func TestNewNull(t *testing.T) {
}
if cache := store.Cache(); len(cache.UserKeys) != 0 {
t.Fatal("persist: Null Cache should return an empty cache")
t.Fatal("persist: Null Cache() should return an empty cache")
}
if store.Purge() != nil {
t.Fatal("persist: Null Purge() shouldn't return an error")
}
}
+2
View File
@@ -53,6 +53,8 @@ type Store interface {
// This is not the main keycache. This is the keycache for
// users that can decrypt the store.
Cache() *keycache.Cache
// Purge clears the persisted keys.
Purge() error
}
// FileMechanism indicates that the persistence mechanism is a file.
+19 -18
View File
@@ -29,24 +29,25 @@ import (
// List of URLs to register and their related functions
var functions = map[string]func([]byte) ([]byte, error){
"/create": core.Create,
"/create-user": core.CreateUser,
"/summary": core.Summary,
"/purge": core.Purge,
"/delegate": core.Delegate,
"/password": core.Password,
"/encrypt": core.Encrypt,
"/re-encrypt": core.ReEncrypt,
"/decrypt": core.Decrypt,
"/owners": core.Owners,
"/modify": core.Modify,
"/export": core.Export,
"/order": core.Order,
"/orderout": core.OrdersOutstanding,
"/orderinfo": core.OrderInfo,
"/ordercancel": core.OrderCancel,
"/restore": core.Restore,
"/status": core.Status,
"/create": core.Create,
"/create-user": core.CreateUser,
"/summary": core.Summary,
"/purge": core.Purge,
"/delegate": core.Delegate,
"/password": core.Password,
"/encrypt": core.Encrypt,
"/re-encrypt": core.ReEncrypt,
"/decrypt": core.Decrypt,
"/owners": core.Owners,
"/modify": core.Modify,
"/export": core.Export,
"/order": core.Order,
"/orderout": core.OrdersOutstanding,
"/orderinfo": core.OrderInfo,
"/ordercancel": core.OrderCancel,
"/restore": core.Restore,
"/reset-persisted": core.ResetPersisted,
"/status": core.Status,
}
type userRequest struct {
+120 -13
View File
@@ -9,6 +9,7 @@ package main
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -19,6 +20,7 @@ import (
"testing"
"time"
"github.com/cloudflare/redoctober/client"
"github.com/cloudflare/redoctober/config"
"github.com/cloudflare/redoctober/core"
"github.com/cloudflare/redoctober/persist"
@@ -59,7 +61,8 @@ var (
Uses: 1,
}
encryptInput = &core.EncryptRequest{
encryptMessage = "Why is a raven like a writing desk?\n"
encryptInput = &core.EncryptRequest{
Minimum: 2,
Name: createVaultInput.Name,
Password: createVaultInput.Password,
@@ -636,6 +639,22 @@ func TestPurge(t *testing.T) {
// These need to write files to disk in order to test recovering delegations. //
////////////////////////////////////////////////////////////////////////////////
var (
restore *client.RemoteServer
// restoreSecret is the encrypted data that should be decryptable
// both before and after the restart.
restoreSecret []byte
restoreEncryptInput = &core.EncryptRequest{
Minimum: 2,
Name: createVaultInput.Name,
Password: createVaultInput.Password,
Owners: []string{createUserInput3.Name, createUserInput2.Name},
Data: []byte(base64.StdEncoding.EncodeToString([]byte(encryptMessage))),
}
)
func restoreSetup(t *testing.T, configPath, vaultPath string) (cmd *exec.Cmd) {
const maxAttempts = 5
@@ -650,7 +669,6 @@ func restoreSetup(t *testing.T, configPath, vaultPath string) (cmd *exec.Cmd) {
}
cmd = exec.Command(binaryPath, "-vaultpath", vaultPath, "-f", configPath)
if err := cmd.Start(); err != nil {
t.Fatalf("Error running redoctober command, %v", err)
}
@@ -705,6 +723,9 @@ func TestRestore(t *testing.T) {
// The server has restarted --- verify that the persisted
// delegations are available.
afterRestartRestore(t, cfgPath, vaultPath)
// Verify that we can reset the persisted delegations.
afterRestartPurge(t, cfgPath, vaultPath)
}
func prepareSetup(t *testing.T, pstore, cfgPath string) {
@@ -736,6 +757,16 @@ func prepareSetup(t *testing.T, pstore, cfgPath string) {
if err != nil {
t.Fatalf("failed to write config file: %s", err)
}
// We'll have two uses: one to permit the pre-restart decryption,
// one to permit the post-restart decryption, and one to verify
// the purge functionality.
delegateInput1.Uses = 3
delegateInput2.Uses = 3
// Allow someone to decrypt.
delegateInput1.Users = []string{createVaultInput.Name}
delegateInput2.Users = []string{createVaultInput.Name}
}
func restoreCheckStatus(t *testing.T, expected string) {
@@ -816,17 +847,23 @@ func beforeRestartRestore(t *testing.T, cfgPath, vaultPath string) {
cmd := restoreSetup(t, cfgPath, vaultPath)
defer teardown(t, cmd)
srv, err := client.NewRemoteServer("localhost:8080", "testdata/server.crt")
if err != nil {
t.Fatalf("failed to set up client: %s", err)
}
// Create a vault/admin user and 2 normal users so there is data to work with.
if _, _, err := post("create", createVaultInput); err != nil {
if _, _, err = post("create", createVaultInput); err != nil {
t.Fatalf("failed to create the vault: %s", err)
}
if _, _, err := post("create-user", createUserInput1); err != nil {
if _, err = srv.CreateUser(*createUserInput1); err != nil {
t.Fatalf("couldn't create user %s: %s", createUserInput1.Name, err)
}
if _, _, err := post("create-user", createUserInput2); err != nil {
if _, err = srv.CreateUser(*createUserInput2); err != nil {
t.Fatalf("couldn't create user %s: %s", createUserInput2.Name, err)
}
if _, _, err := post("create-user", createUserInput3); err != nil {
if _, err = srv.CreateUser(*createUserInput3); err != nil {
t.Fatalf("couldn't create user %s: %s", createUserInput3.Name, err)
}
@@ -835,29 +872,55 @@ func beforeRestartRestore(t *testing.T, cfgPath, vaultPath string) {
restoreCheckStatus(t, persist.Active)
// Delegate two users.
if _, _, err := post("delegate", delegateInput2); err != nil {
if _, err = srv.Delegate(*delegateInput2); err != nil {
t.Fatalf("failed to delegate for %s: %s", delegateInput2.Name, err)
}
if _, _, err := post("delegate", delegateInput3); err != nil {
if _, err = srv.Delegate(*delegateInput3); err != nil {
t.Fatalf("failed to delegate for %s: %s", delegateInput3.Name, err)
}
restoreCheckLiveCount(t, 2)
restoreCheckLiveUsers(t, []string{delegateInput2.Name, delegateInput3.Name},
[]string{createUserInput1.Name, createVaultInput.Name})
// Encrypt a message, and make sure it can be decrypted.
resp, err := srv.Encrypt(*restoreEncryptInput)
restoreSecret = resp.Response
decryptInput := &core.DecryptRequest{
Name: createVaultInput.Name,
Password: createVaultInput.Password,
Data: restoreSecret[:],
}
decrypted, err := srv.DecryptIntoData(*decryptInput)
if err != nil {
t.Fatalf("failed to decrypt message: %s", err)
}
decryptedMessage, err := base64.StdEncoding.DecodeString(string(decrypted))
if string(decryptedMessage) != encryptMessage {
t.Fatalf("decryption produced the wrong message: want '%s' but have '%s'",
encryptMessage, decryptedMessage)
}
}
func afterRestartRestore(t *testing.T, cfgPath, vaultPath string) {
cmd := restoreSetup(t, cfgPath, vaultPath)
defer teardown(t, cmd)
srv, err := client.NewRemoteServer("localhost:8080", "testdata/server.crt")
if err != nil {
t.Fatalf("failed to set up client: %s", err)
}
// An existing vault with a persisted delegation store should
// be inactive.
restoreCheckStatus(t, persist.Inactive)
// Delegate a user who wasn't in the persisted delegation set.
if _, _, err := post("delegate", delegateInput1); err != nil {
t.Fatalf("Error delegating with user 1, %v", err)
if _, err := srv.Delegate(*delegateInput1); err != nil {
t.Fatalf("error delegating with user 1, %v", err)
}
restoreCheckLiveCount(t, 1)
@@ -865,7 +928,7 @@ func afterRestartRestore(t *testing.T, cfgPath, vaultPath string) {
[]string{createUserInput2.Name, delegateInput3.Name, createVaultInput.Name})
// Begin the restoration by delegating for a single user.
if _, _, err := post("restore", delegateInput1); err != nil {
if _, err := srv.Restore(*delegateInput1); err != nil {
t.Fatalf("restoration by user %s failed: %s", delegateInput1.Name, err)
}
@@ -877,12 +940,56 @@ func afterRestartRestore(t *testing.T, cfgPath, vaultPath string) {
// Delegate the second user, which should lead to restoring
// the delegations.
if _, _, err := post("restore", delegateInput4); err != nil {
t.Fatalf("restoration by user %s failed: %s", delegateInput4.Name, err)
if _, err := srv.Restore(*delegateInput4); err != nil {
t.Fatalf("restoration by user %s failed: %s", delegateInput1.Name, err)
}
restoreCheckStatus(t, persist.Active)
restoreCheckLiveCount(t, 2)
restoreCheckLiveUsers(t, []string{delegateInput2.Name, delegateInput3.Name},
[]string{createUserInput1.Name, createVaultInput.Name})
decryptInput := &core.DecryptRequest{
Name: createVaultInput.Name,
Password: createVaultInput.Password,
Data: restoreSecret[:],
}
decrypted, err := srv.DecryptIntoData(*decryptInput)
if err != nil {
t.Fatalf("failed to decrypt message: %s", err)
}
decryptedMessage, err := base64.StdEncoding.DecodeString(string(decrypted))
if string(decryptedMessage) != encryptMessage {
t.Fatalf("decryption produced the wrong message: want '%s' but have '%s'",
encryptMessage, decryptedMessage)
}
}
func afterRestartPurge(t *testing.T, cfgPath, vaultPath string) {
cmd := restoreSetup(t, cfgPath, vaultPath)
defer teardown(t, cmd)
srv, err := client.NewRemoteServer("localhost:8080", "testdata/server.crt")
if err != nil {
t.Fatalf("failed to set up client: %s", err)
}
// An existing vault with a persisted delegation store should
// be inactive.
restoreCheckStatus(t, persist.Inactive)
resp, err := srv.ResetPersisted(core.PurgeRequest{Name: createVaultInput.Name, Password: createVaultInput.Password})
if err != nil {
t.Fatalf("failed to reset persisted delegations: %s", err)
}
if resp.Status != "ok" {
t.Fatalf("failed to reset persisted delegations: %s", resp.Status)
}
// An existing vault whose persistence store has been reset
// should be active.
restoreCheckStatus(t, persist.Active)
}