# Harness CI Plugin

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FNJsoKtG8Uf0Tm9M4MXcl%2Fkeeper%2Bharness.png?alt=media&#x26;token=0ac51e07-5eec-4bfc-bdd4-57657e94917c" alt=""><figcaption></figcaption></figure>

### Features

* Retrieve secrets from the Keeper Vault within the Harness CI pipeline
* Set secret credentials as build arguments in Harness CI pipeline
* Copy secure files from the Keeper Vault
* Plugin URL: <https://plugins.drone.io/plugins/keeper-plugin>

{% hint style="info" %}
For a complete list of Keeper Secrets Manager features see the [Overview](https://docs.keeper.io/en/keeperpam/secrets-manager/overview)
{% endhint %}

### Prerequisites

#### Keeper Secrets Manager Requirements

| Requirement       | Description                                                                                                                                                                         |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| KSM Access        | Active Keeper Secrets Manager subscription ([Quick Start Guide](https://docs.keeper.io/en/keeperpam/secrets-manager/quick-start-guide))                                             |
| Add-on Enabled    | Secrets Manager add-on enabled for your Keeper account                                                                                                                              |
| Role Membership   | Member of a Role with Secrets Manager enforcement policy enabled                                                                                                                    |
| KSM Application   | A configured [Keeper Secrets Manager](https://docs.keeper.io/en/keeperpam/secrets-manager/about/terminology) Application with secrets shared to it                                  |
| KSM Configuration | An initialized [configuration](https://docs.keeper.io/en/keeperpam/secrets-manager/about/secrets-manager-configuration) (Base64 token or One-Time Access Token or JSON config file) |

#### Harness CI Requirements

* Active Harness [account](https://app.harness.io/auth/#/signin)
* A pipeline setup of [Harness CI](https://developer.harness.io/docs/continuous-integration/use-ci/prep-ci-pipeline-components/)
* Understanding of Harness CI [secrets management](https://developer.harness.io/docs/platform/secrets/add-use-text-secrets/)
* Install the [Keeper Plugin](https://plugins.drone.io/plugins/keeper-plugin)

### Configuration Types

The plugin supports three authentication methods. Choose the one that fits your security requirements:

| Type                         | Harness Secret Type | Use Case                     |
| ---------------------------- | ------------------- | ---------------------------- |
| One-Time Access Token (OTAT) | Text Secret         | Single-use, highest security |
| Base64 Token                 | Text Secret         | Reusable, standard security  |
| JSON Config File             | File Secret         | Full configuration, reusable |

### Setup Guide

#### Step 1: Configure Keeper Vault

1. Create a [Shared Folder](https://docs.keeper.io/enterprise-guide/sharing/folders) in Keeper Vault
2. [Create records](https://docs.keeper.io/enterprise-guide/creating-vault-records) containing your secrets inside the shared folder
3. Create a Secrets Manager [Application](https://docs.keeper.io/en/keeperpam/quick-start-guide#create-a-secrets-manager-application)
4. Generate your preferred credential type:
   * One-Time Access Token ([OTAT](https://docs.keeper.io/en/keeperpam/secrets-manager/about/one-time-token))
   * Base64 [Token or JSON](https://docs.keeper.io/en/keeperpam/about/secrets-manager-configuration#creating-a-secrets-manager-configuration) Config

#### Step 2: Create Harness CI Secret

Navigate to: **Project → Project Setup → Secrets → + New Secret**

**Option A: One-Time Access Token (Text Secret)**

1. Click **+ New Secret → Text**
2. Configure:
   * **Secret Name:** `keeper_otat_secret`
   * **Secret Value:** Paste the token (e.g., `US:xxxxx...`)
   * **Scope:** Project (recommended)
3. Click **Save**

{% hint style="info" %}
**Note:** One-time access tokens are single-use. Generate a new token for each pipeline run.
{% endhint %}

**Option B: Base64 Token (Text Secret)**

1. Click **+ New Secret → Text**
2. Configure:
   * **Secret Name:** `keeper_base64_secret`
   * **Secret Value:** Paste the base64-encoded token
   * **Scope:** Project (recommended)
3. Click **Save**

{% hint style="info" %}
**Note:** Ensure no line breaks or whitespace in the base64 string.
{% endhint %}

**Option C: JSON Config File (File Secret)**

1. Click **+ New Secret → File**
2. Configure:
   * **Secret Name:** `keeper_ksm_config_file`
   * **Upload File:** Select your KSM JSON config file
   * **Scope:** Project (recommended)
3. Click **Save**

Expected JSON structure:

```json
{
  "hostname": "keepersecurity.com",
  "clientId": "your-client-id",
  "privateKey": "your-private-key"
}
```

#### Step 3: Reference in Pipeline

Use the following syntax to reference your secret:

```yaml
settings:
  ksm_config: <+secrets.getValue("your_secret_identifier_name")>
```

### Quick Start

#### Pipeline Example

```yaml
pipeline:
  name: harness_keeper_plugin
  identifier: harness_keeper_plugin
  projectIdentifier: default_project
  orgIdentifier: default
  stages:
    - stage:
        name: HkpCI
        identifier: HkpCI
        type: CI
        spec:
          cloneCodebase: false
          platform:
            os: Linux
            arch: Amd64
          runtime:
            type: Cloud
            spec: {}
          execution:
            steps:
              - step:
                  type: Plugin
                  name: Fetch_Keeper_Secrets
                  identifier: Fetch_Keeper_Secrets
                  spec:
                    image: keeper/harness-plugin:latest
                    settings:
                      ksm_config: <+secrets.getValue("keeper_base64_secret")>
                      secrets: |
                        RECORD_UID/field/password > PASSWORD
                        RECORD_UID/field/login > USERNAME
              - step:
                  type: Run
                  name: Use_Secrets
                  identifier: Use_Secrets
                  spec:
                    image: alpine:3.20
                    shell: Sh
                    command: |
                      if [ -f /harness/secrets/USERNAME ] && [ -f /harness/secrets/PASSWORD ]; then
                        USERNAME=$(cat /harness/secrets/USERNAME)
                        PASSWORD=$(cat /harness/secrets/PASSWORD)
                        echo "Username: $USERNAME"
                        echo "Password retrieved successfully"
                      else
                        echo "Error: Secret files not found"
                        exit 1
                      fi
```

{% hint style="info" %}
**Important:** Replace `RECORD_UID` with the actual Record UID from your Keeper Vault.
{% endhint %}

Once the pipeline execution end then we will be able to see secrets printed in Print secrets step logs.

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FaggGT7TJmTqYDSEMMqYp%2Fimage.png?alt=media&#x26;token=f269170e-e348-4b1e-8e44-226fda09c6b1" alt=""><figcaption></figcaption></figure>

### Secrets Configuration

#### Keeper Notation Syntax

The `secrets` input uses Keeper Notation to specify which secrets to retrieve:

```yaml
settings:
  secrets: |
    RECORD_UID/field/password > PASSWORD
    RECORD_UID/field/login > USERNAME
    RECORD_UID/custom_field/apiKey > API_KEY
    RECORD_UID/file/certificate.crt > CERT_FILE
```

#### Notation Format

```
<record_uid>/<selector>/<field_name> > <destination_name>
```

<table><thead><tr><th width="290.93359375">Component</th><th>Description</th></tr></thead><tbody><tr><td><code>record_uid</code></td><td>The unique identifier of the Keeper record</td></tr><tr><td><code>selector</code></td><td>Type of data: <code>field</code>, <code>custom_field</code>, or <code>file</code></td></tr><tr><td><code>field_name</code></td><td>Name of the field or file to retrieve</td></tr><tr><td><code>destination_name</code></td><td>Output filename in <code>/harness/secrets/</code></td></tr></tbody></table>

#### Selector Types

<table><thead><tr><th width="174.6640625">Selector</th><th width="272.37890625">Description</th><th>Output Location</th></tr></thead><tbody><tr><td><code>field</code></td><td>Standard record fields (login, password, etc.)</td><td><code>/harness/secrets/&#x3C;destination></code></td></tr><tr><td><code>custom_field</code></td><td>Custom fields defined in the record</td><td><code>/harness/secrets/&#x3C;destination></code></td></tr><tr><td><code>file</code></td><td>File attachments</td><td><code>/harness/secrets/&#x3C;destination></code></td></tr></tbody></table>

#### Examples

```yaml
secrets: |
  # Standard fields
  abc123/field/password > DB_PASSWORD
  abc123/field/login > DB_USERNAME
  
  # Custom fields
  xyz789/custom_field/apiKey > API_KEY
  xyz789/custom_field/endpoint > API_ENDPOINT
  
  # File attachments
  def456/file/server.crt > SERVER_CERT
  def456/file/server.key > SERVER_KEY
```

{% hint style="info" %}
**Tip:** For complex values (arrays, key-value pairs), refer to the [Keeper Notation](https://docs.keeper.io/en/keeperpam/secrets-manager/about/keeper-notation) - Predicates documentation.
{% endhint %}

### Local Docker Runner Configuration

When using `runtime: type: docker` ([local runner](https://developer.harness.io/docs/continuous-integration/use-ci/set-up-build-infrastructure/define-a-docker-build-infrastructure/)) instead of Harness Cloud, you must configure [Shared Paths](https://developer.harness.io/docs/continuous-integration/use-ci/prep-ci-pipeline-components/#shared-paths) to share [data](https://developer.harness.io/docs/continuous-integration/use-ci/caching-ci-data/share-ci-data-across-steps-and-stages/#share-data-between-steps-in-a-stage) between pipeline on local:

```yaml
spec:
  cloneCodebase: false
  platform:
    os: Linux
    arch: Amd64
  runtime:
    type: docker
    spec: {}
  sharedPaths:
    - /harness
  execution:
    steps:
      # ... your steps
```

{% hint style="info" %}
**Why?** On Harness Cloud, `/harness` is automatically shared. On local Docker, you must explicitly configure `sharedPaths`.
{% endhint %}

### Security Best Practices

| Practice                   | Description                                                |
| -------------------------- | ---------------------------------------------------------- |
| Use One-Time Access Tokens | Generate fresh tokens for each pipeline run when possible  |
| Clean Up Secret Files      | Delete secret files after use in pipeline steps            |
| Appropriate Scope          | Use project-level scope unless broader access is required  |
| Limit Access               | Restrict who can edit pipelines (editors can read secrets) |

#### Secret Masking - Hiding Secrets from Logs

Harness CI automatically masks secrets in logs when printed to console. However:

* This only obscures output logs
* Pipeline editors can potentially extract secrets
* Always follow the principle of least privilege

### Troubleshooting

#### Common Issues

| Issue                     | Cause                                    | Solution                                                  |
| ------------------------- | ---------------------------------------- | --------------------------------------------------------- |
| `KSM config is required`  | Secret not found or expression incorrect | Verify secret name matches exactly (case-sensitive)       |
| `Missing required fields` | JSON config incomplete                   | Ensure JSON contains `hostname`, `clientId`, `privateKey` |
| Expression not resolved   | Secret scope mismatch                    | Ensure secret scope matches pipeline scope                |
| Token already used        | One-time tokens are single-use           | Generate a new OTAT for each run                          |

#### Issue: Pipeline Works on Cloud but Fails Locally

**Symptoms:** `Fetch_Keeper_Secrets` succeeds but next step fails reading `/harness/secrets/`

**Causes & Solutions:**

1. **Secret name mismatch**
   * Plugin writes: `> USERNAME` and `> PASSWORD`
   * Run step must check: `/harness/secrets/USERNAME` and `/harness/secrets/PASSWORD`
2. **Workspace not shared**
   * Add `sharedPaths: /harness` to your stage spec (see Local Docker Runner Configuration)

#### Debugging: Verify Plugin Output

Add a debug step after the plugin:

```yaml
- step:
    type: Run
    name: Debug_Secrets
    spec:
      image: alpine:3.20
      shell: Sh
      command: |
        echo "Contents of /harness/secrets:"
        ls -la /harness/secrets/ || echo "Directory not found"
```

If no files appear, check:

* Plugin configuration (secret names)
* Workspace sharing (for local runner)
* `ksm_config` setting (map to PLUGIN\_KSM\_CONFIG)
