mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 01:43:47 +00:00
signer: implement audit logging
This commit is contained in:
parent
c9eb319137
commit
82dd1080b0
4 changed files with 126 additions and 50 deletions
|
|
@ -53,6 +53,29 @@ type Metadata struct {
|
||||||
Scheme string `json:"scheme"`
|
Scheme string `json:"scheme"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MetadataFromContext(ctx context.Context) Metadata {
|
||||||
|
m := Metadata{"NA", "NA", "NA"}
|
||||||
|
|
||||||
|
if v := ctx.Value("remote"); v != nil {
|
||||||
|
m.Remote = v.(string)
|
||||||
|
}
|
||||||
|
if v := ctx.Value("scheme"); v != nil {
|
||||||
|
m.Scheme = v.(string)
|
||||||
|
}
|
||||||
|
if v := ctx.Value("local"); v != nil {
|
||||||
|
m.Local = v.(string)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Metadata) String() string {
|
||||||
|
s, err := json.Marshal(m)
|
||||||
|
if err == nil {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
// types for the requests/response types
|
// types for the requests/response types
|
||||||
type (
|
type (
|
||||||
// SignTxRequest contains info about a Transaction to sign
|
// SignTxRequest contains info about a Transaction to sign
|
||||||
|
|
@ -129,6 +152,16 @@ func (ew errorWrapper) String() string {
|
||||||
return fmt.Sprintf("%s\n%s", ew.msg, ew.err)
|
return fmt.Sprintf("%s\n%s", ew.msg, ew.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExternalAPI interface {
|
||||||
|
List(ctx context.Context) (Accounts, error)
|
||||||
|
New(ctx context.Context) (accounts.Account, error)
|
||||||
|
SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error)
|
||||||
|
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
|
||||||
|
EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error)
|
||||||
|
Export(ctx context.Context, addr common.Address) (json.RawMessage, error)
|
||||||
|
Import(ctx context.Context, keyJSON json.RawMessage) (Account, error)
|
||||||
|
}
|
||||||
|
|
||||||
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI
|
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI
|
||||||
// for the signer
|
// for the signer
|
||||||
type SignerUI interface {
|
type SignerUI interface {
|
||||||
|
|
@ -188,21 +221,6 @@ func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abi
|
||||||
return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, *abidb}
|
return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, *abidb}
|
||||||
}
|
}
|
||||||
|
|
||||||
func metaData(ctx context.Context) Metadata {
|
|
||||||
m := Metadata{"NA", "NA", "NA"}
|
|
||||||
|
|
||||||
if v := ctx.Value("remote"); v != nil {
|
|
||||||
m.Remote = v.(string)
|
|
||||||
}
|
|
||||||
if v := ctx.Value("scheme"); v != nil {
|
|
||||||
m.Scheme = v.(string)
|
|
||||||
}
|
|
||||||
if v := ctx.Value("local"); v != nil {
|
|
||||||
m.Local = v.(string)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns the set of wallet this signer manages. Each wallet can contain
|
// List returns the set of wallet this signer manages. Each wallet can contain
|
||||||
// multiple accounts.
|
// multiple accounts.
|
||||||
func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
|
func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
|
||||||
|
|
@ -215,7 +233,7 @@ func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := api.ui.ApproveListing(&ListRequest{Accounts: accs, Meta: metaData(ctx)})
|
result, err := api.ui.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +252,7 @@ func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
|
||||||
if len(be) == 0 {
|
if len(be) == 0 {
|
||||||
return accounts.Account{}, errors.New("password based accounts not supported")
|
return accounts.Account{}, errors.New("password based accounts not supported")
|
||||||
}
|
}
|
||||||
resp, err := api.ui.ApproveNewAccount(&NewAccountRequest{metaData(ctx)})
|
resp, err := api.ui.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
|
|
@ -307,7 +325,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Mixedcase
|
||||||
result SignTxResponse
|
result SignTxResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
req := SignTxRequest{Transaction: args, From: from, Meta: metaData(ctx)}
|
req := SignTxRequest{Transaction: args, From: from, Meta: MetadataFromContext(ctx)}
|
||||||
|
|
||||||
data := args.Data
|
data := args.Data
|
||||||
if len(data) > 3 {
|
if len(data) > 3 {
|
||||||
|
|
@ -383,7 +401,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da
|
||||||
|
|
||||||
// We make the request prior to looking up if we actually have the account, to prevent
|
// We make the request prior to looking up if we actually have the account, to prevent
|
||||||
// account-enumeration via the API
|
// account-enumeration via the API
|
||||||
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: metaData(ctx)}
|
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)}
|
||||||
res, err := api.ui.ApproveSignData(req)
|
res, err := api.ui.ApproveSignData(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -454,7 +472,7 @@ func signHash(data []byte) ([]byte, string) {
|
||||||
// Export returns encrypted private key associated with the given address in web3 keystore format.
|
// Export returns encrypted private key associated with the given address in web3 keystore format.
|
||||||
func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
|
func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
|
||||||
|
|
||||||
res, err := api.ui.ApproveExport(&ExportRequest{Address: addr, Meta: metaData(ctx)})
|
res, err := api.ui.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -487,7 +505,7 @@ func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Acco
|
||||||
return Account{}, errors.New("password based accounts not supported")
|
return Account{}, errors.New("password based accounts not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := api.ui.ApproveImport(&ImportRequest{Meta: metaData(ctx)})
|
res, err := api.ui.ApproveImport(&ImportRequest{Meta: MetadataFromContext(ctx)})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Account{}, err
|
return Account{}, err
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,82 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"encoding/json"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuditLogger struct {
|
type AuditLogger struct {
|
||||||
writer *bufio.Writer
|
log log.Logger
|
||||||
|
api ExternalAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l AuditLogger) Store(record *rpc.RPCInvocationRecord) {
|
func (l *AuditLogger) List(ctx context.Context) (Accounts, error) {
|
||||||
l.writer.WriteString(fmt.Sprintf("%v\n%v\n", time.Now().Format(time.RFC3339), record.Method))
|
|
||||||
for i, arg := range record.Args {
|
l.log.Info("Called list", "interface", "http")
|
||||||
l.writer.WriteString(fmt.Sprintf("\t%d: %v\n", i, arg))
|
return l.api.List(ctx)
|
||||||
}
|
|
||||||
l.writer.WriteString(fmt.Sprintf("%v\n", record.Response))
|
|
||||||
l.writer.Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuditLogger(writer io.Writer) *AuditLogger {
|
func (l *AuditLogger) New(ctx context.Context) (accounts.Account, error) {
|
||||||
return &AuditLogger{bufio.NewWriter(writer)}
|
return l.api.New(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AuditLogger) SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) {
|
||||||
|
l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||||
|
"from", from.String(), "tx", args.String(),
|
||||||
|
"methodSelector", methodSelector)
|
||||||
|
b, e := l.api.SignTransaction(ctx, from, args, methodSelector)
|
||||||
|
|
||||||
|
l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(b), "error", e)
|
||||||
|
|
||||||
|
return b, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||||
|
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||||
|
"addr", addr.String(), "data", common.Bytes2Hex(data))
|
||||||
|
b, e := l.api.Sign(ctx, addr, data)
|
||||||
|
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e)
|
||||||
|
return b, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AuditLogger) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
||||||
|
l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||||
|
"data", common.Bytes2Hex(data))
|
||||||
|
a, e := l.api.EcRecover(ctx, data, sig)
|
||||||
|
l.log.Info("EcRecover", "type", "response", "addr", a.String(), "error", e)
|
||||||
|
return a, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AuditLogger) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
|
||||||
|
l.log.Info("Export", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||||
|
"addr", addr.Hex())
|
||||||
|
j, e := l.api.Export(ctx, addr)
|
||||||
|
// In this case, we don't actually log the json-response, which may be extra sensitive
|
||||||
|
l.log.Info("Export", "type", "response", "json response size", len(j), "error", e)
|
||||||
|
return j, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AuditLogger) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) {
|
||||||
|
// Don't actually log the json contents
|
||||||
|
l.log.Info("Import", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||||
|
"keyJSON size", len(keyJSON))
|
||||||
|
a, e := l.api.Import(ctx, keyJSON)
|
||||||
|
l.log.Info("Import", "type", "response", "addr", a.String(), "error", e)
|
||||||
|
return a, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
|
||||||
|
l := log.New("api", "signer")
|
||||||
|
handler, err := log.FileHandler(path, log.LogfmtFormat())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.SetHandler(handler)
|
||||||
|
l.Info("Configured", "audit log", path)
|
||||||
|
return &AuditLogger{l, api}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,23 +115,26 @@ func main() {
|
||||||
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
|
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
api ExternalAPI
|
||||||
|
listener net.Listener
|
||||||
server = rpc.NewServer()
|
server = rpc.NewServer()
|
||||||
|
)
|
||||||
|
|
||||||
api = NewSignerAPI(
|
api_impl := NewSignerAPI(
|
||||||
c.Int64(utils.NetworkIdFlag.Name),
|
c.Int64(utils.NetworkIdFlag.Name),
|
||||||
c.String("keystore"),
|
c.String("keystore"),
|
||||||
c.Bool(utils.NoUSBFlag.Name),
|
c.Bool(utils.NoUSBFlag.Name),
|
||||||
ui, db,
|
ui, db,
|
||||||
c.Bool(utils.LightKDFFlag.Name))
|
c.Bool(utils.LightKDFFlag.Name))
|
||||||
listener net.Listener
|
|
||||||
)
|
api = api_impl
|
||||||
|
|
||||||
// Audit logging
|
// Audit logging
|
||||||
if logfile := c.String("auditlog"); logfile != "" {
|
if logfile := c.String("auditlog"); logfile != "" {
|
||||||
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
api, err = NewAuditLogger(logfile, api_impl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not open %v for audit logging", logfile)
|
utils.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
server.SetAuditLogger(NewAuditLogger(f))
|
|
||||||
log.Info("Audit logs configured", "file", logfile)
|
log.Info("Audit logs configured", "file", logfile)
|
||||||
}
|
}
|
||||||
// register signer API with server
|
// register signer API with server
|
||||||
|
|
@ -156,7 +159,7 @@ func main() {
|
||||||
|
|
||||||
if c.Bool("stdio-ui-test") {
|
if c.Bool("stdio-ui-test") {
|
||||||
log.Info("Performing UI test")
|
log.Info("Performing UI test")
|
||||||
go testExternalUI(api)
|
go testExternalUI(api_impl)
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc.NewHTTPServer(cors, server).Serve(listener)
|
rpc.NewHTTPServer(cors, server).Serve(listener)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue