mirror of
https://github.com/Kruhlmann/punlock.git
synced 2026-11-05 09:24:08 +00:00
Compare commits
5
Commits
migrate
...
b093fd6b0e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b093fd6b0e | ||
|
|
08e3b6025a | ||
|
|
4ce91d617f | ||
|
|
993da22357 | ||
|
|
f7543b2e6c |
No files matched your search
@@ -0,0 +1,64 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-action@stable
|
||||||
|
|
||||||
|
- name: Cache Cargo registry
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --release
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-action@stable
|
||||||
|
- name: Cache Cargo registry
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-
|
||||||
|
- name: Run Clippy
|
||||||
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-action@stable
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: cargo fmt -- --check
|
||||||
Generated
+12
-56
@@ -417,11 +417,11 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex-automata 0.1.10",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -452,12 +452,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.50.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"overload",
|
"windows-sys 0.52.0",
|
||||||
"winapi",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -481,12 +480,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "overload"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -592,17 +585,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata 0.4.9",
|
"regex-automata",
|
||||||
"regex-syntax 0.8.5",
|
"regex-syntax",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
|
||||||
dependencies = [
|
|
||||||
"regex-syntax 0.6.29",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -613,15 +597,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.8.5",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.6.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -942,14 +920,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.19"
|
version = "0.3.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matchers",
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex-automata",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
@@ -1050,28 +1028,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|||||||
+1
-1
@@ -24,5 +24,5 @@ serde_json = "1.0.140"
|
|||||||
tokio = { version = "1.44.2", features = ["full"] }
|
tokio = { version = "1.44.2", features = ["full"] }
|
||||||
toml = "0.8.20"
|
toml = "0.8.20"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Punlock
|
||||||
|
|
||||||
|
PasswordUNLOCKer - A tool to unlock secrets from [Bitwarden](https://bitwarden.com/) and mount them to the filesystem.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Punlock authenticates with Bitwarden, fetches secrets based on `JMESPath` queries, and writes them to a secure runtime directory. It can also create symlinks to make secrets accessible at convenient locations.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Authenticate with Bitwarden (including self-hosted instances)
|
||||||
|
- Fetch secrets using `JMESPath` queries on Bitwarden items
|
||||||
|
- Store secrets in a secure runtime directory (Linux: `/run/user/<uid>/punlock/`)
|
||||||
|
- Create symlinks to secrets at custom locations
|
||||||
|
- Automatic file permissions (readonly, 400 for private secrets)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Rust (for building)
|
||||||
|
- Bitwarden CLI (`bw`) installed and available in PATH
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary will be available at `target/release/punlock`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create a configuration file at one of these locations:
|
||||||
|
- Current directory: `config.toml`
|
||||||
|
- User config: `~/.config/punlock/config.toml`
|
||||||
|
- System config: `/etc/punlock/config.toml`
|
||||||
|
|
||||||
|
Or specify a custom path with `--config`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
punlock --config /path/to/config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Format
|
||||||
|
|
||||||
|
```toml
|
||||||
|
domain = "https://your-bitwarden-instance.com"
|
||||||
|
email = "[email protected]"
|
||||||
|
|
||||||
|
# Secrets to unlock
|
||||||
|
[[entries]]
|
||||||
|
id = "bitwarden-item-uuid"
|
||||||
|
query = "details.fields[[email protected]=='ssh_private_key'].value"
|
||||||
|
path = "ssh/id_rsa"
|
||||||
|
public = false
|
||||||
|
links = ["~/.ssh/id_rsa"]
|
||||||
|
|
||||||
|
[[entries]]
|
||||||
|
id = "another-item-uuid"
|
||||||
|
query = "details.login.password"
|
||||||
|
path = "api/tokens/github"
|
||||||
|
public = true
|
||||||
|
links = ["~/.config/github/token"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Fields
|
||||||
|
|
||||||
|
- `domain` (optional): Bitwarden server URL for self-hosted instances
|
||||||
|
- `email` (optional): Bitwarden account email
|
||||||
|
- `entries`: Array of secrets to unlock
|
||||||
|
- `id`: Bitwarden item UUID
|
||||||
|
- `query`: `JMESPath` expression to extract the secret from the item
|
||||||
|
- `path`: Relative path in the runtime directory to write the secret
|
||||||
|
- `links` (optional): Array of symlink paths to create (supports `~` for home directory)
|
||||||
|
- `public` (default: `false`): If true, file permissions are 0444; otherwise 0400
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
punlock
|
||||||
|
# Or
|
||||||
|
punlock --config /path/to/config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
On first run, if email is not configured, you'll be prompted to enter it. You'll also need to enter your Bitwarden password.
|
||||||
|
|
||||||
|
## Runtime Directory
|
||||||
|
|
||||||
|
On Linux, secrets are stored in: `/run/user/<uid>/punlock/`
|
||||||
|
|
||||||
|
This is a tmpfs directory that is automatically cleaned on reboot to ensure that secrets do not stick around.
|
||||||
+6
-3
@@ -5,8 +5,8 @@ use tokio::process::Command;
|
|||||||
use crate::{config::PunlockConfigurationEntry, email::Email};
|
use crate::{config::PunlockConfigurationEntry, email::Email};
|
||||||
|
|
||||||
pub struct Bitwarden<S> {
|
pub struct Bitwarden<S> {
|
||||||
email: Email,
|
pub email: Email,
|
||||||
session: S,
|
pub session: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bitwarden<()> {
|
impl Bitwarden<()> {
|
||||||
@@ -104,7 +104,10 @@ impl Bitwarden<String> {
|
|||||||
|
|
||||||
let secret = match result.as_string() {
|
let secret = match result.as_string() {
|
||||||
Some(s) => s.to_owned(),
|
Some(s) => s.to_owned(),
|
||||||
None => anyhow::bail!("invalid item"),
|
None => {
|
||||||
|
tracing::error!(?data, ?expr, "find secret");
|
||||||
|
anyhow::bail!("invalid item")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(secret)
|
Ok(secret)
|
||||||
|
|||||||
+14
-2
@@ -1,6 +1,11 @@
|
|||||||
use std::{os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc};
|
use std::{
|
||||||
|
os::unix::fs::PermissionsExt,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use futures::{StreamExt, stream::FuturesUnordered};
|
use futures::{stream::FuturesUnordered, StreamExt, TryFutureExt};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -52,6 +57,12 @@ impl UnixSecretStore {
|
|||||||
|
|
||||||
pub async fn write_secrets(&self, entries: &[PunlockConfigurationEntry]) -> anyhow::Result<()> {
|
pub async fn write_secrets(&self, entries: &[PunlockConfigurationEntry]) -> anyhow::Result<()> {
|
||||||
let mut tasks = FuturesUnordered::new();
|
let mut tasks = FuturesUnordered::new();
|
||||||
|
Command::new("bw")
|
||||||
|
.args(["sync", "--session", &self.bitwarden.session])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "spawn get"))?;
|
||||||
|
|
||||||
for entry in entries.iter() {
|
for entry in entries.iter() {
|
||||||
let root = self.root_path.clone();
|
let root = self.root_path.clone();
|
||||||
@@ -116,6 +127,7 @@ impl UnixSecretStore {
|
|||||||
std::os::windows::fs::symlink_file(&src, &dst)?;
|
std::os::windows::fs::symlink_file(&src, &dst)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
.inspect_err(|error| tracing::error!(src = ?path, dst = ?link_path, ?error, "symlink failed"))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))??;
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))??;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user