这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: nhedger/setup-sops@v2
- uses: alessiodionisi/setup-age-action@v1.3.0
- uses: ./.github/actions/prepare
- run: pnpm run test:unit

Expand Down
168 changes: 141 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
## Features

- Supports decryption of SOPS files encrypted with `age`
- Automatic age key discovery, following SOPS conventions
- SSH key support, automatically converting Ed25519 and RSA SSH keys to age format
- Compatible with various file formats including `.env`, `.json`, and `.yaml`
- Supports multiple input types (`string`, `Buffer`, `File`, `Blob`, streams, etc.)
- Works across different JavaScript runtimes (Node.js, Deno, Bun, browser)
Expand Down Expand Up @@ -35,7 +37,12 @@ The library can be used in various JavaScript environments and supports multiple
```js
import { decryptSops } from "sops-age";

// Decrypt from a local file
// Decrypt from a local file (keys auto-discovered from env and file system)
const config = await decryptSops({
path: "./config.enc.json",
});

// Or with explicit age key
const config = await decryptSops({
path: "./config.enc.json",
secretKey: "AGE-SECRET-KEY-1qgdy...",
Expand Down Expand Up @@ -94,13 +101,12 @@ The library provides a unified `decryptSops` function that can handle various in
```js
import { decryptSops } from "sops-age";

// Decrypt from a local file
// Decrypt from a local file, auto-discovering age keys
const config = await decryptSops({
path: "./config.enc.json",
secretKey: "AGE-SECRET-KEY-1qgdy...",
});

