ocibundle

package module
v0.1.0 Latest Latest
Warning

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

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

README

OCI Bundle Distribution Module

A secure, extensible Go library for distributing file bundles as OCI artifacts using ORAS. This module provides a simple API for pushing directories to and pulling archives from OCI registries.

Features

  • Secure by Default: Prevents common vulnerabilities like path traversal and zip bombs
  • Extensible: Support for multiple archive formats via interfaces
  • Flexible Authentication: Multiple auth mechanisms (Docker config, static, custom functions)
  • Streaming: Handles large files without memory exhaustion
  • ORAS Integration: Uses ORAS v2 for OCI artifact operations
  • Progress Reporting: Built-in progress callbacks for long operations
  • Retry Logic: Automatic retry with exponential backoff
  • Thread Safe: Safe for concurrent use

Installation

go get github.com/input-output-hk/catalyst-forge-libs/oci

Quick Start

Basic Usage
package main

import (
    "context"
    "log"

    "github.com/input-output-hk/catalyst-forge-libs/oci"
)

func main() {
    ctx := context.Background()

    // Create a client with default settings
    client, err := ocibundle.New()
    if err != nil {
        log.Fatal(err)
    }

    // Push a directory to an OCI registry
    err = client.Push(ctx, "./my-files", "ghcr.io/myorg/bundle:v1.0.0")
    if err != nil {
        log.Fatal(err)
    }

    // Pull and extract an OCI artifact
    err = client.Pull(ctx, "ghcr.io/myorg/bundle:v1.0.0", "./output")
    if err != nil {
        log.Fatal(err)
    }
}

Examples

The examples/ directory contains runnable examples:

Advanced Usage

Custom Configuration
// Create client with custom configuration
client, err := ocibundle.NewWithOptions(
    // Security limits
    ocibundle.WithMaxFiles(10000),
    ocibundle.WithMaxSize(1*1024*1024*1024), // 1GB
    ocibundle.WithMaxFileSize(100*1024*1024), // 100MB per file

    // Authentication
    ocibundle.WithStaticAuth("registry.example.com", "user", "token"),

    // HTTP configuration
    ocibundle.WithHTTP(true, false, []string{"localhost:5000"}),
)
Push with Metadata
// Push with annotations and platform information
annotations := map[string]string{
    "org.opencontainers.image.title":       "My Application Bundle",
    "org.opencontainers.image.description": "Production application files",
    "org.opencontainers.image.version":     "2.1.0",
    "org.opencontainers.image.vendor":      "My Company",
    "com.example.build-id":                 "build-12345",
    "com.example.git-commit":               "abc123def456",
}

err := client.Push(ctx, "./dist", "ghcr.io/myorg/app:v2.1.0",
    ocibundle.WithAnnotations(annotations),
    ocibundle.WithPlatform("linux/amd64"),
    ocibundle.WithProgressCallback(func(current, total int64) {
        percentage := float64(current) / float64(total) * 100
        fmt.Printf("\rUpload progress: %.1f%%", percentage)
    }),
)
Pull with Security Options
// Pull with security constraints
err := client.Pull(ctx, "ghcr.io/myorg/bundle:v1.0.0", "./app",
    // Security limits
    ocibundle.WithMaxFiles(5000),
    ocibundle.WithMaxSize(500*1024*1024), // 500MB
    ocibundle.WithMaxFileSize(50*1024*1024), // 50MB per file

    // Extraction options
    ocibundle.WithPullPreservePermissions(false), // Sanitize permissions
    ocibundle.WithPullStripPrefix("bundle-root/"), // Remove prefix
    ocibundle.WithPullAllowHiddenFiles(false), // Reject hidden files

    // Retry configuration
    ocibundle.WithPullMaxRetries(5),
    ocibundle.WithPullRetryDelay(3*time.Second),
)

Authentication

The module uses ORAS's native authentication system, providing robust support for Docker's standard authentication mechanisms.

By default, the client uses ORAS's built-in Docker credential chain:

// Uses ~/.docker/config.json and credential helpers automatically
client, err := ocibundle.New()

This automatically supports:

  • Docker config files (~/.docker/config.json)
  • Credential helpers (osxkeychain, pass, desktop, wincred, etc.)
  • Multiple registries with different authentication methods
  • Token refresh and OAuth2 flows where supported

Example ~/.docker/config.json:

{
  "auths": {
    "ghcr.io": {
      "auth": "dXNlcjpwYXNzd29yZA=="
    },
    "docker.io": {
      "auth": "dXNlcjpwYXNzd29yZA=="
    }
  },
  "credHelpers": {
    "registry.example.com": "desktop"
  },
  "credsStore": "osxkeychain"
}
Static Credentials Override

For specific registries, override the default chain:

client, err := ocibundle.NewWithOptions(
    ocibundle.WithStaticAuth("ghcr.io", "username", "personal-access-token"),
)
Custom Credential Function

For advanced scenarios requiring custom credential logic:

import "oras.land/oras-go/v2/registry/remote/auth"

customCreds := func(ctx context.Context, registry string) (auth.Credential, error) {
    switch registry {
    case "ghcr.io":
        return auth.Credential{Username: "user", Password: "token"}, nil
    case "registry.company.com":
        return getCompanyCredentials(ctx, registry)
    default:
        return auth.Credential{}, nil // Anonymous access
    }
}

