Skip to content

Commit

Permalink
Refactor Postman
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort committed Jan 11, 2025
1 parent 73ef401 commit 0f5b8e4
Show file tree
Hide file tree
Showing 14 changed files with 402 additions and 691 deletions.
58 changes: 32 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@

Complete documentation for Envoy can be found at [https://trisa.dev/envoy](https://trisa.dev/envoy).

## Deploying Envoy

The simplest way to deploy Envoy is onto a Kubernetes cluster using Helm. The helm chart and values description for deploying Envoy can be found here:

[artifacthub.io/packages/helm/trisacrypto/envoy](https://artifacthub.io/packages/helm/trisacrypto/envoy)

If you're developing Envoy or looking to run it locally, see the instructions below.

## Envoy Implementation Options

| Open Source | One-Time Setup | Managed Service |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Envoy is open source (MIT License). Download, install, integrate, host and support your own Envoy node and service. | The Envoy team will install and configure your Envoy node in your environment while you host, maintain, and support the node on an ongoing basis. | The Envoy team will install, configure, host, maintain, and support an Envoy node for you. Includes dedicated, provenance-aware node with regional deployments. |
| | | |

If you’d like more information on the one-time integration service or managed services, [schedule a demo](https://rtnl.link/p2WzzmXDuSu) with the Envoy team!


## Envoy Support

| | Open Source | One-Time Setup | Managed Service |
| ------------------------------- | ---------------------- | ---------------------- | ---------------------- |
| [Envoy Documentation](https://trisa.dev/envoy/index.html) | ✓ | ✓ | ✓ |
| Access to [TRISA Slack Community](https://trisa-workspace.slack.com/) | ✓ | ✓ | ✓ |
| Training from Envoy Team | | ✓ | ✓ |
| Dedicated Support | | | ✓ |
| Response Time* | Within 5 business days | Within 5 business days | Within 3 business days |


*The Envoy team's business hours are 9AM - 6PM Eastern.

## Running Envoy Locally

**NOTE**: Development is happening rapidly on the node right now; if these instructions don't work correctly, please open an issue on GitHub so we can review the docs.
Expand Down Expand Up @@ -77,29 +108,4 @@ $ docker compose exec counterparty.local envoy createuser -e [email] -r admin

You can access the counterparty at [http://localhost:9000](http://localhost:9000) or at [http://counterparty.local:9000](http://counterparty.local:9000) if you have edited your hosts file.

> **NOTE**: Due to the way cookie domains work with the credentials, you can only be logged into either the envoy development node or the counterparty development node at the same time. It's annoying, but you'll have to login again when switching between nodes unfortunately.

## Envoy Implementation Options

| Open Source | One-Time Setup | Managed Service |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Envoy is open source (MIT License). Download, install, integrate, host and support your own Envoy node and service. | The Envoy team will install and configure your Envoy node in your environment while you host, maintain, and support the node on an ongoing basis. | The Envoy team will install, configure, host, maintain, and support an Envoy node for you. Includes dedicated, provenance-aware node with regional deployments. |
| | | |

If you’d like more information on the one-time integration service or managed services, [schedule a demo](https://rtnl.link/p2WzzmXDuSu) with the Envoy team!


## Envoy Support

| | Open Source | One-Time Setup | Managed Service |
| ------------------------------- | ---------------------- | ---------------------- | ---------------------- |
| [Envoy Documentation](https://trisa.dev/envoy/index.html) | ✓ | ✓ | ✓ |
| Access to [TRISA Slack Community](https://trisa-workspace.slack.com/) | ✓ | ✓ | ✓ |
| Training from Envoy Team | | ✓ | ✓ |
| Dedicated Support | | | ✓ |
| Response Time* | Within 5 business days | Within 5 business days | Within 3 business days |



*The Envoy team's business hours are 9AM - 6PM Eastern.
> **NOTE**: Due to the way cookie domains work with the credentials, you can only be logged into either the envoy development node or the counterparty development node at the same time. It's annoying, but you'll have to login again when switching between nodes unfortunately.
24 changes: 24 additions & 0 deletions pkg/postman/direction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package postman

type Direction uint8

const (
Unknown Direction = iota
DirectionIncoming
DirectionOutgoing
)

func (d Direction) String() string {
switch d {
case DirectionIncoming:
return "incoming"
case DirectionOutgoing:
return "outgoing"
default:
return "unknown"
}
}

func (d Direction) Valid() bool {
return d > Unknown && d <= DirectionOutgoing
}
39 changes: 39 additions & 0 deletions pkg/postman/direction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package postman_test

import (
"testing"

"github.com/stretchr/testify/require"
"github.com/trisacrypto/envoy/pkg/postman"
)

func TestDirectionString(t *testing.T) {
tests := []struct {
direction postman.Direction
expected string
}{
{postman.Unknown, "unknown"},
{postman.DirectionIncoming, "incoming"},
{postman.DirectionOutgoing, "outgoing"},
}

for _, test := range tests {
require.Equal(t, test.expected, test.direction.String())
}
}

func TestDirectionValid(t *testing.T) {
tests := []struct {
direction postman.Direction
expected bool
}{
{postman.Unknown, false},
{postman.DirectionIncoming, true},
{postman.DirectionOutgoing, true},
{postman.Direction(3), false},
}

for _, test := range tests {
require.Equal(t, test.expected, test.direction.Valid())
}
}
10 changes: 7 additions & 3 deletions pkg/postman/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import (
)

var (
ErrNoCounterpartyInfo = errors.New("no counterparty info available on packet")
ErrNoUnsealingKey = errors.New("cannot open incoming envelope without unsealing key")
ErrNoSealingKey = errors.New("cannot seal outgoing envelope without sealing key")
ErrNoCounterpartyInfo = errors.New("no counterparty info available on packet")
ErrNoUnsealingKey = errors.New("cannot open incoming envelope without unsealing key")
ErrNoSealingKey = errors.New("cannot seal outgoing envelope without sealing key")
ErrDatabaseNotReady = errors.New("no database has been set on the packet")
ErrCounterpartyNotReady = errors.New("no counterparty has been set on the packet")
ErrTransactionNotReady = errors.New("cannot resolve transaction from packet")
ErrPacketNotReady = errors.New("packet was not instantiated correctly")
)
196 changes: 3 additions & 193 deletions pkg/postman/incoming.go
Original file line number Diff line number Diff line change
@@ -1,198 +1,8 @@
package postman

import (
"database/sql"
"encoding/base64"
"fmt"
import "github.com/trisacrypto/trisa/pkg/trisa/envelope"

"github.com/trisacrypto/envoy/pkg/store/models"
"github.com/trisacrypto/envoy/pkg/ulids"
"github.com/trisacrypto/envoy/pkg/web/api/v1"
"github.com/trisacrypto/envoy/pkg/webhook"

trisa "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1"
"github.com/trisacrypto/trisa/pkg/trisa/envelope"
"github.com/trisacrypto/trisa/pkg/trisa/keys"
)

// Incoming messages are received from the remote; they can be replies to transfers
// initiated by the local node or they can be incoming messages that require a reply
// from the local node (e.g. received by the TRISA or TRP servers).
//
// Incoming messages imply that they are sealed for decryption by the local host.
type Incoming struct {
Envelope *envelope.Envelope
UnsealingKey keys.PrivateKey
packet *Packet
model *models.SecureEnvelope
original *trisa.SecureEnvelope
}

// Returns the original protocol buffers that was wrapped by the incoming message.
func (i *Incoming) Proto() *trisa.SecureEnvelope {
return i.original
}

// Returns the public key signature from the original message.
func (i *Incoming) PublicKeySignature() string {
return i.original.PublicKeySignature
}

// Returns the original transfer state on the envelope.
func (i *Incoming) TransferState() trisa.TransferState {
return i.original.TransferState
}

// Opens the incoming envelope, unsealing and decrypting it for handling.
func (i *Incoming) Open() (reject *trisa.Error, err error) {
if i.UnsealingKey == nil {
return nil, ErrNoUnsealingKey
}

if i.Envelope, reject, err = i.Envelope.Unseal(envelope.WithUnsealingKey(i.UnsealingKey)); err != nil {
if reject != nil {
return reject, err
}

i.packet.Log.Error().Err(err).Str("pks", i.PublicKeySignature()).Msg("could not unseal incoming secure envelope")
return nil, err
}

if i.Envelope, reject, err = i.Envelope.Decrypt(); err != nil {
if reject != nil {
return reject, err
}

i.packet.Log.Error().Err(err).Str("pks", i.PublicKeySignature()).Msg("could not decrypt incoming secure envelope")
return nil, err
}

return nil, nil
}

// Creates a secure envelope model to store in the database with all of the information
// that is in the envelope and in the packet. Note that the PeerInfo is expected to be
// on the packet; and if this is an incoming reply to an outgoing transaction, the
// outgoing model must already have been created and have an ID.
//
// TODO: we need to store public key information about the key that was actually used
// to decrypt the model, so that we can decrypt the model in the future.
func (i *Incoming) Model() *models.SecureEnvelope {
if i.model == nil {
// Create the incoming secure envelope model
i.model = &models.SecureEnvelope{
Direction: models.DirectionIncoming,
Remote: sql.NullString{Valid: i.packet.PeerInfo.CommonName != "", String: i.packet.PeerInfo.CommonName},
ReplyTo: ulids.NullULID{},
IsError: i.Envelope.IsError(),
EncryptionKey: i.original.EncryptionKey,
HMACSecret: i.original.HmacSecret,
ValidHMAC: sql.NullBool{},
PublicKey: sql.NullString{Valid: i.original.PublicKeySignature != "", String: i.original.PublicKeySignature},
TransferState: int32(i.original.TransferState),
Envelope: i.original,
}

if !i.model.IsError {
// Validate the HMAC but only store if its valid or not
i.model.ValidHMAC.Bool, _ = i.Envelope.ValidateHMAC()
i.model.ValidHMAC.Valid = true
}

i.model.EnvelopeID, _ = i.Envelope.UUID()
i.model.Timestamp, _ = i.Envelope.Timestamp()

// This assumes that the outgoing model has already been created!
if i.packet.Reply == DirectionIncoming {
i.model.ReplyTo = ulids.NullULID{
Valid: true, ULID: i.packet.Out.Model().ID,
}
}
}
return i.model
}

// Updates the transaction info and status based on the incoming envelope.
func (i *Incoming) UpdateTransaction() (err error) {
// Ensure that we have a counterparty
if err = i.packet.ResolveCounterparty(); err != nil {
return err
}

// If the transaction on the packet is empty, create a stub.
if i.packet.Transaction == nil {
i.packet.Transaction = &models.Transaction{}
}

// If the transaction is new and being created by the remote, add the counterparty.
// Otherwise make sure it's the same counterparty or return an error.
// TODO: Make sure it's the same counterparty or return an error
if i.packet.DB.Created() && i.packet.Request == DirectionIncoming {
if err = i.packet.DB.AddCounterparty(i.packet.Counterparty); err != nil {
return fmt.Errorf("could not associate counterparty with transaction: %w", err)
}

// Also update the transaction source as remote if this is the request
i.packet.Transaction.Source = models.SourceRemote
}

// Update the status and last update on the transaction.
timestamp, _ := i.Envelope.Timestamp()
i.packet.Transaction.Status = i.StatusFromTransferState()
i.packet.Transaction.LastUpdate = sql.NullTime{
Valid: !timestamp.IsZero(), Time: timestamp,
}

// Update the transaction in the database
if err = i.packet.DB.Update(i.packet.Transaction); err != nil {
return fmt.Errorf("could not update transaction in database: %w", err)
}

return nil
}

// Creates a webhook callback request from the incoming envelope. Note that the packet
// must have the counterparty set and that the envelope UUID has been validated.
func (i *Incoming) WebhookRequest() *webhook.Request {
request := &webhook.Request{
Timestamp: i.original.Timestamp,
HMAC: base64.RawURLEncoding.EncodeToString(i.original.Hmac),
PKS: i.original.PublicKeySignature,
TransferState: i.original.TransferState.String(),
Error: i.original.Error,
Payload: nil,
}

// Ignore any errors: we expect that this has been validated already
// TODO: configure the webhook to specify the encoding and format of the IVMS record
request.TransactionID, _ = i.Envelope.UUID()
request.Counterparty, _ = api.NewCounterparty(i.packet.Counterparty, nil)

return request
}

// StatusFromTransferState determines what the status should be based on the incoming
// message transfer state. For example, if the incoming transfer state is accepted, then
// the Transfer can be marked as completed.
func (i *Incoming) StatusFromTransferState() string {
switch ts := i.original.TransferState; ts {
case trisa.TransferStateUnspecified:
return models.StatusUnspecified
case trisa.TransferStarted:
return models.StatusReview
case trisa.TransferPending:
return models.StatusPending
case trisa.TransferReview:
return models.StatusReview
case trisa.TransferRepair:
return models.StatusRepair
case trisa.TransferAccepted:
return models.StatusAccepted
case trisa.TransferCompleted:
return models.StatusCompleted
case trisa.TransferRejected:
return models.StatusRejected
default:
panic(fmt.Errorf("unknown transfer state %s", ts.String()))
}
Envelope *envelope.Envelope
packet *Packet
}
Loading

0 comments on commit 0f5b8e4

Please sign in to comment.