[go: up one dir, main page]

corteq-onepassword 0.1.2

Secure 1Password SDK wrapper with FFI bindings for Rust applications
docs.rs failed to build corteq-onepassword-0.1.2
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: corteq-onepassword-0.1.1

corteq-onepassword

This is a 1Password SDK wrapper for Rust applications. This does NOT use the 1Password CLI! 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"

Installing from crates.io

When you install this crate from crates.io, the native library is not included (due to crates.io's 10MB size limit). The library is automatically downloaded during cargo build:

  1. First build - Downloads the library from PyPI (~15-18MB)
  2. Subsequent builds - Uses the cached library in your target directory

Requirements

  • Network access during first build to:
    • pypi.org (package metadata)
    • files.pythonhosted.org (library download)

Offline Builds

For environments without network access:

  1. Download the library on a connected machine:

    # Download for your platform (example for Linux x86_64)
    curl -L "https://pypi.org/pypi/onepassword-sdk/json" | \
      jq -r '.urls[] | select(.filename | contains("manylinux")) | .url' | \
      head -1 | xargs curl -L -o sdk.whl
    unzip sdk.whl "onepassword/*.so" -d extracted/
    
  2. Set the library path before building:

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

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);
    }
}

Troubleshooting

"Could not find libop_uniffi_core.so"

This error occurs when the native library cannot be located at runtime.

Solutions:

  1. Rebuild the crate - The build script downloads the library automatically:

    cargo clean && cargo build
    
  2. Check network access - The build script needs to reach PyPI:

    curl -I https://pypi.org/pypi/onepassword-sdk/json
    
  3. Set custom path - If you have the library elsewhere:

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

Build Script Download Failed

If the automatic download fails during build:

  1. Check your network connection
  2. Check if PyPI is accessible: curl https://pypi.org
  3. Try setting ONEPASSWORD_SKIP_DOWNLOAD=1 and provide the library manually via ONEPASSWORD_LIB_PATH

License

MIT