client, err := ocibundle.NewWithOptions(
    ocibundle.WithCredentialFunc(customCreds),
)
HTTP and Insecure Registries
// HTTP-only registry (local development)
client, err := ocibundle.NewWithOptions(
    ocibundle.WithAllowHTTP(),
)

// Insecure registry (testing only)
client, err := ocibundle.NewWithOptions(
    ocibundle.WithInsecureHTTP(),
)

Security

  • Threats Addressed:

    • Path traversal and absolute path injection
    • Symlink-based directory escape
    • Zip/decompression bombs (file count and total size)
    • Oversized individual files
    • Dangerous permission bits (setuid/setgid)
  • Validators and Enforcement:

    • internal/validate.PathTraversalValidator rejects absolute paths, .., encoded traversal variants, and validates symlink targets against the extraction root.
    • SizeValidator enforces per-file and total uncompressed size limits.
    • FileCountValidator enforces file-count limits to prevent resource exhaustion.
    • PermissionSanitizer rejects files with setuid/setgid bits and sanitizes permissions when writing.
    • ValidatorChain composes validators and fails fast on the first violation.
  • Safe Defaults:

    • DefaultExtractOptions: 10,000 files, 1GB total, 100MB per file, permissions sanitized, hidden files rejected.
  • Testing & Verification:

    • Unit tests for each validator and extraction behavior.
    • Fuzz tests for path validation and size validator to ensure robustness against arbitrary inputs.
    • Malicious archive generators (OWASP inspired) validate that extraction blocks path traversal and symlink attacks.
  • Credentials Handling:

    • Authentication is delegated to ORAS; the library never logs usernames, passwords, or tokens.
    • Error messages avoid echoing sensitive values; only generic messages are returned (e.g., "static password required").

Error Handling

The module provides detailed error information through the BundleError type:

err := client.Push(ctx, sourceDir, reference)
if err != nil {
    var bundleErr *ocibundle.BundleError
    if errors.As(err, &bundleErr) {
        // Handle specific error types
        if bundleErr.IsAuthError() {
            log.Printf("Authentication failed for %s", bundleErr.Reference)
        } else if bundleErr.IsSecurityError() {
            log.Printf("Security violation in %s", bundleErr.Reference)
        }
    }
    return fmt.Errorf("push failed: %w", err)
}

API Reference

Testing

The module includes comprehensive tests:

# Run all tests
go test ./lib/oci/...

# Run with coverage
go test -cover ./lib/oci/...

# Run integration tests (requires registry)
go test -tags=integration ./lib/oci/...

# Run specific test
go test -run TestClient_Push ./lib/oci/
Test Infrastructure
  • Unit Tests: Comprehensive coverage of all components
  • Integration Tests: End-to-end testing with test registries
  • Security Tests: Malicious archive testing
  • Benchmark Tests: Performance validation

Filesystem Injection and In-Memory Testing

The client and archiver operate over an abstract filesystem interface so you can swap the backing store.

  • Default: OS filesystem (rooted at "/").
  • Custom: Inject any implementation (e.g., in-memory) for tests.
import (
    billyfs "github.com/input-output-hk/catalyst-forge-libs/fs/billy"
    ocibundle "github.com/input-output-hk/catalyst-forge-libs/oci"
)

// In-memory filesystem for fast, isolated tests
mem := billyfs.NewInMemoryFS()

client, err := ocibundle.NewWithOptions(
    ocibundle.WithFilesystem(mem),
)
if err != nil { /* handle */ }

// Use the same FS with the archiver
archiver := ocibundle.NewTarGzArchiverWithFS(mem)

// Build fixture
_ = mem.MkdirAll("/src", 0o755)
_ = mem.WriteFile("/src/hello.txt", []byte("hi"), 0o644)

// Archive and extract entirely in-memory
var buf bytes.Buffer
_ = archiver.Archive(context.Background(), "/src", &buf)
_ = archiver.Extract(context.Background(), &buf, "/dst", ocibundle.DefaultExtractOptions)

b, _ := mem.ReadFile("/dst/hello.txt")
Unit Tests: Avoiding Network Dependencies

Unit tests should not perform real network calls. Inject a mocked ORAS client and disable retries to keep tests fast and deterministic:

mock := &mocks.ClientMock{
    PushFunc: func(ctx context.Context, ref string, d *oras.PushDescriptor, a *oras.AuthOptions) error { return fmt.Errorf("simulated push error") },
    PullFunc: func(ctx context.Context, ref string, a *oras.AuthOptions) (*oras.PullDescriptor, error) { /* return small tar.gz */ return desc, nil },
}
client, _ := ocibundle.NewWithOptions(
    ocibundle.WithORASClient(mock),
    ocibundle.WithFilesystem(billyfs.NewInMemoryFS()),
)
// Disable retries in tests that expect error paths
_ = client.Push(ctx, "/src", "example/repo:tag", ocibundle.WithMaxRetries(0), ocibundle.WithRetryDelay(0))

Registry Compatibility

OCI Compliance
  • ✅ OCI Distribution Specification v1.1
  • ✅ OCI Image Specification
  • ✅ ORAS artifact types
  • ✅ Standard media types

Performance

Benchmarks
# Run performance benchmarks
go test -bench=. ./lib/oci/

# Memory profiling
go test -bench=. -memprofile=mem.out ./lib/oci/
go tool pprof mem.out

