mirror of
https://github.com/Keeper-Security/harness-integration.git
synced 2026-06-04 10:14:56 +08:00
updated readme file and added support for file based record from keeper
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Dependencies - we install fresh in Docker
|
||||
node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Git files - not needed in image
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# IDE and editor files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Build artifacts
|
||||
dist/
|
||||
build/
|
||||
*.log
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
@@ -1,139 +1,51 @@
|
||||
# Harness Keeper Security Plugin
|
||||
# Harness CI Keeper Plugin
|
||||
|
||||
A Harness CI/CD plugin that integrates with Keeper Security Secrets Manager (KSM) to securely fetch and inject secrets into your CI/CD pipelines at runtime.
|
||||
Keeper Secrets Manager integration into Harness CI for dynamic secrets retrieval
|
||||
|
||||
## Overview
|
||||
|
||||
This plugin securely retrieves secrets from Keeper Security Secrets Manager and makes them available to subsequent steps in your Harness pipeline. The plugin implements a zero-knowledge architecture where secrets are retrieved directly from Keeper Vault at runtime and never pass through Harness systems in decrypted form.
|
||||
|
||||
**Key Features:**
|
||||
- **Zero-Knowledge Architecture**: Secrets are retrieved directly from Keeper Vault at runtime
|
||||
- **Multiple Authentication Methods**: Supports one-time access tokens (`US:...`) and full JSON config
|
||||
- **Keeper Notation Support**: Query fields (`field`), custom fields (`custom_field`), and file attachments (`file`)
|
||||
- **Multiple Output Formats**: Environment variables, file output, and output variables
|
||||
- **Secure Storage**: Secrets written to `/harness/secrets/` with secure file permissions
|
||||
- **Automatic Validation**: Validates token format and required config fields
|
||||
- **Base64 Decoding**: Automatically decodes base64-encoded tokens
|
||||
## Features
|
||||
|
||||
## Architecture
|
||||
|
||||
### Zero-Knowledge Design
|
||||
|
||||
The plugin ensures zero-knowledge security through the following design:
|
||||
|
||||
1. **Harness CI/CD** calls the custom Docker-based plugin
|
||||
2. **Plugin authenticates** using KSM configuration stored in Harness Secrets Manager
|
||||
3. **Secrets are retrieved** directly from Keeper Vault at runtime
|
||||
4. **Decrypted secrets** are returned as step outputs for downstream stages
|
||||
5. **No external storage**: Secrets never pass through Harness systems in decrypted form
|
||||
- Retrieve secrets from the Keeper Vault within the Harness CI pipeline
|
||||
- Support for standard fields, custom fields, and file attachments
|
||||
- Zero-knowledge architecture - secrets retrieved directly from Keeper at runtime
|
||||
|
||||
All secret flow happens strictly between the plugin container and Keeper, while Harness provides orchestration, governance, and secure secret injection across the pipeline.
|
||||
|
||||
### Components
|
||||
|
||||
1. **`src/index.js`**: Main plugin logic
|
||||
- Token processing and validation
|
||||
- Keeper Secrets Manager integration
|
||||
- Secret fetching using Keeper Notation
|
||||
- Secret distribution
|
||||
|
||||
2. **`entrypoint.sh`**: Container entrypoint script
|
||||
- Executes the Node.js plugin
|
||||
- Captures and processes plugin output
|
||||
- Writes secrets to Harness directories
|
||||
- Manages security and cleanup
|
||||
|
||||
3. **`Example/Pipeline.yaml`**: Example Harness CI pipeline
|
||||
- Demonstrates plugin configuration
|
||||
- Shows secret consumption patterns
|
||||
|
||||
### Execution Flow
|
||||
|
||||
```
|
||||
1. Container starts → entrypoint.sh executes
|
||||
2. entrypoint.sh → Runs: node /app/src/index.js
|
||||
3. Plugin processes:
|
||||
- Reads KSM_CONFIG from environment
|
||||
- Validates and decodes token (base64 if needed)
|
||||
- Initializes Keeper storage
|
||||
- Fetches secrets from Keeper using Keeper Notation
|
||||
- Outputs secrets to stdout (with ENV:/OUT: prefixes)
|
||||
4. entrypoint.sh captures stdout:
|
||||
- Parses ENV:/OUT: prefixes
|
||||
- Writes to /harness/outputs/outputs.txt (output variables)
|
||||
- Writes to /harness/secrets/ (file-based access)
|
||||
- Sets secure permissions (chmod 600)
|
||||
5. Subsequent steps access secrets via:
|
||||
- Files: /harness/secrets/VARIABLE_NAME (recommended)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Keeper Secrets Manager access** ([Quick Start Guide](https://docs.keeper.io/secrets-manager/secrets-manager/quick-start-guide))
|
||||
- **Secrets Manager addon enabled** for your Keeper account
|
||||
- **Membership in a Role** with the Secrets Manager enforcement policy enabled
|
||||
- **A Keeper Secrets Manager Application** with secrets shared to it
|
||||
- **An initialized Keeper Secrets Manager Configuration** (JSON or one-time token)
|
||||
- **Record UID(s)** for the secrets you want to access ⚠️ **REQUIRED**
|
||||
- **Harness CI/CD account** with a project set up
|
||||
- **Docker** (for building the plugin image)
|
||||
- **Node.js 18+** (for local development)
|
||||
- Keeper Secrets Manager access with Application configured
|
||||
- Harness CI account with project setup
|
||||
- KSM configuration (one-time access token `US:...` or Base64-encoded token or JSON confign)
|
||||
|
||||
## Installation
|
||||
## About
|
||||
|
||||
### Build and Push Docker Image
|
||||
The plugin retrieves secrets from Keeper Secrets Manager and stores them in **Harness pipeline volume storage** at `/harness/secrets/` directory for reliable file-based access by subsequent steps.
|
||||
|
||||
Build the plugin Docker image:
|
||||
**Important:** Secrets are stored in `/harness/secrets/` directory (Harness shared workspace volume), not as Harness environment variables.
|
||||
|
||||
```bash
|
||||
docker build -t your-dockerhub-username/harness-keeper-plugin:latest .
|
||||
All secrets are:
|
||||
- **Pipeline-scoped**: Only accessible during the current pipeline execution
|
||||
- **Automatically cleaned up**: Harness CI removes all secrets after pipeline completion
|
||||
- **Securely stored**: Files have restricted permissions (600 - owner read/write only)
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
harness_keeper_plugin/
|
||||
├── src/
|
||||
│ └── index.js # Main plugin logic
|
||||
├── entrypoint.sh # Container entrypoint script
|
||||
├── Dockerfile # Docker image definition
|
||||
├── package.json # Node.js dependencies
|
||||
├── Example/
|
||||
│ └── Pipeline.yaml # Example Harness CI pipeline
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
Push to your container registry:
|
||||
|
||||
```bash
|
||||
docker push your-dockerhub-username/harness-keeper-plugin:latest
|
||||
```
|
||||
|
||||
**Note:** Replace `your-dockerhub-username` with your actual Docker Hub username or container registry path.
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Create Keeper Access Token
|
||||
|
||||
#### Option A: One-Time Access Token
|
||||
1. Log in to Keeper Security
|
||||
2. Navigate to Secrets Manager
|
||||
3. Generate a one-time access token (format: `US:xxxxx`)
|
||||
4. Base64 encode (optional but recommended):
|
||||
```bash
|
||||
echo -n "US:your-token-here" | base64
|
||||
```
|
||||
|
||||
#### Option B: Full JSON Config
|
||||
Create a JSON config with:
|
||||
```json
|
||||
{
|
||||
"hostname": "keepersecurity.com",
|
||||
"clientId": "your-client-id",
|
||||
"privateKey": "your-private-key"
|
||||
}
|
||||
```
|
||||
Base64 encode:
|
||||
```bash
|
||||
echo -n '{"hostname":"...","clientId":"...","privateKey":"..."}' | base64
|
||||
```
|
||||
|
||||
### 2. Create Harness Secret
|
||||
|
||||
In Harness, create a secret:
|
||||
- **Type**: TEXT or FILE (both work)
|
||||
- **Name**: `KSM_CONFIG` (or your preferred name)
|
||||
- **Value**: Your base64-encoded token or JSON config
|
||||
|
||||
### 3. Configure Pipeline
|
||||
|
||||
**Before you begin:** You must have the Record UID(s) for the secrets you want to access. See "How to Get Record UID" section below.
|
||||
## Quick Start
|
||||
|
||||
```yaml
|
||||
pipeline:
|
||||
@@ -161,15 +73,15 @@ pipeline:
|
||||
name: Fetch_Keeper_Secrets
|
||||
identifier: Fetch_Keeper_Secrets
|
||||
spec:
|
||||
image: your-dockerhub-username/harness-keeper-plugin:latest
|
||||
image: dhborse/keeper-harness-plugin
|
||||
settings:
|
||||
secrets: |
|
||||
JQPso5SMBpP29gKKGp6Enw/field/password > env:DB_PASSWORD
|
||||
JQPso5SMBpP29gKKGp6Enw/field/login > env:DB_USERNAME
|
||||
JQPso5SMBpP29gKKGp6Enw/custom_field/API_Key > env:API_KEY
|
||||
JQPso5SMBpP29gKKGp6Enw/file/api.key > file:/app/config/api.key
|
||||
VeYTRo-PHElAwfQT6f0TIA/field/password > DB_PASSWORD
|
||||
VeYTRo-PHElAwfQT6f0TIA/field/login > DB_USERNAME
|
||||
VeYTRo-PHElAwfQT6f0TIA/custom_field/text > KEEPER_TEXT
|
||||
VeYTRo-PHElAwfQT6f0TIA/file/credentials.txt > FILE_DATA
|
||||
envVariables:
|
||||
KSM_CONFIG: <+secrets.getValue("KSM_CONFIG")>
|
||||
KSM_CONFIG: <+secrets.getValue("Test_File_secret")>
|
||||
- step:
|
||||
type: Run
|
||||
name: Use_Keeper_Secrets
|
||||
@@ -178,116 +90,79 @@ pipeline:
|
||||
image: alpine:3.20
|
||||
shell: Sh
|
||||
command: |
|
||||
if [ -f /harness/secrets/DB_USERNAME ] && [ -f /harness/secrets/DB_PASSWORD ]; then
|
||||
DB_USERNAME=$(cat /harness/secrets/DB_USERNAME)
|
||||
DB_PASSWORD=$(cat /harness/secrets/DB_PASSWORD)
|
||||
API_KEY=$(cat /harness/secrets/API_KEY)
|
||||
echo "Secrets retrieved successfully"
|
||||
# Use secrets in your build/deploy process
|
||||
echo "Username: $DB_USERNAME"
|
||||
echo "Password: $DB_PASSWORD"
|
||||
fi
|
||||
|
||||
if [ -f /harness/secrets/FILE_DATA ]; then
|
||||
FILE_DATA=$(cat /harness/secrets/FILE_DATA)
|
||||
echo "File Data: $FILE_DATA"
|
||||
fi
|
||||
```
|
||||
|
||||
**Note:** `envVariables` is used ONLY to pass the Keeper configuration token to the plugin. The actual secrets retrieved from Keeper are stored in Harness pipeline volume storage at `/harness/secrets/`.
|
||||
## Inputs
|
||||
|
||||
## Usage
|
||||
### KSM_CONFIG
|
||||
|
||||
### Secret Mapping Format
|
||||
Keeper Secrets Manager configuration for authentication. Store in Harness secrets and reference:
|
||||
|
||||
```
|
||||
<keeper-notation> > <destination>
|
||||
```yaml
|
||||
envVariables:
|
||||
KSM_CONFIG: <+secrets.getValue("Keeper_Config_Secret")>
|
||||
```
|
||||
|
||||
### Keeper Notation Format
|
||||
**Supported Formats:**
|
||||
- One-time access token: `US:xxxxx`
|
||||
- JSON configuration: `{"hostname": "...", "clientId": "...", "privateKey": "..."}`
|
||||
- Base64-encoded token or JSON config
|
||||
|
||||
### Secrets
|
||||
|
||||
Keeper Notation queries mapping secrets to destinations:
|
||||
|
||||
**Format:** `<keeper-notation> > <destination>`
|
||||
|
||||
**Example:**
|
||||
```yaml
|
||||
secrets: |
|
||||
VeYTRo-PHElAwfQT6f0TIA/field/password > DB_PASSWORD
|
||||
VeYTRo-PHElAwfQT6f0TIA/field/login > DB_USERNAME
|
||||
VeYTRo-PHElAwfQT6f0TIA/custom_field/text > KEEPER_TEXT
|
||||
VeYTRo-PHElAwfQT6f0TIA/file/credentials.txt > FILE_DATA
|
||||
```
|
||||
|
||||
## Keeper Notation Format
|
||||
|
||||
The plugin supports three types of Keeper Notation queries:
|
||||
|
||||
1. **Standard Fields**: `<record-uid>/field/<field-type>`
|
||||
- Examples: `password`, `login`, `url`, `text`, `host`, etc.
|
||||
| Type | Format | Example |
|
||||
|------|--------|---------|
|
||||
| **Standard Fields** | `<record-uid>/field/<field-type>` | `VeYTRo.../field/password` |
|
||||
| **Custom Fields** | `<record-uid>/custom_field/<field-label>` | `VeYTRo.../custom_field/API_Key` |
|
||||
| **File Attachments** | `<record-uid>/file/<file-name>` | `VeYTRo.../file/credentials.txt` |
|
||||
|
||||
2. **Custom Fields**: `<record-uid>/custom_field/<field-label>`
|
||||
- Examples: `API_Key`, `My Custom Field`, `Environment`, etc.
|
||||
**Note:** Record UID is the unique identifier for a secret record in Keeper. Get it from Keeper Vault → Record details → Record UID.
|
||||
|
||||
3. **File Attachments**: `<record-uid>/file/<file-name>`
|
||||
- Examples: `api.key`, `certificate.pem`, `config.json`, etc.
|
||||
## Destination Format
|
||||
|
||||
**Important:** The first part (e.g., `JQPso5SMBpP29gKKGp6Enw`) is the **Record UID**, the unique identifier for a specific secret record in Keeper.
|
||||
The destination defines where the secret is stored:
|
||||
|
||||
### Destination Types
|
||||
| Format | Description | Output Location |
|
||||
|--------|-------------|----------------|
|
||||
| `VARIABLE_NAME` | Default output (recommended) | `/harness/secrets/VARIABLE_NAME` |
|
||||
|
||||
| Destination Prefix | Description | Output Location |
|
||||
|-------------------|-------------|----------------|
|
||||
| `env:VARIABLE_NAME` | Environment variable | `/harness/secrets/VARIABLE_NAME` |
|
||||
| `file:/path/to/file` | File output | Specified file path |
|
||||
| `VARIABLE_NAME` (default) | Output variable | `/harness/secrets/VARIABLE_NAME` |
|
||||
|
||||
### Examples
|
||||
|
||||
#### 1. Standard Field → Environment Variable
|
||||
**Examples:**
|
||||
```yaml
|
||||
secrets: |
|
||||
JQPso5SMBpP29gKKGp6Enw/field/password > env:DB_PASSWORD
|
||||
JQPso5SMBpP29gKKGp6Enw/field/login > env:DB_USERNAME
|
||||
```
|
||||
- Creates files: `/harness/secrets/DB_PASSWORD`, `/harness/secrets/DB_USERNAME`
|
||||
- Access: `DB_PASSWORD=$(cat /harness/secrets/DB_PASSWORD)`
|
||||
|
||||
#### 2. Custom Field → Output Variable
|
||||
```yaml
|
||||
secrets: |
|
||||
JQPso5SMBpP29gKKGp6Enw/custom_field/API_Key > API_KEY
|
||||
JQPso5SMBpP29gKKGp6Enw/custom_field/Environment > ENV_NAME
|
||||
```
|
||||
- Creates files: `/harness/secrets/API_KEY`, `/harness/secrets/ENV_NAME`
|
||||
- Access: `API_KEY=$(cat /harness/secrets/API_KEY)`
|
||||
|
||||
#### 3. File Attachment → File Path
|
||||
```yaml
|
||||
secrets: |
|
||||
JQPso5SMBpP29gKKGp6Enw/file/api.key > file:/app/config/api.key
|
||||
JQPso5SMBpP29gKKGp6Enw/file/certificate.pem > file:/app/ssl/cert.pem
|
||||
```
|
||||
- Downloads files and writes to specified paths
|
||||
- Access: `cat /app/config/api.key`
|
||||
|
||||
#### 4. Mixed Usage
|
||||
```yaml
|
||||
secrets: |
|
||||
JQPso5SMBpP29gKKGp6Enw/field/password > env:DB_PASSWORD
|
||||
JQPso5SMBpP29gKKGp6Enw/custom_field/API_Key > API_KEY
|
||||
JQPso5SMBpP29gKKGp6Enw/file/config.json > file:/app/config.json
|
||||
# Default: saves to /harness/secrets/DB_PASSWORD
|
||||
VeYTRo-PHElAwfQT6f0TIA/field/password > DB_PASSWORD
|
||||
```
|
||||
|
||||
### How to Get Record UID
|
||||
## Accessing Secrets
|
||||
|
||||
The Record UID is **mandatory** and identifies which specific secret record to retrieve. Obtain it using one of these methods:
|
||||
|
||||
**Method 1: From Keeper Web Vault**
|
||||
1. Log in to your Keeper Vault
|
||||
2. Open the secret record you want to access
|
||||
3. Click on the record details/info icon
|
||||
4. The Record UID is displayed in the record information (usually at the bottom)
|
||||
5. Copy the Record UID (format: `JQPso5SMBpP29gKKGp6Enw`)
|
||||
|
||||
**Method 2: Using Keeper Commander CLI**
|
||||
```bash
|
||||
keeper list
|
||||
# This will show all records with their UIDs
|
||||
```
|
||||
|
||||
**Method 3: Using Keeper Secrets Manager SDK/API**
|
||||
- Use the Keeper Secrets Manager SDK to list records and retrieve their UIDs
|
||||
- The Record UID is returned when querying records
|
||||
|
||||
**Method 4: From Keeper Admin Console**
|
||||
1. Navigate to Secrets Manager in Admin Console
|
||||
2. View the Application and its shared records
|
||||
3. Record UIDs are visible in the record list
|
||||
|
||||
**Note:** The one-time access token (`US:...`) or JSON config is used to **authenticate** and access Keeper, but you still need the **Record UID** to specify which secret record to retrieve.
|
||||
|
||||
## Accessing Secrets in Subsequent Steps
|
||||
|
||||
### Method 1: File-Based Access (Recommended)
|
||||
|
||||
The most reliable method for accessing secrets in subsequent steps:
|
||||
Secrets are stored in `/harness/secrets/` directory. Read them in subsequent steps:
|
||||
|
||||
```yaml
|
||||
- step:
|
||||
@@ -297,177 +172,52 @@ The most reliable method for accessing secrets in subsequent steps:
|
||||
image: alpine:3.20
|
||||
shell: Sh
|
||||
command: |
|
||||
DB_PASSWORD=$(cat /harness/secrets/DB_PASSWORD)
|
||||
DB_USERNAME=$(cat /harness/secrets/DB_USERNAME)
|
||||
API_KEY=$(cat /harness/secrets/API_KEY)
|
||||
# Use secrets in your process
|
||||
echo "Secrets loaded successfully"
|
||||
# Clean up after use (optional)
|
||||
rm -f /harness/secrets/DB_PASSWORD /harness/secrets/DB_USERNAME
|
||||
DB_PASSWORD=$(cat /harness/secrets/DB_PASSWORD)
|
||||
FILE_DATA=$(cat /harness/secrets/FILE_DATA)
|
||||
# Use secrets in your build/deploy process
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Most reliable method
|
||||
- No truncation issues
|
||||
- Works with all secret types
|
||||
- Works with all secret types including binary files
|
||||
- Supports long values and special characters
|
||||
|
||||
### Method 2: Direct File Access (for file: prefix)
|
||||
## Secret Storage
|
||||
|
||||
When using `file:` prefix, files are written directly to the specified path:
|
||||
|
||||
```yaml
|
||||
- step:
|
||||
type: Run
|
||||
name: Use_File
|
||||
spec:
|
||||
command: |
|
||||
# File already at /app/config/api.key
|
||||
cat /app/config/api.key
|
||||
```
|
||||
|
||||
## Secret Storage Mechanism
|
||||
|
||||
**Important:** This plugin uses **Harness pipeline volume storage**, not Harness environment variables.
|
||||
|
||||
- **Storage Location:** `/harness/secrets/` directory (Harness shared workspace volume)
|
||||
- **Storage Type:** File-based storage on pipeline-scoped volume
|
||||
- **Access Method:** Read files directly using `cat` or file operations
|
||||
- **Location:** `/harness/secrets/` directory (Harness shared workspace volume)
|
||||
- **Permissions:** `600` (owner read/write only)
|
||||
- **Scope:** Pipeline execution only - automatically cleaned up after completion
|
||||
- **Permissions:** Files have `600` permissions (owner read/write only)
|
||||
- **Access:** Read files directly using `cat` or file operations
|
||||
|
||||
The `envVariables` section in the plugin step is **only** used to pass the `KSM_CONFIG` (Keeper configuration token) to the plugin. The actual secrets retrieved from Keeper are written to files in `/harness/secrets/` directory.
|
||||
The `envVariables` section is **only** used to pass `KSM_CONFIG` to the plugin. Actual secrets are written to files in `/harness/secrets/` directory.
|
||||
|
||||
## Security
|
||||
|
||||
### Zero-Knowledge Architecture
|
||||
|
||||
- **Direct Retrieval**: Secrets are retrieved directly from Keeper Vault at runtime
|
||||
- **Zero-Knowledge**: Secrets retrieved directly from Keeper Vault at runtime
|
||||
- **No Decrypted Storage**: Secrets never pass through Harness systems in decrypted form
|
||||
- **Pipeline-Scoped**: All secrets are scoped to the current pipeline execution
|
||||
- **Automatic Cleanup**: Harness CI automatically cleans up `/harness/outputs/` and `/harness/secrets/` after pipeline completion
|
||||
- **File Permissions**: Secrets written to `/harness/secrets/` have `600` permissions (owner read/write only)
|
||||
- **No Persistence**: Secrets are not accessible outside the pipeline execution context
|
||||
- **Pipeline-Scoped**: All secrets scoped to current pipeline execution
|
||||
- **Automatic Cleanup**: Harness CI removes secrets after pipeline completion
|
||||
- **File Permissions**: Files have `600` permissions (owner read/write only)
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use one-time access tokens** instead of permanent credentials when possible
|
||||
2. **Base64 encode tokens** before storing in Harness secrets
|
||||
3. **Clean up secret files** after use in pipeline steps
|
||||
4. **Use appropriate secret scope** (project/org/account)
|
||||
5. **Verify Record UIDs** before configuring pipelines
|
||||
1. Use one-time access tokens instead of permanent credentials
|
||||
2. Base64 encode tokens before storing in Harness secrets
|
||||
3. Clean up secret files after use in pipeline steps
|
||||
4. Use appropriate secret scope (project/org/account)
|
||||
5. Verify Record UIDs before configuring pipelines
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Token Not Found
|
||||
|
||||
**Error:** `KSM config is required`
|
||||
|
||||
**Solutions:**
|
||||
- Verify the secret exists in Harness
|
||||
- Check the secret scope matches your project
|
||||
- Ensure the expression `<+secrets.getValue("KSM_CONFIG")>` is correct
|
||||
- Verify secret name matches exactly
|
||||
|
||||
### Invalid Token Format
|
||||
|
||||
**Error:** `Invalid token format - expected US:xxxxx or JSON config`
|
||||
|
||||
**Solutions:**
|
||||
- Verify the token starts with `US:` (for one-time tokens)
|
||||
- Check if the token is base64 encoded (it will be auto-decoded)
|
||||
- For JSON config, ensure it starts with `{` and has required fields: `hostname`, `clientId`, `privateKey`
|
||||
|
||||
### Token Already Used / Expired
|
||||
|
||||
**Error:** `Failed: token ... invalid/expired`
|
||||
|
||||
**Solutions:**
|
||||
1. Generate a new token in Keeper Security
|
||||
2. Base64 encode: `echo -n "US:new-token" | base64`
|
||||
3. Update the Harness secret with the new token
|
||||
4. Run the pipeline again
|
||||
|
||||
**Note:** One-time access tokens are single-use - generate a new token for each use or use JSON config for reusable credentials.
|
||||
|
||||
### Secret File Not Found
|
||||
|
||||
**Error:** `ERROR: Secret files not found` or `Value not found for notation: <record-uid>/field/<field-type>`
|
||||
|
||||
**Solutions:**
|
||||
- **Verify Record UID is correct**: The Record UID must match exactly (case-sensitive)
|
||||
- **Check Record UID exists**: Ensure the record exists in Keeper and is shared to your Secrets Manager Application
|
||||
- **Verify access permissions**: The one-time token or JSON config must have access to the record
|
||||
- **Check field name**: Ensure the field type (`password`, `login`, `text`, etc.) or custom field label exists in the record
|
||||
- **Verify Application sharing**: The record must be shared to the Secrets Manager Application you're using
|
||||
- **Check logs**: `ls -la /harness/secrets/` to see what files were created
|
||||
- **Verify notation format**: Ensure the notation follows the correct format (e.g., `/field/password`, `/custom_field/MyLabel`, `/file/filename`)
|
||||
|
||||
### Expression Not Resolved
|
||||
|
||||
**Error:** Token preview shows `${...}` or `<+...>` unresolved
|
||||
|
||||
**Solutions:**
|
||||
- Use `envVariables` (not `settings`) for Harness expressions when passing `KSM_CONFIG` to the plugin
|
||||
- Verify secret name matches exactly
|
||||
- Check secret scope matches the reference
|
||||
- Ensure you're using the correct Harness expression syntax: `<+secrets.getValue("SECRET_NAME")>`
|
||||
|
||||
### Harness Expression Not Resolved
|
||||
|
||||
**Error:** `Harness expression not resolved`
|
||||
|
||||
**Solutions:**
|
||||
- Ensure `KSM_CONFIG` is passed via `envVariables`, not `settings`
|
||||
- Verify the secret exists and is accessible in the current scope
|
||||
- Check that the Harness expression syntax is correct: `<+secrets.getValue("SECRET_NAME")>`
|
||||
|
||||
## Local Development
|
||||
|
||||
### Test Locally
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export PLUGIN_SECRETS="JQPso5SMBpP29gKKGp6Enw/field/password > env:DB_PASSWORD"
|
||||
export KSM_CONFIG="VVM6..." # Your base64 encoded token or JSON config
|
||||
|
||||
# Run the plugin
|
||||
node ./src/index.js
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
harness_keeper_plugin/
|
||||
├── src/
|
||||
│ └── index.js # Main plugin logic
|
||||
├── entrypoint.sh # Container entrypoint script
|
||||
├── Dockerfile # Docker image definition
|
||||
├── package.json # Node.js dependencies
|
||||
├── Example/
|
||||
│ └── Pipeline.yaml # Example Harness CI pipeline
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Example Pipeline
|
||||
|
||||
See `Example/Pipeline.yaml` for a complete working example that demonstrates:
|
||||
- Plugin configuration
|
||||
- Secret mapping with multiple record UIDs
|
||||
- File-based secret consumption
|
||||
- Error handling and failure strategies
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Check the [Keeper Security Documentation](https://docs.keeper.io/secrets-manager/)
|
||||
- Review [Keeper Notation Documentation](https://docs.keeper.io/secrets-manager/secrets-manager/keeper-notation/)
|
||||
- Review Harness CI documentation for plugin configuration
|
||||
- Verify token format and Keeper record permissions
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
| Error | Solution |
|
||||
|-------|----------|
|
||||
| `KSM config is required` | Verify secret exists and expression `<+secrets.getValue("SECRET_NAME")>` is correct |
|
||||
| `Invalid token format` | Check token starts with `US:` or `{` for JSON config |
|
||||
| `Value not found for notation` | Verify Record UID, field name (case-sensitive), and Application permissions |
|
||||
| `Failed to download file` | Check file exists in record, name matches exactly, and Application has file access permissions |
|
||||
| `Harness expression not resolved` | Verify secret reference expression and secret scope (project/org/account) |
|
||||
|
||||
## Version History
|
||||
|
||||
@@ -478,3 +228,8 @@ ISC
|
||||
- File-based secret access
|
||||
- Base64 token decoding
|
||||
- Secure file permissions and cleanup
|
||||
|
||||
## References
|
||||
|
||||
- [Keeper Notation Documentation](https://docs.keeper.io/en/keeperpam/secrets-manager/about/keeper-notation)
|
||||
- [Keeper Secrets Manager Quick Start Guide](https://docs.keeper.io/en/keeperpam/secrets-manager/quick-start-guide)
|
||||
+31
-8
@@ -120,19 +120,42 @@ const runPlugin = async () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const notationIsFile = input.notation.includes('/file/');
|
||||
const isFileReference = typeof secret === 'object' &&
|
||||
secret !== null &&
|
||||
(secret.fileId || secret.fileUid || secret.url || notationIsFile);
|
||||
|
||||
let data;
|
||||
|
||||
if (isFileReference) {
|
||||
try {
|
||||
const fileData = await downloadFile(secret);
|
||||
data = fileData instanceof Uint8Array ? Buffer.from(fileData) :
|
||||
Buffer.isBuffer(fileData) ? fileData : Buffer.from(fileData);
|
||||
} catch (downloadError) {
|
||||
core.error(`Failed to download file for notation ${input.notation}: ${downloadError.message}`);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
data = Buffer.isBuffer(secret) ? secret :
|
||||
typeof secret === 'string' ? Buffer.from(secret, 'utf8') :
|
||||
Buffer.from(String(secret), 'utf8');
|
||||
}
|
||||
|
||||
if (input.destinationType === 'file') {
|
||||
const fullPath = path.resolve(input.destination);
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
const data = (typeof secret === 'object' && secret.fileId)
|
||||
? await downloadFile(secret)
|
||||
: secret;
|
||||
fs.writeFileSync(fullPath, data);
|
||||
} else if (input.destinationType === 'environment') {
|
||||
// Output with ENV: prefix for environment variables
|
||||
console.log(`ENV:${input.destination}='${secret}'`);
|
||||
} else {
|
||||
// Output variable (for Harness output variables)
|
||||
console.log(`OUT:${input.destination}='${secret}'`);
|
||||
fs.mkdirSync('/harness/secrets', { recursive: true });
|
||||
const secretFilePath = path.join('/harness/secrets', input.destination);
|
||||
fs.writeFileSync(secretFilePath, data);
|
||||
fs.chmodSync(secretFilePath, 0o600);
|
||||
|
||||
if (input.destinationType === 'environment') {
|
||||
const outputValue = Buffer.isBuffer(data) ? data.toString('utf8') : String(data);
|
||||
console.log(`ENV:${input.destination}='${outputValue}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user