// Decrypt from a URL
// Decrypt from a URL with explicit age key
const remoteConfig = await decryptSops({
url: "https://example.com/config.enc.yaml",
secretKey: "AGE-SECRET-KEY-1qgdy...",
Expand Down Expand Up @@ -136,13 +142,104 @@ The library automatically detects the file type based on file extension or conte
- `Buffer`: Node.js Buffer (in Node.js environment)
- `ReadableStream<Uint8Array>`: Stream of binary data

## Age Key Discovery

`sops-age` automatically discovers age keys using the same logic as SOPS itself. When no `secretKey` is explicitly provided, the library will search for keys in the following order:

### 1. SSH Keys (Converted to Age Format)

The library can automatically convert SSH private keys to age format:

- **Environment variable**: `SOPS_AGE_SSH_PRIVATE_KEY_FILE` - path to SSH private key
- **Default locations**: `~/.ssh/id_ed25519` and `~/.ssh/id_rsa` (in that order)
- **Supported types**: Ed25519 and RSA keys

```js
// Set environment variable to use specific SSH key
process.env.SOPS_AGE_SSH_PRIVATE_KEY_FILE = "/path/to/my/ssh/key";

// Keys will be auto-discovered and converted
const config = await decryptSops({ path: "./config.enc.json" });
```

### 2. Age Keys from Environment Variables

- **`SOPS_AGE_KEY`**: Direct age private key
- **`SOPS_AGE_KEY_FILE`**: Path to file containing age keys
- **`SOPS_AGE_KEY_CMD`**: Command that outputs age keys

```js
// Direct key
process.env.SOPS_AGE_KEY = "AGE-SECRET-KEY-1qgdy...";

// Key file
process.env.SOPS_AGE_KEY_FILE = "/path/to/keys.txt";

// Command that outputs keys
process.env.SOPS_AGE_KEY_CMD = "my-key-manager get-age-key";
```

### 3. Default Config File

The library checks for age keys in the default SOPS config directory:

- **Linux/Unix**: `~/.config/sops/age/keys.txt` (or `$XDG_CONFIG_HOME/sops/age/keys.txt`)
- **macOS**: `~/Library/Application Support/sops/age/keys.txt` (or `$XDG_CONFIG_HOME/sops/age/keys.txt` if set)
- **Windows**: `%APPDATA%\sops\age\keys.txt`

Example `keys.txt` file:

```text
# Created: 2024-01-15T10:30:00Z
# Public key: age1je6kjhzuhdjy3fqptpttxjh5k8q46vygzlgtpuq3030c947pc5tqz9dqvr
AGE-SECRET-KEY-1QGDY7NWZDM5HG2QMSKQHQZPQF2QJLTQHQZPQF2QJLTQHQZPQF2QJLTQHQZ

# Another key
AGE-SECRET-KEY-1ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOP
```

## Key Discovery Priority

When no explicit `secretKey` is provided, keys are discovered in this order:

1. **SSH Keys**: `SOPS_AGE_SSH_PRIVATE_KEY_FILE` → `~/.ssh/id_ed25519` → `~/.ssh/id_rsa`
2. **Age Keys**: `SOPS_AGE_KEY` → `SOPS_AGE_KEY_FILE` → `SOPS_AGE_KEY_CMD`
3. **Default Config**: Platform-specific `sops/age/keys.txt` file

The library will try all discovered keys until one successfully decrypts the file.

## Age Key Utilities

The library includes utilities for discovering age keys and converting SSH keys to age format:

```js
import { sshKeyToAge, sshKeyFileToAge, findAllAgeKeys } from "sops-age";

// Convert SSH key content to age format
const sshKeyContent = "-----BEGIN OPENSSH PRIVATE KEY-----\n...";
const ageKey = sshKeyToAge(sshKeyContent);

// Convert SSH key file to age format
const ageKey = await sshKeyFileToAge("/path/to/ssh/key");

// Discover all available age keys
const allKeys = await findAllAgeKeys();
console.log("Found keys:", allKeys);
```

## API Reference

### `decryptSops(input, options?)`

Decrypts SOPS-encrypted content directly from a string, Buffer, or other supported input types.

```js
// With auto-discovered keys
const decrypted = await decryptSops(jsonString, {
fileType: "json",
});

// With explicit key
const decrypted = await decryptSops(jsonString, {
secretKey: "AGE-SECRET-KEY-1qgdy...",
fileType: "json",
Expand All @@ -156,7 +253,7 @@ Decrypts a SOPS-encrypted file from the local filesystem.
```js
const decrypted = await decryptSops({
path: "/path/to/config.enc.json",
secretKey: "AGE-SECRET-KEY-1qgdy...",
// secretKey optional - will auto-discover
});
```

Expand All @@ -167,7 +264,7 @@ Decrypts a SOPS-encrypted file from a URL.
```js
const decrypted = await decryptSops({
url: "https://example.com/config.enc.json",
secretKey: "AGE-SECRET-KEY-1qgdy...",
secretKey: "AGE-SECRET-KEY-1qgdy...", // or auto-discover
});
```

Expand All @@ -180,42 +277,59 @@ const sopsObject = {
secret:
"ENC[AES256_GCM,data:trrpgezXug4Dq9T/inwkMA==,iv:glPwxoY2UuHO91vlJRaqYtFkPY1VsWvkJtfkEKZJdns=,tag:v7DbOYl7C5gdQRdW6BVoLw==,type:str]",
sops: {
kms: null,
gcp_kms: null,
azure_kv: null,
hc_vault: null,
age: [
{
recipient:
"age1je6kjhzuhdjy3fqptpttxjh5k8q46vygzlgtpuq3030c947pc5tqz9dqvr",
enc: "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsYVh6WTdzNnArL1E1RVVw\nZEoyU2hqZVZuVjR3bFJ2dlZaOGQ4VUVla1hVCll3MDZrY1VZeWtPNzVPUGNFWjBK\ncDZLVFZheU4wK0hBOTNmRFcwUkE4OFkKLS0tIHl3Y0x5Sk9lVDRCQkR3QzNzRWY4\ncFNPWFZqdEtyUmFhL3pLOHJvNlhuTTAKNLKVIFujPtmpYo/Oycit0JbcfPVN2TN5\nG9emUjK1XVOwkNda0olhEt4KAjSBV7dYt8luOL8VQeR33PadX7RK3A==\n-----END AGE ENCRYPTED FILE-----\n",
},
],
lastmodified: "2024-12-25T09:03:17Z",
mac: "ENC[AES256_GCM,data:pjgkspXlmS1uFtR5yRZXcXISNEOk2/L4lN1zJoo49kgbABun2EwpZ2wfhPUDEDKn7kfmKuSOl4xQ/adUHVJh6bOHyQTf9lfH2BekCy828BIODowzk2tR5uiin8bB5q5VQfNJIaYEn3EWpGaOupEaNTZOyi5ML+WXB8s6w53Wg0w=,iv:wKEh9xSZ5RtsNdlRcEpYnbLKmUV6yitneJQhVt7qBSM=,tag:CmfHyYRe2/GVEexW5A8OWg==,type:str]",
pgp: null,
unencrypted_suffix: "_unencrypted",
version: "3.9.2",
// ... SOPS metadata
},
};

// Assumes SOPS_AGE_KEY is set in the env
// Auto-discovers keys from environment/config
const decrypted = await decryptSops(sopsObject);
```

### Options
### `DecryptSopsOptions`

The `decryptSops` function accepts the following options:

- `secretKey`: The age secret key for decryption (required unless `SOPS_AGE_KEY` env var is set)
- `secretKey`: The age secret key for decryption (optional - will auto-discover if not provided)
- `fileType`: Optional file type ('env', 'json', or 'yaml'). Auto-detected if not specified
- `keyPath`: Optional path to decrypt only a specific value
- `path`: Path to local SOPS file (when using file-based decryption)
- `url`: URL of SOPS file (when using URL-based decryption)

## Environment Variables
### Utility Functions

#### `findAllAgeKeys()`

- `SOPS_AGE_KEY`: If set, this environment variable will be used as the default secret key when none is provided in the options.
Discovers all available age keys (including converted SSH keys) using SOPS logic:

```js
import { findAllAgeKeys } from "sops-age";

const keys = await findAllAgeKeys();
console.log("Available age keys:", keys);
```

#### `sshKeyToAge(keyContent, filePath)`

Converts SSH private key content to age format:

```js
import { sshKeyToAge } from "sops-age";

const sshKey = "-----BEGIN OPENSSH PRIVATE KEY-----\n...";
const ageKey = sshKeyToAge(sshKey, "id_ed25519");
// Returns: "AGE-SECRET-KEY-1..." or null for unsupported keys
```

#### `sshKeyFileToAge(filePath)`

Converts SSH private key file to age format:

```js
import { sshKeyFileToAge } from "sops-age";

const ageKey = await sshKeyFileToAge("~/.ssh/id_ed25519");
// Returns: "AGE-SECRET-KEY-1..." or null
```

## License

Expand Down
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.cjs",
Expand Down Expand Up @@ -52,9 +52,12 @@
},
"dependencies": {
"@noble/ciphers": "^1.2.1",
"@noble/hashes": "^1.8.0",
"@scure/base": "^1.2.5",
"age-encryption": "^0.2.3",
"dotenv": "^16.4.7",
"lodash": "^4.17.21",
"sshpk": "^1.18.0",
"yaml": "^2.7.0",
"zod": "^3.24.2"
},
Expand All @@ -64,10 +67,10 @@
"@types/deno": "^2.2.0",
"@types/lodash": "^4.17.15",
"@types/node": "^22.13.5",
"@types/sshpk": "^1.17.4",
"@vitest/coverage-v8": "^3.0.6",
"bun": "^1.2.3",
"bun-types": "^1.2.3",
"console-fail-test": "^0.5.0",
"deno": "^2.2.1",
"esbuild-visualizer": "^0.7.0",
"husky": "^9.1.7",
Expand Down
Loading