# CPU profiling
go test -bench=. -cpuprofile=cpu.out ./lib/oci/
go tool pprof cpu.out
Performance Characteristics
  • Memory Usage: Constant memory for any file size (streaming)
  • Large Files: Handles files > 10GB without memory exhaustion
  • Concurrent Operations: Thread-safe for multiple simultaneous operations
  • Network Efficiency: Automatic retry and connection reuse
  • Registry Optimization: Request batching and connection pooling

Contributing

This module follows Go best practices and uses Test-Driven Development (TDD).

Development Setup
# Clone the repository
git clone https://github.com/input-output-hk/catalyst-forge-ai.git
cd catalyst-forge-ai/lib/oci

# Install dependencies
go mod download

# Run tests
go test ./...

# Generate mocks (if needed)
go generate ./...
Code Quality
# Run linters
golangci-lint run

# Format code
gofmt -w .

# Check for security issues
gosec ./...

# Run static analysis
staticcheck ./...

Architecture

The module follows clean architecture principles:

Core Components
  • Client: Main entry point with push/pull operations
  • Archiver: Interface for different compression formats (default: tar.gz)
  • Validator: Interface for security validation with chain pattern
  • Options: Functional options pattern for configuration
Key Interfaces
// Archiver handles compression/decompression
type Archiver interface {
    Archive(ctx context.Context, sourceDir string, output io.Writer) error
    ArchiveWithProgress(ctx context.Context, sourceDir string, output io.Writer, progress func(current, total int64)) error
    Extract(ctx context.Context, input io.Reader, targetDir string, opts ExtractOptions) error
    MediaType() string
}

// Validator checks for security issues
type Validator interface {
    ValidatePath(path string) error
    ValidateFile(info FileInfo) error
    ValidateArchive(stats ArchiveStats) error
}

License

See LICENSE for license information.

Documentation

Overview

Package ocibundle provides OCI bundle distribution functionality. This file contains archive interface and implementations.

Package ocibundle provides OCI bundle distribution functionality. This file contains the tar.gz implementation of the Archiver interface.

Package ocibundle provides OCI bundle distribution functionality. This file contains the main client interface and implementation.

Package ocibundle provides OCI bundle distribution functionality. This file contains domain-specific error types for OCI bundle operations.

Package ocibundle provides OCI bundle distribution functionality. This file contains functional options for configuration.

Package ocibundle provides OCI bundle distribution functionality. This file contains security validators and constraints for safe archive handling.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrAuthenticationFailed indicates that authentication with the OCI registry failed.
	// This could be due to invalid credentials, expired tokens, or authentication service issues.
	ErrAuthenticationFailed = errors.New("authentication failed")

	// ErrRegistryUnreachable indicates that the OCI registry could not be contacted.
	// This could be due to network issues, registry being down, or DNS resolution failures.
	ErrRegistryUnreachable = errors.New("registry unreachable")

	// ErrInvalidReference indicates that the provided OCI reference is malformed or invalid.
	// This includes invalid registry names, malformed tags, or unsupported reference formats.
	ErrInvalidReference = errors.New("invalid OCI reference")

	// ErrSecurityViolation indicates that a security constraint was violated during operation.
	// This includes path traversal attempts, file size limits exceeded, or other security checks.
	ErrSecurityViolation = errors.New("security constraint violated")

	// ErrArchiveCorrupted indicates that the archive file is corrupted or invalid.
	// This could be due to transmission errors, storage corruption, or unsupported archive formats.
	ErrArchiveCorrupted = errors.New("archive corrupted or invalid")

	// ErrConfigNotFound indicates that the Docker config file could not be found.
	// This occurs when the config file path does not exist or is not readable.
	ErrConfigNotFound = errors.New("docker config file not found")

	// ErrConfigInvalid indicates that the Docker config file is malformed or invalid.
	// This occurs when the config file contains invalid JSON or missing required fields.
	ErrConfigInvalid = errors.New("docker config file is invalid")
)

Sentinel errors for different failure modes. These are used to identify specific types of failures in OCI bundle operations. They can be checked using errors.Is() for error handling and testing.

View Source
var DefaultExtractOptions = ExtractOptions{
	MaxFiles:      10000,
	MaxSize:       1 * 1024 * 1024 * 1024,
	MaxFileSize:   100 * 1024 * 1024,
	StripPrefix:   "",
	PreservePerms: false,
}

DefaultExtractOptions provides safe defaults for archive extraction. These defaults enforce security constraints to prevent common attacks: - MaxFiles: 10000 (prevents file count attacks) - MaxSize: 1GB (prevents resource exhaustion) - MaxFileSize: 100MB (prevents large individual files) - PreservePerms: false (sanitizes permissions)

Functions

This section is empty.

Types

type ArchiveStats

type ArchiveStats struct {
	// TotalFiles is the total number of files in the archive
	TotalFiles int

	// TotalSize is the total uncompressed size of all files in bytes
	TotalSize int64
}

ArchiveStats represents archive statistics used for security validation. This struct provides summary information about the entire archive to enable validation of aggregate limits and constraints.

type Archiver

