pgxmagic

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2025 License: MIT Imports: 19 Imported by: 0

README

pgxmagic

pgxmagic is a pgx-driven implementation of certmagic.Storage designed to fit in to an existing golang+pgx codebase.

Godoc

Features

  1. Uses an existing pgxpool: No need to maintain a separate database connection just for CertMagic
  2. Session-level advisory locks: Uses pg_try_advisory_lock to handle distributed locking
  3. Migrations are optional: Convenience method to do this for you, but only if you want.
  4. Focus on correctness: Complete implementation of certmagic.Storage with a full test suite.
  5. Optional encryption: An EncryptedStorage adapter exists to allow for encryption of values.

Usage

Create table

Optionally, add the following as a database migration. If you'd prefer not to, you can call storage.Migrate() to do this idempotently instead.

CREATE TABLE IF NOT EXISTS certmagic_data (
    key TEXT PRIMARY KEY,
    value bytea,
    modified timestamptz DEFAULT NOW()
);
Without encryption

Certificates are stored in the clear in the database

storage := pgxmagic.NewStorage(pool)
err := storage.Migrate(context.Background()) // Optional, or manually create table.
With encryption

Certificates are stored encrypted in the database -- in this case, encrypted with AES-256.

storage := pgxmagic.NewEncryptedStorage(
    pool,
    pgxmagic.NewAES256Encrypter("correct horse battery staple"),
)

Not supported yet

  1. No Caddy plugin: This was built to slot into an existing go codebase that's using CertMagic directly. A plugin could easily be created if needed though.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AES256Encrypter

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

func NewAES256Encrypter

func NewAES256Encrypter(key string) *AES256Encrypter

NewAES256Encrypter returns an Encrypter that uses AES-256. If "key" is not 32 bytes, it is hashed into a 32 byte value using sha256.

func (*AES256Encrypter) Decrypt

func (e *AES256Encrypter) Decrypt(src []byte) ([]byte, error)

func (*AES256Encrypter) Encrypt

func (e *AES256Encrypter) Encrypt(src []byte) []byte

type EncryptedStorage

type EncryptedStorage struct {
	certmagic.Storage
	// contains filtered or unexported fields
}

func NewEncryptedStorage

func NewEncryptedStorage(source certmagic.Storage, encrypter Encrypter) *EncryptedStorage

func (*EncryptedStorage) Load

func (e *EncryptedStorage) Load(ctx context.Context, key string) ([]byte, error)

func (*EncryptedStorage) Stat

func (*EncryptedStorage) Store

func (e *EncryptedStorage) Store(ctx context.Context, key string, value []byte) error

type Encrypter

type Encrypter interface {
	Encrypt([]byte) []byte
	Decrypt([]byte) ([]byte, error)
}

type Locker

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

Locker implements a basic distributed lock using session-level advisory locks, which have useful properties: (1) Locks are auto-released if a connection session ends, e.g. the client crashes, and (2) Locking and unlocking don't write, and so rule out any risk of table bloat.

As with any distributed lock, there are trade-offs: Locks can end up "held" by multiple instances (from the perspective of that instance) if the connection to the postgres server is lost after acquisition, as locks are auto-released at the end of a connection session, and there is no way to know when a session has failed short of trying to use it.

In the real world this should be fine -- CertMagic uses locks to reduce redundant expensive work rather than to prevent data races, and postgres connections tend to be extremely stable.

func NewLocker

func NewLocker(pool *pgxpool.Pool) *Locker

func (*Locker) Lock

func (l *Locker) Lock(ctx context.Context, name string) error

Lock acquires the lock 'name', blocking until it can do so. If the lock is already held, its status is polled every 1s.

func (*Locker) TryLock

func (l *Locker) TryLock(ctx context.Context, name string) (acquired bool, err error)

func (*Locker) Unlock

func (l *Locker) Unlock(ctx context.Context, name string) error

type Storage

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

func NewStorage

func NewStorage(pool *pgxpool.Pool) *Storage

func (*Storage) Delete

func (s *Storage) Delete(ctx context.Context, key string) error

Delete "deletes the named key. If the name is a directory (i.e. prefix of other keys), all keys prefixed by this key should be deleted. An error should be returned only if the key still exists when the method returns."

func (*Storage) Exists

func (s *Storage) Exists(ctx context.Context, key string) bool

Exists "returns true if the key exists either as a directory (prefix to other keys) or a file, and there was no error checking."

func (*Storage) List

func (s *Storage) List(ctx context.Context, path string, recursive bool) ([]string, error)

List returns all keys in the given path. If recursive is true, non-terminal keys will be enumerated (i.e. "directories" should be walked); otherwise, only keys prefixed exactly by prefix will be listed.

func (*Storage) Load

func (s *Storage) Load(ctx context.Context, key string) ([]byte, error)

Load "Uses exact key match because "Keys passed into Load and Store always have "file" semantics"

func (*Storage) Lock

func (s *Storage) Lock(ctx context.Context, name string) error

func (*Storage) Migrate

func (s *Storage) Migrate(ctx context.Context) error

Migrate can be manually invoked to ensure the certmagic_data table exists. It is not invoked automatically to allow for table setup to be part of whatever existing migration system you might have. Idempotent, so can be run as part of initialization.

Table structure matches that of travisjeffery/certmagic-sqlstorage and yroc92/postgres-storage

func (*Storage) Stat

func (s *Storage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error)

func (*Storage) Store

func (s *Storage) Store(ctx context.Context, key string, value []byte) error

Store "Uses exact key match because "Keys passed into Load and Store always have "file" semantics"

func (*Storage) Unlock

func (s *Storage) Unlock(ctx context.Context, name string) error

Jump to

Keyboard shortcuts

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