Skip to content

Commit de44015

Browse files
committed
add ability to send encrypted secrets to disco backend
For now, this uses a hardcoded RSA key for which I threw away the private key, since we don't have the ability to pull JWKs yet This also includes a few test tweaks to help make this easier, and an example folder which produces an output.json showing how this can work Signed-off-by: Ashley Davis <ashley.davis@cyberark.com>
1 parent f8ee4b1 commit de44015

File tree

16 files changed

+585
-48
lines changed

16 files changed

+585
-48
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ predicate.json
1515

1616
_bin
1717
.envrc
18+
19+
examples/encrypted-secrets/output.json

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ go run . agent \
3434
> - [./agent.yaml](./agent.yaml).
3535
> - [./examples/one-shot-secret.yaml](./examples/one-shot-secret.yaml).
3636
> - [./examples/cert-manager-agent.yaml](./examples/cert-manager-agent.yaml).
37+
> - [./examples/encrypted-secrets](./examples/encrypted-secrets) - Send encrypted Kubernetes secrets to CyberArk.
3738
3839
You might also want to run a local echo server to monitor requests sent by the agent:
3940

deploy/charts/disco-agent/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,13 @@ This cluster name will be associated with the data that the agent uploads to the
295295
A short description of the cluster where the agent is deployed (optional).
296296
297297
This description will be associated with the data that the agent uploads to the Discovery and Context service. The description may include contact information such as the email address of the cluster administrator, so that any problems and risks identified by the Discovery and Context service can be communicated to the people responsible for the affected secrets.
298+
#### **config.sendSecrets** ~ `bool`
299+
> Default value:
300+
> ```yaml
301+
> false
302+
> ```
303+
304+
Enable sending of Secret data to CyberArk, in addition to the metadata. When enabled, Secret data is encrypted using envelope encryption using a key managed by CyberArk. Default: false (but default will change to true for a future release)
298305
#### **authentication.secretName** ~ `string`
299306
> Default value:
300307
> ```yaml

deploy/charts/disco-agent/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ spec:
7676
name: {{ .Values.authentication.secretName }}
7777
key: ARK_DISCOVERY_API
7878
optional: true
79+
- name: ARK_SEND_SECRETS
80+
value: {{ .Values.config.sendSecrets | default "false" | quote }}
7981
{{- with .Values.http_proxy }}
8082
- name: HTTP_PROXY
8183
value: {{ . }}

deploy/charts/disco-agent/values.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
},
119119
"period": {
120120
"$ref": "#/$defs/helm-values.config.period"
121+
},
122+
"sendSecrets": {
123+
"$ref": "#/$defs/helm-values.config.sendSecrets"
121124
}
122125
},
123126
"type": "object"
@@ -148,6 +151,11 @@
148151
"description": "Push data every 12 hours unless changed.",
149152
"type": "string"
150153
},
154+
"helm-values.config.sendSecrets": {
155+
"default": false,
156+
"description": "Enable sending of Secret data to CyberArk, in addition to the metadata. When enabled, Secret data is encrypted using envelope encryption using a key managed by CyberArk. Default: false (but default will change to true for a future release)",
157+
"type": "boolean"
158+
},
151159
"helm-values.extraArgs": {
152160
"default": [],
153161
"description": "extraArgs:\n- --logging-format=json\n- --log-level=6 # To enable HTTP request logging",

deploy/charts/disco-agent/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ config:
154154
# be communicated to the people responsible for the affected secrets.
155155
clusterDescription: ""
156156

157+
# Enable sending of Secret data to CyberArk, in addition to the metadata.
158+
# When enabled, Secret data is encrypted using envelope encryption using
159+
# a key managed by CyberArk.
160+
# Default: false (but default will change to true for a future release)
161+
sendSecrets: false
162+
157163
authentication:
158164
secretName: agent-credentials
159165

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Encrypted Secrets Example
2+
3+
This example demonstrates how to use the disco agent to gather Kubernetes secrets and encrypt their data fields.
4+
5+
## Overview
6+
7+
When the `ARK_SEND_SECRETS` environment variable is set to `"true"`, the disco agent will:
8+
9+
0. Fetch an encryption key from the configured endpoint (if running in production) or use a local key for testing
10+
1. Discover Kubernetes secrets in your cluster (excluding common system secret types)
11+
2. Encrypt each secret's data fields using RSA envelope encryption with JWE (JSON Web Encryption) format
12+
3. If running in production, send the encrypted secrets to the configured endpoint; otherwise, write them to `output.json` for testing
13+
14+
The encryption uses:
15+
16+
- **Key Algorithm**: RSA-OAEP-256 (for encrypting the content encryption key)
17+
- **Content Encryption**: AES-256-GCM (for encrypting the actual secret data)
18+
- **Format**: JWE Compact Serialization
19+
20+
Metadata (names, namespaces, labels, annotations) remains in plaintext for discovery purposes, while the sensitive secret data is encrypted. Some keys in Secret data fields are also preserved in the `data` section, for backwards compatibility.
21+
22+
## Prerequisites
23+
24+
1. A running Kubernetes cluster with secrets to discover
25+
3. Go installed
26+
27+
## Configuration File
28+
29+
The `config.yaml` file configures:
30+
31+
- The data gatherer to collect Kubernetes secrets
32+
- Field selectors to exclude system secrets (service account tokens, docker configs, etc.)
33+
- The cluster ID and organization ID for grouping data
34+
35+
## Running the Example
36+
37+
Test the agent locally by running this script:
38+
39+
```bash
40+
./test.sh
41+
```
42+
43+
This will:
44+
45+
- Connect to your current Kubernetes context
46+
- Gather all non-system secrets
47+
- Write the raw data to `output.json`
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# encrypted-secrets config.yaml
2+
#
3+
# An example configuration file demonstrating how to use the disco agent
4+
# to send encrypted secrets to CyberArk Discovery & Context.
5+
#
6+
# The agent will:
7+
# 1. Discover Kubernetes secrets in the cluster
8+
# 2. Encrypt the secret data fields using RSA envelope encryption (JWE format)
9+
# 3. Upload the encrypted secrets to CyberArk Discovery & Context
10+
#
11+
# Example usage:
12+
#
13+
# export ARK_SUBDOMAIN="your-subdomain"
14+
# export ARK_USERNAME="your-username"
15+
# export ARK_SECRET="your-secret"
16+
# export ARK_SEND_SECRETS="true"
17+
#
18+
# go run . agent \
19+
# --agent-config-file examples/encrypted-secrets/config.yaml \
20+
# --one-shot \
21+
# --output-path output.json
22+
#
23+
organization_id: "my-organization"
24+
cluster_id: "my_cluster"
25+
period: 1m
26+
data-gatherers:
27+
- kind: "k8s-dynamic"
28+
name: "k8s/secrets"
29+
config:
30+
resource-type:
31+
version: v1
32+
resource: secrets
33+
# Filter out common system secret types to focus on application secrets
34+
field-selectors:
35+
- type!=kubernetes.io/service-account-token
36+
- type!=kubernetes.io/dockercfg
37+
- type!=kubernetes.io/dockerconfigjson
38+
- type!=kubernetes.io/basic-auth
39+
- type!=kubernetes.io/ssh-auth
40+
- type!=bootstrap.kubernetes.io/token
41+
- type!=helm.sh/release.v1

examples/encrypted-secrets/test.sh

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env bash
2+
# test.sh - Test script for the encrypted secrets example
3+
#
4+
# This script demonstrates running the disco agent with encrypted secrets enabled.
5+
# It will run in one-shot mode and output to a local file for inspection.
6+
7+
set -euo pipefail
8+
9+
# Colors for output
10+
RED='\033[0;31m'
11+
GREEN='\033[0;32m'
12+
YELLOW='\033[1;33m'
13+
NC='\033[0m' # No Color
14+
15+
echo -e "${GREEN}=== Encrypted Secrets Example Test ===${NC}\n"
16+
17+
echo -e "${GREEN}Testing agent with Kubernetes secrets${NC}"
18+
echo ""
19+
20+
# Enable encrypted secrets
21+
export ARK_SEND_SECRETS="true"
22+
23+
# Check Kubernetes connectivity
24+
if ! kubectl cluster-info &> /dev/null; then
25+
echo -e "${RED}Error: Unable to connect to Kubernetes cluster${NC}"
26+
echo "Please ensure your kubeconfig is configured correctly."
27+
exit 1
28+
fi
29+
30+
echo -e "${GREEN}✓ Connected to Kubernetes cluster${NC}"
31+
CONTEXT=$(kubectl config current-context)
32+
echo " Context: ${CONTEXT}"
33+
echo ""
34+
35+
# Check for secrets
36+
SECRET_COUNT=$(kubectl get secrets --all-namespaces --no-headers 2>/dev/null | wc -l | tr -d ' ')
37+
echo "Found ${SECRET_COUNT} secrets in cluster"
38+
echo ""
39+
40+
# Run the agent in one-shot mode with output to file
41+
OUTPUT_FILE="output.json"
42+
echo -e "${GREEN}Running disco agent with encrypted secrets enabled...${NC}"
43+
echo "Command: go run ../.. agent --agent-config-file config.yaml --one-shot --output-path ${OUTPUT_FILE}"
44+
echo ""
45+
46+
if go run ../.. agent \
47+
--agent-config-file config.yaml \
48+
--one-shot \
49+
--output-path "${OUTPUT_FILE}"; then
50+
51+
echo ""
52+
echo -e "${GREEN}✓ Agent completed successfully${NC}"
53+
54+
# Check if output file was created
55+
if [ -f "${OUTPUT_FILE}" ]; then
56+
echo -e "${GREEN}✓ Output file created: ${OUTPUT_FILE}${NC}"
57+
else
58+
echo -e "${RED}✗ Output file was not created${NC}"
59+
exit 1
60+
fi
61+
else
62+
echo ""
63+
echo -e "${RED}✗ Agent failed${NC}"
64+
exit 1
65+
fi

internal/envelope/rsa/keys.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ import (
1010

1111
// This file contains helpers for loading keys. In practice we'll retrieve keys in some format from a DisCo endpoint
1212

13+
const (
14+
// HardcodedPublicKeyPEM contains a temporary hardcoded RSA public key (2048-bit) for envelope encryption.
15+
// This is a TEMPORARY solution for initial development and testing.
16+
// TODO: Replace with dynamic key fetching from CyberArk Discovery & Context API.
17+
HardcodedPublicKeyPEM = `-----BEGIN PUBLIC KEY-----
18+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoeq+dk4aoGdV9xjrnGJt
19+
VbUh5jvkQgynkP+9Ph2NVeoasXWqYOmOVeKOI7Yr58W/L8Mro6C22iSEJrPFgPF6
20+
t+RJsLAsAY6w1Pocq16COeelAWtxhHQGXt77WQKk0kmwhOJZ4VSeiQC4hWLUnq4N
21+
Ft7lwLw/50opTXLuSErrwec/bEV7G/Xp11BMsHGEL7dzpwWAfIrbCEomyWrO/L6p
22+
O3SAgYMdfup5ddnszeCU2FbFQziOkuMLOyir91XXk8wgdSy4IGAEGpwNx88i8fuj
23+
Qafze2aGWUtpWlOEQPP8lH2cj2TGUgLxGITbczJRcwuGIoJBOzAmPDWi/bapj4b6
24+
zQIDAQAB
25+
-----END PUBLIC KEY-----`
26+
27+
// hardcodedUID is a temporary hardcoded UID associated with the hardcoded public key
28+
// It was randomly generated with the macOS "uuidgen" command
29+
hardcodedUID = "A39798E6-8CE7-4E6E-9CF6-24A3C923B3A7"
30+
)
31+
1332
// LoadPublicKeyFromPEM parses an RSA public key from PEM-encoded bytes.
1433
// The PEM block should be of type "PUBLIC KEY" or "RSA PUBLIC KEY".
1534
func LoadPublicKeyFromPEM(pemBytes []byte) (*rsa.PublicKey, error) {
@@ -55,3 +74,16 @@ func LoadPublicKeyFromPEMFile(path string) (*rsa.PublicKey, error) {
5574

5675
return LoadPublicKeyFromPEM(pemBytes)
5776
}
77+
78+
// LoadHardcodedPublicKey loads and parses the hardcoded RSA public key.
79+
// Returns a hardcoded UID associated with the key.
80+
// This is a temporary solution for initial development and testing.
81+
// Returns an error if the hardcoded key is invalid or cannot be parsed.
82+
func LoadHardcodedPublicKey() (*rsa.PublicKey, string, error) {
83+
key, err := LoadPublicKeyFromPEM([]byte(HardcodedPublicKeyPEM))
84+
if err != nil {
85+
return nil, "", err
86+
}
87+
88+
return key, hardcodedUID, nil
89+
}

0 commit comments

Comments
 (0)