type Archiver interface {
	// Archive creates an archive from a directory.
	// The sourceDir parameter specifies the directory to archive.
	// The output parameter is where the archive data is written.
	// Returns an error if the archiving process fails.
	Archive(ctx context.Context, sourceDir string, output io.Writer) error

	// ArchiveWithProgress creates an archive from a directory with progress reporting.
	// The sourceDir parameter specifies the directory to archive.
	// The output parameter is where the archive data is written.
	// The progress callback is called periodically during archiving.
	// Returns an error if the archiving process fails.
	ArchiveWithProgress(
		ctx context.Context,
		sourceDir string,
		output io.Writer,
		progress func(current, total int64),
	) error

	// Extract expands an archive to a directory.
	// The input parameter provides the archive data to read.
	// The targetDir parameter specifies where to extract the files.
	// The opts parameter controls extraction behavior and security constraints.
	// Returns an error if the extraction process fails.
	Extract(ctx context.Context, input io.Reader, targetDir string, opts ExtractOptions) error

	// MediaType returns the OCI media type for this archive format.
	// This is used as the artifact type when pushing to OCI registries.
	// Examples: "application/vnd.oci.image.layer.v1.tar+gzip"
	MediaType() string
}

Archiver handles compression/decompression of file bundles. Implementations of this interface provide different archive formats such as tar.gz, zip, etc. for bundling files for OCI distribution.

type BundleError

type BundleError struct {
	// Op describes the operation that failed (e.g., "push", "pull", "extract").
	Op string

	// Reference is the OCI reference being processed when the error occurred.
	// This could be a full reference like "ghcr.io/org/repo:v1.0.0".
	Reference string

	// Err is the underlying error that caused this BundleError to be created.
	// This preserves the original error context and allows for proper error wrapping.
	Err error
}

BundleError provides detailed context about OCI bundle operation failures. It wraps underlying errors with additional context specific to OCI bundle operations, including the operation type and OCI reference being processed.

BundleError implements the error interface and supports error wrapping, allowing it to be used with errors.Is() and errors.As() for proper error handling.

func NewBundleError

func NewBundleError(op, ref string, err error) *BundleError

NewBundleError creates a new BundleError with the specified context. This is a convenience function for creating BundleError instances.

Parameters:

  • op: The operation that failed (e.g., "push", "pull")
  • ref: The OCI reference being processed
  • err: The underlying error

Returns a pointer to the new BundleError.

func (*BundleError) Error

func (e *BundleError) Error() string

Error implements the error interface. It returns the error message from the underlying error to maintain compatibility with existing error handling code that expects the underlying error message.

func (*BundleError) FormatError

func (e *BundleError) FormatError() string

FormatError creates a formatted error message with BundleError context. This is useful for logging or displaying errors with full context.

The format includes the operation, reference, and underlying error message. Example output: "push ghcr.io/org/repo:v1.0.0: authentication failed"

func (*BundleError) IsAuthError

func (e *BundleError) IsAuthError() bool

IsAuthError checks if this error or any wrapped error is an authentication failure. This is a convenience method for quickly identifying authentication-related errors.

Returns true if ErrAuthenticationFailed is found in the error chain.

func (*BundleError) IsSecurityError

func (e *BundleError) IsSecurityError() bool

IsSecurityError checks if this error or any wrapped error is a security violation. This is a convenience method for quickly identifying security-related errors.

Returns true if ErrSecurityViolation is found in the error chain.

func (*BundleError) Unwrap

func (e *BundleError) Unwrap() error

Unwrap returns the underlying error to support error wrapping. This allows BundleError to be used with errors.Is() and errors.As() for checking error types and extracting wrapped errors.

type CacheConfig

type CacheConfig struct {
	// Coordinator provides the cache implementation.
	// If nil, caching is disabled.
	Coordinator cache.Cache

	// CachePath specifies the filesystem path for cache storage.
	// If empty and Coordinator is nil, no cache directory is created.
	CachePath string

	// Policy controls when caching should be used.
	Policy CachePolicy

	// MaxSizeBytes is the maximum size of the cache in bytes.
	// Defaults to 1GB if not specified.
	MaxSizeBytes int64

	// DefaultTTL is the default time-to-live for cache entries.
	// Defaults to 24 hours if not specified.
	DefaultTTL time.Duration
}

CacheConfig contains configuration for OCI caching behavior.

type CachePolicy

type CachePolicy string

CachePolicy defines when caching should be applied to operations.

const (
	// CachePolicyDisabled disables caching completely.
	CachePolicyDisabled CachePolicy = "disabled"

	// CachePolicyEnabled enables caching for all operations.
	CachePolicyEnabled CachePolicy = "enabled"

	// CachePolicyPull enables caching only for pull operations.
	CachePolicyPull CachePolicy = "pull"

	// CachePolicyPush enables caching only for push operations.
	CachePolicyPush CachePolicy = "push"
)

type Client

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

Client provides OCI bundle operations using ORAS for registry communication. The client is safe for concurrent use and isolates ORAS dependencies in internal packages.

func New

func New() (*Client, error)

New creates a new Client with default configuration. It uses ORAS's default Docker credential chain for authentication.

func NewWithOptions

func NewWithOptions(opts ...ClientOption) (*Client, error)

NewWithOptions creates a new Client with custom configuration. It accepts functional options to customize authentication and other behaviors.

Example usage:

client, err := NewWithOptions(
    WithStaticAuth("ghcr.io", "username", "password"),
)
if err != nil {
    return err
}

func (*Client) Pull

func (c *Client) Pull(ctx context.Context, reference, targetDir string, opts ...PullOption) error

Pull downloads and extracts an OCI artifact to the specified directory. It downloads the artifact from the OCI registry and extracts it with security validation.

