p2pclient

package module
v0.0.0-...-9d75940 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2025 License: MIT Imports: 16 Imported by: 0

README

P2P Client

A lightweight Go client for communicating with a p2p-gateway over libp2p. This package handles:

  • Generating and managing DID identities from Ed25519 keys.
  • Constructing and signing canonical request payloads.
  • Sending ProxyRequest messages to a remote peer using libp2p streams.
  • Receiving and decoding ProxyResponse messages.

Canonical Header Protocol

All requests must include an Authorization header that cryptographically proves the request’s origin and prevents tampering.

1. Canonical Payload

A deterministic payload string is constructed as follows:

  • METHOD: The HTTP-like method, uppercased (e.g., GET, POST).
  • PATH: The request path (e.g., /v1/resource).
  • SHA256(BODY): A lowercase hex-encoded SHA-256 digest of the request body.
  • NONCE: A unique client-generated string to prevent replay attacks.
  • TIMESTAMP: Unix timestamp (in seconds) when the request was created.
2. Signature
  • The canonical payload is signed using the client’s Ed25519 private key.
  • The resulting signature is encoded in Base64.
3. Authorization Header

The final header is constructed as:

Authorization: DID did🔑...;sig=<base64_signature>;ts=;nonce=

4. Verification Process

On the Serverista API Gateway, the request is verified by:

  1. Reconstructing the canonical payload.
  2. Extracting the DID from the header and resolving the public key.
  3. Verifying the Ed25519 signature.
  4. Validating the timestamp is within acceptable skew.
  5. Checking that the nonce has not been reused.

If any step fails, the request is rejected.

Security Properties

  • Integrity: The signed payload ensures no tampering with method, path, body, or metadata.
  • Authentication: Only the holder of the DID private key can generate valid signatures.
  • Replay Protection: Timestamps and nonces ensure requests cannot be reused.

Features

  • Canonical Request Signing: Builds deterministic payloads for signature generation.
  • DID Support: Automatically derives a DID from an Ed25519 public key.
  • Signed Requests: Canonical request signing using Ed25519 signatures.
  • Proxy Communication: Serialize, send, and receive requests/responses over libp2p streams.
  • Timeout Handling: Built-in request timeout for reliability.

Installation

go get github.com/serverista/p2p-client

Usage

1. Create a client
import (
    "crypto/ed25519"
    "github.com/libp2p/go-libp2p"
    "github.com/serverista/p2p-client"
)

func main() {
    // Generate a new Ed25519 key pair
    pub, priv, _ := ed25519.GenerateKey(nil)

    // Start a libp2p host
    h, _ := libp2p.New()

    // Proxy peer multiaddress (example)
    proxyAddr := "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooW..."

    // Create client
    client, err := p2pclient.New(h, priv, proxyAddr)
    if err != nil {
        panic(err)
    }

    println("Client DID:", client.DID())
}
2. Send a request
ctx := context.Background()
resp, err := client.RawRequest(ctx, "GET", "/v1/services", nil, "random-nonce", time.Now().Unix())
if err != nil {
    panic(err)
}

fmt.Printf("Response status: %d\n", resp.Status)
fmt.Printf("Response body: %s\n", string(resp.Body))

Data Structures

ProxyRequest
type ProxyRequest struct {
    Method  string            `json:"method"`
    Path    string            `json:"path"`
    Headers map[string]string `json:"headers,omitempty"`
    Body    []byte            `json:"body,omitempty"`
}
ProxyResponse
type ProxyResponse struct {
    Status  int               `json:"status"`
    Headers map[string]string `json:"headers,omitempty"`
    Body    []byte            `json:"body,omitempty"`
    Error   string            `json:"error,omitempty"`
}

Documentation

Index

Constants

View Source
const (
	// ServiceStart starts a service
	ServiceStart = "START"
	// ServiceShutdown stops a service
	ServiceShutdown = "SHUTDOWN"
	// ServiceRestart restarts a service
	ServiceRestart = "RESTART"
	// ServiceReinstall reinstalls the service to its initial state
	ServiceReinstall = "REINSTALL"
	// ServiceDelete destroys the service
	ServiceDelete = "DELETE"
)
View Source
const (
	Ubuntu24_04 = "Ubuntu 24.04 LTS"
	Ubuntu22_04 = "Ubuntu 22.04 LTS"
	Debian11    = "Debian 11"
)
View Source
const ProtocolID = "/serverista-proxy/1.0.0"

ProtocolID to communicate with p2p-gateway.

Variables

