corteq-onepassword 0.1.1

Secure 1Password SDK wrapper with FFI bindings for Rust applications
Documentation
corteq-onepassword-0.1.1 has been yanked.

corteq-onepassword

This is a 1Password SDK wrapper for Rust applications. Providing a safe interface to 1Password secrets using FFI bindings for the official 1Password SDK Core library.

Features

  • Secure by default - Secrets wrapped in SecretString with automatic memory zeroization
  • Simple API - Retrieve secrets with a single function call
  • Thread-safe - Client is Send + Sync for use in async applications
  • Builder pattern - Flexible configuration with sensible defaults
  • Type-safe - Compile-time guarantees for secret handling

Quick Start

use corteq_onepassword::{OnePassword, ExposeSecret};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create client from OP_SERVICE_ACCOUNT_TOKEN environment variable
    let client = OnePassword::from_env()?
        .integration("my-app", "1.0.0") // Name of your app for audit purposes on 1password side
        .connect()
        .await?;

    // Resolve a secret
    let api_key = client.secret("op://vault/item/api-key").await?;

    // Use the secret (expose only when needed)
    println!("API key length: {}", api_key.expose_secret().len());

    Ok(())
}

Installation

Add to your Cargo.toml:

[dependencies]
corteq-onepassword = "0.1"

Authentication

This crate uses 1Password service account tokens. Personal account tokens are not supported.

Environment Variable (Recommended)

export OP_SERVICE_ACCOUNT_TOKEN="ops_..."
let client = OnePassword::from_env()?.connect().await?;

Explicit Token (Not recommended for production use!)

let client = OnePassword::from_token("ops_...")
    .connect()
    .await?;

Secret References

Secrets are referenced using the op://vault/item/field format:

  • op://Production/Database/password - Simple reference
  • op://Production/Database/admin/password - Section-scoped reference

See https://developer.1password.com/docs/cli/secret-reference-syntax/

API

Single Secret

let api_key = client.secret("op://prod/stripe/api-key").await?;

Batch Resolution

let secrets = client.secrets(&[
    "op://prod/db/host",
    "op://prod/db/user",
    "op://prod/db/pass",
]).await?;

let host = secrets[0].expose_secret();
let user = secrets[1].expose_secret();
let pass = secrets[2].expose_secret();

Named Resolution

let secrets = client.secrets_named(&[
    ("host", "op://prod/db/host"),
    ("user", "op://prod/db/user"),
    ("pass", "op://prod/db/pass"),
]).await?;

let host = secrets.get("host").unwrap().expose_secret();

Sharing the Client

The client is thread-safe and can be shared via Arc:

use std::sync::Arc;

let client = Arc::new(OnePassword::from_env()?.connect().await?);

let client1 = Arc::clone(&client);
let client2 = Arc::clone(&client);

tokio::join!(
    async move { client1.secret("op://vault/item/field1").await },
    async move { client2.secret("op://vault/item/field2").await },
);

Feature Flags

  • blocking - Enable synchronous API via connect_blocking()
  • tracing - Enable tracing spans for observability
[dependencies]
corteq-onepassword = { version = "0.1", features = ["blocking"] }

Platform Support

Platform Architecture Status
Linux x86_64 ✅ Supported
Linux aarch64 ✅ Supported
macOS x86_64 ✅ Supported
macOS aarch64 ✅ Supported
Windows - ❌ Not supported
Alpine - ❌ Not supported (musl)

Build Process

The build script looks for the 1Password SDK native library in this order:

  1. ONEPASSWORD_LIB_PATH - Custom path via environment variable
  2. Bundled libraries - Pre-downloaded in src/libs/{platform}/
  3. PyPI download - Automatic download at build time (requires network)

Bundled Libraries (Git LFS)

This repository includes pre-downloaded libraries for all supported platforms in src/libs/:

src/libs/
├── linux-x86_64/libop_uniffi_core.so      (~18MB)
├── linux-aarch64/libop_uniffi_core.so     (~17MB)
├── macos-x86_64/libop_uniffi_core.dylib   (~16MB)
└── macos-aarch64/libop_uniffi_core.dylib  (~15MB)

These files are tracked with Git LFS due to their size. After cloning:

git lfs pull  # Download the actual library files

Why bundle libraries?

  • crates.io size limit: crates.io enforces a 10MB limit per crate, so we can't include libraries there
  • Offline builds: No network access required when using bundled libraries
  • Build reproducibility: Known library versions with verified checksums

Refreshing Bundled Libraries

To update the bundled libraries (e.g., for a new SDK version):

./scripts/download-libs.sh

This script fetches all 4 platform libraries from PyPI with SHA256 verification.

PyPI Fallback

If bundled libraries are not found, the build script downloads from PyPI:

  1. Fetches metadata from PyPI's JSON API
  2. Downloads the appropriate wheel for your target platform
  3. Verifies the SHA256 checksum
  4. Extracts the native library

Network Requirements

When downloading from PyPI, network access is required to:

  • pypi.org - Package metadata and checksums
  • files.pythonhosted.org - Library downloads

Custom Library Path

For custom library locations:

export ONEPASSWORD_LIB_PATH="/path/to/libop_uniffi_core.so"

Security

  • Tokens wrapped in SecretString and zeroized on drop
  • Secrets never appear in logs or error messages
  • Debug implementations redact sensitive data
  • Native library verified via SHA256 checksum at build time

Error Handling

All errors are typed and implement std::error::Error:

use corteq_onepassword::Error;

match client.secret("op://vault/item/field").await {
    Ok(secret) => { /* use secret */ },
    Err(Error::SecretNotFound { reference }) => {
        eprintln!("Secret not found: {}", reference);
    },
    Err(Error::AccessDenied { vault }) => {
        eprintln!("Access denied to vault: {}", vault);
    },
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}

License

MIT