Parameters:

  • ctx: Context for cancellation and timeouts
  • reference: OCI reference to download (e.g., "ghcr.io/org/repo:tag")
  • targetDir: Directory to extract the artifact to (created if it doesn't exist)
  • opts: Optional pull options for security limits and behavior

Returns:

  • Error if the operation fails

func (*Client) PullWithCache

func (c *Client) PullWithCache(ctx context.Context, reference, targetDir string, opts ...PullOption) error

PullWithCache downloads and extracts an OCI artifact to the specified directory with intelligent caching to improve performance on repeated pulls. This method enhances the regular Pull method with caching capabilities.

Parameters:

  • ctx: Context for cancellation and timeouts
  • reference: OCI reference to download (e.g., "ghcr.io/org/repo:tag")
  • targetDir: Directory to extract the artifact to (created if it doesn't exist)
  • opts: Optional pull options for security limits, behavior, and cache control

Returns:

  • Error if the operation fails

The method will: 1. Check if caching is enabled and appropriate for the operation 2. Attempt to serve from cache if available and not bypassed 3. Fall back to network pull if cache miss or bypass requested 4. Cache the result for future pulls if caching is enabled

func (*Client) Push

func (c *Client) Push(ctx context.Context, sourceDir, reference string, opts ...PushOption) error

Push uploads a directory as an OCI artifact to the specified reference. It archives the source directory and pushes it to the OCI registry with the given options.

Parameters:

  • ctx: Context for cancellation and timeouts
  • sourceDir: Path to directory to upload (must exist and be readable)
  • reference: OCI reference (e.g., "ghcr.io/org/repo:tag")
  • opts: Optional push options for annotations, platform, and progress reporting

Returns:

  • Error if the operation fails

type ClientOption

type ClientOption func(*ClientOptions)

ClientOption is a functional option for configuring the Client.

func WithAllowHTTP

func WithAllowHTTP() ClientOption

WithAllowHTTP is a convenience function for enabling HTTP connections. This enables HTTP for all registries, useful for local development.

Example usage:

client, err := New(WithAllowHTTP())

func WithAuthNone

func WithAuthNone() ClientOption

WithAuthNone configures the client to rely on ORAS's default Docker credential chain. This is the default behavior and uses ~/.docker/config.json and credential helpers like osxkeychain, pass, desktop, etc. as configured by the user.

func WithCache

func WithCache(coordinator cache.Cache, cachePath string, maxSizeBytes int64, defaultTTL time.Duration) ClientOption

WithCache configures caching for OCI operations. The coordinator parameter provides the cache implementation. The cachePath parameter specifies where to store cache data. The maxSizeBytes parameter sets the maximum cache size (0 for default 1GB). The defaultTTL parameter sets the default TTL for cache entries (0 for default 24h).

func WithCachePolicy

func WithCachePolicy(policy CachePolicy) ClientOption

WithCachePolicy sets the cache policy for OCI operations. The policy parameter controls when caching should be applied: - CachePolicyDisabled: No caching - CachePolicyEnabled: Cache all operations - CachePolicyPull: Cache only pull operations - CachePolicyPush: Cache only push operations

func WithCredentialFunc

func WithCredentialFunc(fn func(ctx context.Context, registry string) (auth.Credential, error)) ClientOption

WithCredentialFunc configures a custom credential callback function. This completely overrides the default Docker credential chain and provides full control over credential resolution for all registries.

Parameters:

  • fn: Function that returns credentials for a given registry. Return empty credentials to fall back to anonymous access. Return an error to fail authentication for that registry.

The function should be safe for concurrent use and handle context cancellation.

func WithFilesystem

func WithFilesystem(fsys fsapi.Filesystem) ClientOption

WithFilesystem injects a custom filesystem implementation used by the client.

func WithHTTP

func WithHTTP(allowHTTP, allowInsecure bool, registries []string) ClientOption

WithHTTP configures HTTP transport settings for registry connections. This allows explicit control over HTTP vs HTTPS usage and certificate validation.

Parameters:

  • allowHTTP: Enable HTTP instead of HTTPS for registry connections
  • allowInsecure: Allow connections to registries with self-signed certificates
  • registries: Specific registries to apply this config to (empty applies to all)

Example usage:

client, err := New(WithHTTP(true, true, []string{"localhost:5000"}))

This is preferred over automatic localhost detection for better control and testability.

func WithInsecureHTTP

func WithInsecureHTTP() ClientOption

WithInsecureHTTP is a convenience function for enabling insecure HTTP connections. This enables both HTTP and allows self-signed certificates for all registries. WARNING: Only use this for testing environments.

Example usage:

client, err := New(WithInsecureHTTP())

func WithORASClient

func WithORASClient(client oras.Client) ClientOption

WithORASClient configures the client to use a custom ORAS client. This is primarily used for testing to inject mock implementations.

func WithStaticAuth

func WithStaticAuth(registry, username, password string) ClientOption

WithStaticAuth configures static credentials for a specific registry. This overrides the default Docker credential chain for the specified registry but allows other registries to use the default chain.

Parameters:

  • registry: The registry hostname (e.g., "ghcr.io")
  • username: Username for authentication
  • password: Password for authentication

For other registries not matching the specified one, the default Docker credential chain will be used.

type ClientOptions

type ClientOptions struct {
	// Auth options for ORAS operations
	Auth *oras.AuthOptions

	// ORASClient allows injecting a custom ORAS client for testing
	// If nil, the default ORAS client will be used
	ORASClient oras.Client

	// HTTPConfig controls HTTP vs HTTPS and certificate validation
	HTTPConfig *HTTPConfig

	// FS provides filesystem operations for archive/temp/extraction handling.
	// If nil, a default OS-backed filesystem will be used.
	FS fsapi.Filesystem

	// CacheConfig contains cache configuration for OCI operations.
	// If nil, caching is disabled.
	CacheConfig *CacheConfig
}

ClientOptions contains configuration options for the Client.

func DefaultClientOptions

func DefaultClientOptions() *ClientOptions

DefaultClientOptions returns the default client options.

type ExtractOptions

type ExtractOptions struct {
	// MaxFiles is the maximum number of files allowed in the archive.
	// Set to 0 for unlimited (not recommended for security).
	MaxFiles int

	// MaxSize is the maximum total uncompressed size of all files combined.
	MaxSize int64

	// MaxFileSize is the maximum size allowed for any individual file.
	MaxFileSize int64

	// StripPrefix removes this prefix from all file paths during extraction.
	// Useful for removing leading directory names from archived paths.
	StripPrefix string

	// PreservePerms determines whether to preserve original file permissions.
	// When false, permissions are sanitized for security.
	PreservePerms bool
}

ExtractOptions controls extraction behavior and security constraints. These options provide safety limits to prevent various attack vectors such as zip bombs, path traversal attacks, and resource exhaustion.

type FileCountValidator

type FileCountValidator struct {
	// MaxFiles is the maximum number of files allowed in an archive.
	// Set to 0 to disable file count limits.
	MaxFiles int
}

FileCountValidator prevents zip bomb attacks by limiting the number of files.

func NewFileCountValidator

func NewFileCountValidator(maxFiles int) *FileCountValidator

NewFileCountValidator creates a new FileCountValidator with the specified limit.

func (*FileCountValidator) ValidateArchive

func (v *FileCountValidator) ValidateArchive(stats ArchiveStats) error

ValidateArchive checks if the file count is within acceptable limits.

func (*FileCountValidator) ValidateFile

func (v *FileCountValidator) ValidateFile(info FileInfo) error

ValidateFile is a no-op for FileCountValidator since it validates at archive level.

func (*FileCountValidator) ValidatePath

func (v *FileCountValidator) ValidatePath(path string) error

ValidatePath is a no-op for FileCountValidator since it doesn't validate paths.

type FileInfo

type FileInfo struct {
	// Name is the file path within the archive
	Name string

	// Size is the uncompressed size of the file in bytes
	Size int64

	// Mode contains the file permissions and type information
	Mode uint32
}

FileInfo represents file information used for security validation. This struct contains the essential metadata needed to validate files during archive extraction without requiring access to the actual file content.

type HTTPConfig

type HTTPConfig struct {
	// AllowHTTP enables HTTP instead of HTTPS for registry connections.
	// This is useful for local registries that don't support HTTPS.
	AllowHTTP bool

	// AllowInsecure allows connections to registries with self-signed
	// or invalid certificates. This should only be used for testing.
	AllowInsecure bool

	// Registries specifies which registries this configuration applies to.
	// If empty, applies to all registries. Supports hostname matching.
	Registries []string
}

HTTPConfig contains configuration for HTTP transport settings. This allows explicit control over HTTP usage and certificate validation, rather than relying on brittle localhost detection.

type PermissionSanitizer

type PermissionSanitizer struct{}

PermissionSanitizer removes dangerous permission bits from files. It prevents privilege escalation by removing setuid and setgid bits.

func NewPermissionSanitizer

func NewPermissionSanitizer() *PermissionSanitizer

NewPermissionSanitizer creates a new PermissionSanitizer.

func (*PermissionSanitizer) SanitizePermissions

func (v *PermissionSanitizer) SanitizePermissions(mode uint32) uint32

SanitizePermissions removes dangerous permission bits from a file mode. This is a utility function that can be used during extraction.

func (*PermissionSanitizer) ValidateArchive

func (v *PermissionSanitizer) ValidateArchive(stats ArchiveStats) error

ValidateArchive is a no-op for PermissionSanitizer since it validates at file level.

func (*PermissionSanitizer) ValidateFile

func (v *PermissionSanitizer) ValidateFile(info FileInfo) error

ValidateFile checks and sanitizes file permissions. It removes setuid and setgid bits to prevent privilege escalation.

func (*PermissionSanitizer) ValidatePath

func (v *PermissionSanitizer) ValidatePath(path string) error

ValidatePath is a no-op for PermissionSanitizer since it doesn't validate paths.

type PullOption

type PullOption func(*PullOptions)

PullOption is a functional option for configuring Pull operations.

func WithCacheBypass

func WithCacheBypass(bypass bool) PullOption

WithCacheBypass is an alias for WithPullCacheBypass for convenience.

func WithMaxFiles

func WithMaxFiles(maxFiles int) PullOption

WithMaxFiles is an alias for WithPullMaxFiles for convenience.

func WithMaxSize

func WithMaxSize(maxSize int64) PullOption

WithMaxSize is an alias for WithPullMaxSize for convenience.

func WithPullAllowHiddenFiles

func WithPullAllowHiddenFiles(allow bool) PullOption

WithPullAllowHiddenFiles determines whether hidden files are allowed.

func WithPullCacheBypass

func WithPullCacheBypass(bypass bool) PullOption

WithPullCacheBypass disables caching for this pull operation.

func WithPullMaxFileSize

func WithPullMaxFileSize(maxFileSize int64) PullOption

WithPullMaxFileSize sets the maximum size allowed for any individual file.

func WithPullMaxFiles

func WithPullMaxFiles(maxFiles int) PullOption

WithPullMaxFiles sets the maximum number of files allowed in the archive.

func WithPullMaxRetries

func WithPullMaxRetries(maxRetries int) PullOption

WithPullMaxRetries sets the maximum number of retry attempts for network operations.

func WithPullMaxSize

func WithPullMaxSize(maxSize int64) PullOption

WithPullMaxSize sets the maximum total uncompressed size of all files combined.

func WithPullPreservePermissions

func WithPullPreservePermissions(preserve bool) PullOption

WithPullPreservePermissions determines whether to preserve original file permissions.

func WithPullRetryDelay

func WithPullRetryDelay(delay time.Duration) PullOption

WithPullRetryDelay sets the delay between retry attempts.

func WithPullStripPrefix

func WithPullStripPrefix(prefix string) PullOption

WithPullStripPrefix sets the prefix to remove from all file paths during extraction.

type PullOptions

type PullOptions struct {
	// MaxFiles is the maximum number of files allowed in the archive.
	// Set to 0 for unlimited (not recommended for security).
	MaxFiles int

	// MaxSize is the maximum total uncompressed size of all files combined.
	// Set to 0 for unlimited (not recommended for security).
	MaxSize int64

	// MaxFileSize is the maximum size allowed for any individual file.
	// Set to 0 for unlimited (not recommended for security).
	MaxFileSize int64

	// AllowHiddenFiles determines whether hidden files (starting with .) are allowed.
	AllowHiddenFiles bool

	// PreservePermissions determines whether to preserve original file permissions.
	// When false, permissions are sanitized for security.
	PreservePermissions bool

	// StripPrefix removes this prefix from all file paths during extraction.
	// Useful for removing leading directory names from archived paths.
	StripPrefix string

	// MaxRetries is the maximum number of retry attempts for network operations.
	MaxRetries int

	// RetryDelay is the delay between retry attempts.
	RetryDelay time.Duration

	// CacheBypass disables caching for this specific pull operation.
	// When true, the operation will bypass any configured cache.
	CacheBypass bool
}

PullOptions contains options for the Pull operation.

func DefaultPullOptions

func DefaultPullOptions() *PullOptions

DefaultPullOptions returns the default pull options.

type PushOption

type PushOption func(*PushOptions)

PushOption is a functional option for configuring Push operations.

func WithAnnotations

func WithAnnotations(annotations map[string]string) PushOption

WithAnnotations sets annotations to be attached to the OCI artifact.

func WithMaxRetries

func WithMaxRetries(maxRetries int) PushOption

WithMaxRetries sets the maximum number of retry attempts for network operations.

func WithPlatform

func WithPlatform(platform string) PushOption

WithPlatform sets the target platform for the OCI artifact.

func WithProgressCallback

func WithProgressCallback(callback func(current, total int64)) PushOption

WithProgressCallback sets a callback function for progress reporting.

func WithPushCacheBypass

func WithPushCacheBypass(bypass bool) PushOption

WithPushCacheBypass disables caching for this push operation.

func WithRetryDelay

func WithRetryDelay(delay time.Duration) PushOption

WithRetryDelay sets the delay between retry attempts.

type PushOptions

type PushOptions struct {
	// Annotations to attach to the OCI artifact manifest
	Annotations map[string]string

	// Platform specifies the target platform for the artifact
	Platform string

	// ProgressCallback is called during push operations to report progress
	ProgressCallback func(current, total int64)

	// MaxRetries is the maximum number of retry attempts for network operations
	MaxRetries int

	// RetryDelay is the delay between retry attempts
	RetryDelay time.Duration

	// CacheBypass disables caching for this specific push operation.
	// When true, the operation will bypass any configured cache.
	CacheBypass bool
}

PushOptions contains options for the Push operation.

func DefaultPushOptions

func DefaultPushOptions() *PushOptions

DefaultPushOptions returns the default push options.

type SizeValidator

type SizeValidator struct {
	// MaxFileSize is the maximum allowed size for any individual file (in bytes).
	// Set to 0 to disable individual file size limits.
	MaxFileSize int64

	// MaxTotalSize is the maximum allowed total size for all files combined (in bytes).
	// Set to 0 to disable total size limits.
	MaxTotalSize int64
}

SizeValidator enforces size limits on files and archives. It prevents resource exhaustion attacks by limiting individual file sizes and total archive sizes.

func NewSizeValidator

func NewSizeValidator(maxFileSize, maxTotalSize int64) *SizeValidator

NewSizeValidator creates a new SizeValidator with the specified limits.

func (*SizeValidator) ValidateArchive

func (v *SizeValidator) ValidateArchive(stats ArchiveStats) error

ValidateArchive checks if the total archive size is within acceptable limits.

func (*SizeValidator) ValidateFile

func (v *SizeValidator) ValidateFile(info FileInfo) error

ValidateFile checks if a file's size is within acceptable limits.

func (*SizeValidator) ValidatePath

func (v *SizeValidator) ValidatePath(path string) error

ValidatePath is a no-op for SizeValidator since it doesn't validate paths.

type TarGzArchiver

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

TarGzArchiver implements the Archiver interface using tar.gz format. It provides secure, streaming archive and extraction operations with comprehensive validation and progress reporting capabilities. Uses concurrent processing for improved performance on multi-core systems.

func DefaultArchiver

func DefaultArchiver() *TarGzArchiver

DefaultArchiver returns an archiver initialized with the default OS-backed filesystem.

func NewTarGzArchiver

func NewTarGzArchiver() *TarGzArchiver

NewTarGzArchiver creates a new TarGzArchiver instance. The archiver uses standard tar.gz format compatible with common tools and implements security validation during extraction.

func NewTarGzArchiverWithFS

func NewTarGzArchiverWithFS(fsys fsapi.Filesystem) *TarGzArchiver

NewTarGzArchiverWithFS returns a tar.gz archiver bound to the provided filesystem. Phase 0 placeholder: the filesystem will be used internally starting in Phase 1.

func (*TarGzArchiver) Archive

func (a *TarGzArchiver) Archive(ctx context.Context, sourceDir string, output io.Writer) error

Archive creates a tar.gz archive from the specified source directory. The archive is written to the provided output writer in a streaming fashion to minimize memory usage even with large directories.

Parameters:

  • ctx: Context for cancellation
  • sourceDir: Directory to archive (must exist and be readable)
  • output: Writer to receive the compressed archive data

Returns an error if the source directory doesn't exist, is not readable, or if writing to the output fails.

func (*TarGzArchiver) ArchiveWithProgress

func (a *TarGzArchiver) ArchiveWithProgress(
	ctx context.Context,
	sourceDir string,
	output io.Writer,
	progress func(current, total int64),
) error

ArchiveWithProgress creates a tar.gz archive from the specified source directory with progress reporting. The archive is written to the provided output writer in a streaming fashion to minimize memory usage even with large directories. Uses concurrent file processing for improved performance.

Parameters:

  • ctx: Context for cancellation
  • sourceDir: Directory to archive (must exist and be readable)
  • output: Writer to receive the compressed archive data
  • progress: Optional callback for progress reporting (current, total bytes)

Returns an error if the source directory doesn't exist, is not readable, or if writing to the output fails.

func (*TarGzArchiver) Extract

func (a *TarGzArchiver) Extract(ctx context.Context, input io.Reader, targetDir string, opts ExtractOptions) error

Extract expands a tar.gz archive to the specified target directory. The extraction process includes security validation to prevent common archive-based attacks such as path traversal and resource exhaustion.

Parameters:

  • ctx: Context for cancellation
  • input: Reader providing the compressed archive data
  • targetDir: Directory to extract files to (created if it doesn't exist)
  • opts: Extraction options controlling security limits and behavior

Returns an error if the archive is corrupted, contains security violations, exceeds configured limits, or if file operations fail.

func (*TarGzArchiver) MediaType

func (a *TarGzArchiver) MediaType() string

MediaType returns the OCI media type for tar.gz archives. This is used when pushing bundles to OCI registries to identify the archive format to registry clients.

type Validator

type Validator interface {
	// ValidatePath checks if a file path is safe for extraction.
	// This prevents path traversal attacks by rejecting paths containing ".."
	// or absolute paths that could escape the extraction directory.
	ValidatePath(path string) error

	// ValidateFile checks if a file's properties are acceptable.
	// This includes size limits, permission checks, and other file-specific validations.
	ValidateFile(info FileInfo) error

	// ValidateArchive checks if archive statistics are within acceptable limits.
	// This prevents zip bomb attacks by enforcing total file count and size limits.
	ValidateArchive(stats ArchiveStats) error
}

Validator checks for security issues during archive extraction. Implementations of this interface validate different aspects of files and archives to prevent security vulnerabilities such as path traversal attacks, zip bombs, and other malicious archive content.

type ValidatorChain

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

ValidatorChain combines multiple validators and executes them in sequence. It fails fast - returns the first validation error encountered.

func NewValidatorChain

func NewValidatorChain(validators ...Validator) *ValidatorChain

NewValidatorChain creates a new ValidatorChain with the specified validators.

func (*ValidatorChain) AddValidator

func (vc *ValidatorChain) AddValidator(validator Validator)

AddValidator adds a validator to the chain.

func (*ValidatorChain) ValidateArchive

func (vc *ValidatorChain) ValidateArchive(stats ArchiveStats) error

ValidateArchive runs all validators' ValidateArchive methods in sequence. Returns the first error encountered, or nil if all pass.

func (*ValidatorChain) ValidateFile

func (vc *ValidatorChain) ValidateFile(info FileInfo) error

ValidateFile runs all validators' ValidateFile methods in sequence. Returns the first error encountered, or nil if all pass.

func (*ValidatorChain) ValidatePath

func (vc *ValidatorChain) ValidatePath(path string) error

ValidatePath runs all validators' ValidatePath methods in sequence. Returns the first error encountered, or nil if all pass.

Directories

Path Synopsis
internal
cache
Package cache provides a comprehensive caching system for OCI (Open Container Initiative) artifacts.
Package cache provides a comprehensive caching system for OCI (Open Container Initiative) artifacts.
oras
Package oras provides caching functionality for HTTP transports and authentication credentials.
Package oras provides caching functionality for HTTP transports and authentication credentials.
testutil
Package testutil provides testing utilities for the OCI bundle library.
Package testutil provides testing utilities for the OCI bundle library.
validate
Package validate provides path and content validation functionality.
Package validate provides path and content validation functionality.

Jump to

Keyboard shortcuts

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