View Source
var (
	// PlansEndpoint is a public endpoint
	PlansEndpoint = Endpoint{
		Method: "GET",
		Uri:    "/v1/plans",
	}

	// ListUserServicesEndpoint
	ListUserServicesEndpoint = Endpoint{
		Method: "GET",
		Uri:    "/v1/services",
	}

	// GetUserServiceEndpoint
	GetUserServiceEndpoint = Endpoint{
		Method: "GET",
		Uri:    "/v1/services/%d",
	}

	// CreateServicesEndpoint
	CreateServicesEndpoint = Endpoint{
		Method: "POST",
		Uri:    "/v1/services",
	}

	// ManageServiceEndpoint
	ManageServiceEndpoint = Endpoint{
		Method: "POST",
		Uri:    "/v1/services/%d/management",
	}
)
View Source
var (
	ErrNoPermissions = errors.New("no permission")
)

Functions

func Ed25519PubKeyToDID

func Ed25519PubKeyToDID(pubKey ed25519.PublicKey) (string, error)

Ed25519PubKeyToDID gets the DID from a public key.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client hold the structures to sign messages and communicate with the p2p-gateway.

func New

func New(h host.Host, privKey ed25519.PrivateKey) (*Client, error)

New creates a new client given a libp2p host which will be used to connect and send a message to the remote protocol. in the params you can use any ed25519 private key to sign the messages. This should be the private key that the DID was derived from and entered in serverista IAM DID Key.

func (*Client) CreateServices

func (c *Client) CreateServices(ctx context.Context, request CreateServiceRequest, nonce string) ([]Service, error)

CreateServices creates a new service given an optional custom name plan id, os type, number of instances and a public key.

func (*Client) DID

func (c *Client) DID() string

DID returns the DID for this client.

func (*Client) GetService

func (c *Client) GetService(ctx context.Context, id uint, nonce string) (*Service, error)

Get a specific service.

func (*Client) ListServices

func (c *Client) ListServices(ctx context.Context, nonce string) ([]Service, error)

ListServices returns a list of available services.

func (*Client) Plans

func (c *Client) Plans(ctx context.Context) ([]Plan, error)

Plans returns a list of available plans.

func (*Client) RawRequest

func (c *Client) RawRequest(ctx context.Context, method, path string, body []byte, nonce string, ts int64) (*ProxyResponse, error)

RawRequest sends a raw request given the method, path, body and other args

func (*Client) ServiceAction

func (c *Client) ServiceAction(ctx context.Context, action ServiceAction, id uint, nonce string) error

ServiceAction performs an action such as start, shutdown, restart, reinstall and delete on a service.

func (*Client) SetGatewayAddr

func (c *Client) SetGatewayAddr(addr string) error

SetGatewayAddr sets the gateway address.

type CreateServiceRequest

type CreateServiceRequest struct {
	PlanID       uint   `json:"plan_id"`
	OS           Os     `json:"os"`
	Amount       int    `json:"amount"`
	SSHPublicKey string `json:"ssh_public_key"`
	Name         string `json:"name"`
}

CreateServiceRequest is the service creation payload.

type Endpoint

type Endpoint struct {
	Method string
	Uri    string
}

type Os

type Os string

type Plan

type Plan struct {
	ID          uint            `json:"id"`
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Type        PlanType        `json:"type"`
	Data        json.RawMessage `json:"data"`
	Price       float64         `json:"price"`
}

type PlanType

type PlanType string
const (
	VPSPlan       PlanType = "VPS"
	DedicatedPlan PlanType = "Dedicated Server"
	OtherPlan     PlanType = "Other"
)

type ProxyRequest

type ProxyRequest struct {
	Method  string            `json:"method"`
	Path    string            `json:"path"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    []byte            `json:"body,omitempty"`
}

ProxyRequest represents the payload to send to the p2p-gateway.

type ProxyResponse

type ProxyResponse struct {
	Status  int               `json:"status"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    []byte            `json:"body,omitempty"`
	Error   string            `json:"error,omitempty"`
}

ProxyResponse is the response from p2p-gateway.

type Service

type Service struct {
	ID              uint            `json:"id"`
	AccountID       uint            `json:"account_id"`
	UserID          uint            `json:"user_id"`
	PlanID          uint            `json:"plan_id"`
	Data            json.RawMessage `json:"data"`
	Status          string          `json:"status"`
	UserDefinedName string          `json:"user_defined_name"`
	FirewallEnabled bool            `json:"firewall_enabled"`
	UniqueID        uint            `json:"unique_id"`
	IP              string          `json:"ip"`
	CreatedAt       time.Time       `json:"created_at"`
	UpdatedAt       time.Time       `json:"updated_at"`
}

type ServiceAction

type ServiceAction string

ServiceAction represents the management action on a service

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL