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
77 changes: 0 additions & 77 deletions .github/workflows/publish.pypi.cli.yml

This file was deleted.

2 changes: 2 additions & 0 deletions .github/workflows/test.ansible.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:

jobs:
test-ansible:
# Skip ansible tests on CLI release branches (tests fixed on ansible release branch)
if: ${{ !startsWith(github.head_ref, 'release/tool/cli/') }}
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down
71 changes: 70 additions & 1 deletion .github/workflows/test.cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
on:
pull_request:
branches: [ master ]
paths:
- 'integration/keeper_secrets_manager_cli/**'
- 'sdk/python/core/**'
- 'sdk/python/helper/**'
- '.github/workflows/test.cli.yml'

jobs:
test-cli:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -52,10 +58,73 @@
- name: Install CLI dependencies
working-directory: ./integration/keeper_secrets_manager_cli
run: |
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 -m pip install boto3 # test dep — optional in production
python3 -m pip install pytest pytest-cov

- name: Run CLI tests
working-directory: ./integration/keeper_secrets_manager_cli
run: |
PYTHONPATH=$PWD pytest

test-cli-keyring:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install system deps (dbus + gnome-keyring)
run: sudo apt-get update && sudo apt-get install -y gnome-keyring dbus-x11

########## KSM Python SDK (from source)

- name: Install SDK dependencies
working-directory: ./sdk/python/core
run: |
python3 -m pip install --upgrade pip
python3 -m pip install setuptools
python3 -m pip install -r requirements.txt
python3 -m pip install -e .

- name: Install SDK for integrations
working-directory: ./sdk/python/core
run: |
python3 setup.py build install

########## KSM Python Helper (from source)

- name: Install SDK Helper dependencies
working-directory: ./sdk/python/helper
run: |
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 -m pip install -e .

- name: Install SDK Helper for integrations
working-directory: ./sdk/python/helper
run: |
python3 setup.py build install

########## CLI with keyring extra

- name: Install CLI with keyring extra
working-directory: ./integration/keeper_secrets_manager_cli
run: pip install -e ".[keyring]" pytest

- name: Run keyring integration tests
working-directory: ./integration/keeper_secrets_manager_cli
run: |
dbus-run-session -- bash -c "
echo '' | gnome-keyring-daemon --unlock --components=secrets,keyring
KSM_KEYRING_INTEGRATION=1 python -m pytest tests/keyring_integration_test.py -v --tb=short
"
Comment on lines +72 to +129

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI about 1 month ago

In general, you fix this by explicitly adding a permissions block that restricts the GITHUB_TOKEN to the minimal scopes needed. For a pure CI/test workflow like this—where the jobs only check out source, install dependencies, and run tests—contents: read is usually sufficient. You can set this either at the workflow root (so it applies to all jobs that don’t override it) or per job. Here, the cleanest solution is to add a single root-level permissions block after the name: or on: section.

Concretely, in .github/workflows/test.cli.yml, add:

permissions:
  contents: read

at the top level of the workflow (aligned with name: and on:), e.g. between name: Test-CLI and on:. This will apply read-only repository-content permissions to both test-cli and test-cli-keyring jobs, without changing any of the existing steps or functionality. No imports or additional methods are needed because this is a configuration-only change in the workflow YAML.

Suggested changeset 1
.github/workflows/test.cli.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/test.cli.yml b/.github/workflows/test.cli.yml
--- a/.github/workflows/test.cli.yml
+++ b/.github/workflows/test.cli.yml
@@ -1,5 +1,8 @@
 name: Test-CLI
 
+permissions:
+  contents: read
+
 on:
   pull_request:
     branches: [ master ]
EOF
@@ -1,5 +1,8 @@
name: Test-CLI

permissions:
contents: read

on:
pull_request:
branches: [ master ]
Copilot is powered by AI and may make mistakes. Always verify output.

24 changes: 24 additions & 0 deletions integration/keeper_secrets_manager_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ For more information see our official documentation page https://docs.keeper.io/

# Change History

## 1.3.0
- **Feature**: KSM-800 - OS-native keyring storage for CLI configuration
- New profiles store configuration in the OS keyring by default (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- Existing `keeper.ini` profiles continue to work without migration
- Added `--ini-file` flag to opt into explicit file-based storage
- Added `keyring` as an optional dependency: `pip install keeper-secrets-manager-cli[keyring]`
- **Fix**: KSM-814 - `--ini-file` flag now respected by all profile and config subcommands: `profile list`, `profile active`, `profile export`, `profile import`, `profile init`, `profile setup`; `config show`, `config color`, `config cache`, `config record-type-dir`, `config editor`
- **Fix**: KSM-691 - keeper.ini now written with owner-only permissions (0600)
- **Breaking**: KSM-799, KSM-817 - Minimum Python raised from 3.7 to 3.10
- **Breaking**: KSM-817 - boto3 is now an optional dependency; AWS sync users must install the `[aws]` extra: `pip install keeper-secrets-manager-cli[aws]`
- **Dependency**: Updated keeper-secrets-manager-core to >=17.2.0 and keeper-secrets-manager-helper to >=1.1.0
- **Security**: KSM-761 - Fixed CVE-2026-23949 (jaraco.context path traversal vulnerability)
- **Fix**: Updated prompt-toolkit from ~=2.0 to >=3.0 (fixes dependency resolution conflicts)
- **Fix**: KSM-804 - Warn on stderr when keyring is active but empty and a keeper.ini file exists at CWD or standard locations, including hint to use `--ini-file`
- **Fix**: KSM-805 - SHA-256 integrity hash now persisted as a separate Keychain entry and verified on every load; tampered entries raise a `KsmCliIntegrityException` with a clear recovery hint
- **Fix**: KSM-810 - Added `ksm profile delete <name>` command; fixed keyring storage to clear the active profile pointer when the active profile is deleted, preventing a broken state on subsequent invocations
- **Fix**: KSM-702 - Record create payload now always includes `custom: []`; previously the key was silently omitted when no custom fields were set
- **Fix**: KSM-815 - Profile name is now validated before redeeming the one-time token; invalid names (containing whitespace or exceeding 64 characters) are rejected immediately, preventing the token from being consumed on a failed init
- **Fix**: KSM-818 - `ksm shell` no longer crashes on any command when click>=8.2 is installed; pinned click-repl to <0.3.0 (0.3.0 incompatible with click>=8.2)
- **Fix**: KSM-820 - `ksm secret get --json` now outputs custom fields under `"custom"` key (was `"custom_fields"`), matching the canonical V3 record format used by Commander and the Keeper Vault
- **Fix**: KSM-828 - Unit tests no longer write mock data to the real system keyring; added `KeyringConfigStorage.is_available` mock to all tests that call `Profile.init()` as scaffolding (`secret_test.py`, `exec_test.py`, `secret_inflate_test.py`)
- **Fix**: KSM-829 - Profile name validation before OTT redemption now uses the same strict pattern as keyring storage (`[a-zA-Z0-9_-]{1,64}`); previously the early check allowed path-traversal characters and special characters through, consuming the one-time token before the stricter validator fired
- **Fix**: KSM-831 - `--ini-file` no longer fails with `Missing import dependencies: boto3` for non-AWS profiles; `AwsConfigProvider` import is now deferred to the `aws` storage branch in `_load_config`, so users without the `[aws]` extra are unaffected
- **Fix**: KSM-832 - removed lkru utility integration; `is_available()` now correctly returns `False` when `keyring` is not installed or no Secret Service daemon is running, falling back to `keeper.ini` file storage in both cases
## 1.2.0
- KSM-649 Added AWS KMS JSON support for sync command
- KSM-465 Implemented ksm interpolate command for shell built-in compatibility
Expand Down
169 changes: 169 additions & 0 deletions integration/keeper_secrets_manager_cli/docs/keyring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Keyring Storage for CLI Configuration

Starting in v1.3.0, KSM CLI stores profile configuration in the OS-native keyring by default instead of a plaintext `keeper.ini` file.

## Why This Change

Prior to v1.3.0, CLI profiles were stored in `keeper.ini` on disk. This file was created with world-readable permissions by default on Linux and macOS, meaning other users and processes on the same system could read your KSM credentials.

Keyring storage uses the OS credential manager, which enforces access control at the OS level — other users cannot read entries that don't belong to them.

## Platform Support

| Platform | Keyring Backend |
|----------|----------------|
| macOS | Keychain |
| Windows | Credential Manager |
| Linux | Secret Service API (e.g. GNOME Keyring, KWallet) |

Linux requires a running Secret Service daemon (GNOME Keyring, KWallet). See [Linux Setup](#linux-setup).

## How It Works

**New profiles** are stored in the keyring automatically — no `keeper.ini` is created.

**Existing profiles** in `keeper.ini` continue to work without any migration. The CLI detects which storage backend a profile uses and reads from the correct location.

### Using File-Based Storage

If you need an explicit `keeper.ini` file (e.g. Docker containers, CI/CD pipelines, or shared service accounts), use the `--ini-file` flag:

```bash
# Initialize a profile using file storage
ksm profile init --ini-file /path/to/keeper.ini

# Use a specific ini file for a command
ksm secret list --ini-file /path/to/keeper.ini
```

File-based profiles created with `--ini-file` are now written with `0600` permissions (owner read/write only).

## Linux Setup

Linux requires a running Secret Service-compatible keyring daemon (e.g. GNOME Keyring or KWallet).

`keyring` is an optional dependency. Install it alongside the CLI:

```bash
pip install keeper-secrets-manager-cli[keyring]
```

If `keyring` is not installed, new profiles fall back to `keeper.ini` file storage (with `0600` permissions).

### Headless / Server Environments

If no Secret Service daemon is running, keyring storage is not available. Use file-based storage instead:

```bash
ksm profile init --ini-file ~/.keeper/keeper.ini
```

## CI/CD and Docker

Keyring storage is not suitable for containerized or ephemeral environments. Use the `KSM_CONFIG` environment variable or `--ini-file` instead.

### Using KSM_CONFIG (recommended for CI/CD)

```bash
# Export your profile as a base64 config string
export KSM_CONFIG=$(ksm profile export --profile-name default)

# Use in your pipeline — no ini file needed
ksm secret list
```

### Using --ini-file in Docker

```dockerfile
# Copy your keeper.ini into the container
COPY keeper.ini /app/keeper.ini

# Use it at runtime
CMD ["ksm", "secret", "list", "--ini-file", "/app/keeper.ini"]
```

## Profile Name Requirements

Profile names stored in the keyring must be 1–64 characters and contain only letters, numbers, hyphens, and underscores:

```
✓ default
✓ my-profile
✓ prod_api_v2
✗ my profile (spaces not allowed)
✗ ../escape (path characters not allowed)
```

## Troubleshooting

### Keyring not available

```
Error: No keyring backend available. Install: pip install keyring
```

Install the `keyring` library or use `--ini-file` for file-based storage.

### Wrong keyring backend on Linux

If no Secret Service daemon is running, `keyring` returns a `fail.Keyring` backend. The CLI detects this and falls back to file-based storage. To confirm which backend is active:

```bash
python3 -c "import keyring; b = keyring.get_keyring(); print(b.__class__.__module__)"
```

If the output contains `fail`, start GNOME Keyring or KWallet, or use `--ini-file` for file-based storage.

### Integrity check failure

```
ksm had a problem: Keyring entry for profile '<name>' failed integrity check.
The entry may have been modified outside of the KSM CLI.
To recover, delete the profile and re-initialize:
ksm profile delete <name>
ksm profile init --token <one-time-token>
```

This error means the Keychain entry for the named profile was modified outside the CLI (e.g. by another application or a manual edit). The CLI refuses to use the entry to protect against credential substitution attacks.

**Recovery steps:**

```bash
# 1. Delete the affected profile
ksm profile delete <name>

# 2. Re-initialize with a new one-time token from the Keeper Admin Console
ksm profile init --token <one-time-token> --profile-name <name>
```

> **Profile name format**: must be 1–64 characters containing only letters, numbers, hyphens, and underscores. The name is validated before the one-time token is redeemed, so a bad name won't consume the token.

### Finding entries in the OS keyring

The CLI stores entries under the application name `KSM-cli`:
- Common config: key `ksm-cli-common`
- Per-profile: key `ksm-cli-profile-{profile_name}`

On macOS you can view these in **Keychain Access**. On Windows, use **Credential Manager** → **Windows Credentials**.

### Migrating from keeper.ini to keyring

No automated migration is provided. To move an existing profile to keyring storage:

```bash
# 1. Export the existing profile
ksm profile export --profile-name default > token.txt

# 2. Re-initialize using keyring storage (omit --ini-file)
ksm profile init --token "$(cat token.txt)"

# 3. Verify the new profile works
ksm secret list

# 4. Remove the old keeper.ini if no longer needed
```

## See Also

- [KSM CLI Documentation](https://docs.keeper.io/secrets-manager/secrets-manager/secrets-manager-command-line-interface)
- [Profile Management](https://docs.keeper.io/secrets-manager/secrets-manager/secrets-manager-command-line-interface/profile-management)
Loading
Loading