-
Notifications
You must be signed in to change notification settings - Fork 24
Add ability to send encrypted secrets to disco backend #770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,3 +15,5 @@ predicate.json | |
|
|
||
| _bin | ||
| .envrc | ||
|
|
||
| examples/encrypted-secrets/output.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Encrypted Secrets Example | ||
|
|
||
| This example demonstrates how to use the disco agent to gather Kubernetes secrets and encrypt their data fields. | ||
|
|
||
| ## Overview | ||
|
|
||
| When the `ARK_SEND_SECRETS` environment variable is set to `"true"`, the disco agent will: | ||
|
|
||
| 0. Fetch an encryption key from the configured endpoint (if running in production) or use a local key for testing | ||
| 1. Discover Kubernetes secrets in your cluster (excluding common system secret types) | ||
| 2. Encrypt each secret's data fields using RSA envelope encryption with JWE (JSON Web Encryption) format | ||
| 3. If running in production, send the encrypted secrets to the configured endpoint; otherwise, write them to `output.json` for testing | ||
|
|
||
| The encryption uses: | ||
|
|
||
| - **Key Algorithm**: RSA-OAEP-256 (for encrypting the content encryption key) | ||
| - **Content Encryption**: AES-256-GCM (for encrypting the actual secret data) | ||
| - **Format**: JWE Compact Serialization | ||
|
|
||
| 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. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. A running Kubernetes cluster with secrets to discover | ||
| 3. Go installed | ||
|
|
||
| ## Configuration File | ||
|
|
||
| The `config.yaml` file configures: | ||
|
|
||
| - The data gatherer to collect Kubernetes secrets | ||
| - Field selectors to exclude system secrets (service account tokens, docker configs, etc.) | ||
| - The cluster ID and organization ID for grouping data | ||
|
|
||
| ## Running the Example | ||
|
|
||
| Test the agent locally by running this script: | ||
|
|
||
| ```bash | ||
| ./test.sh | ||
| ``` | ||
|
|
||
| This will: | ||
|
|
||
| - Connect to your current Kubernetes context | ||
| - Gather all non-system secrets | ||
| - Write the raw data to `output.json` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # encrypted-secrets config.yaml | ||
| # | ||
| # An example configuration file demonstrating how to use the disco agent | ||
| # to send encrypted secrets to CyberArk Discovery & Context. | ||
| # | ||
| # The agent will: | ||
| # 1. Discover Kubernetes secrets in the cluster | ||
| # 2. Encrypt the secret data fields using RSA envelope encryption (JWE format) | ||
| # 3. Upload the encrypted secrets to CyberArk Discovery & Context | ||
| # | ||
| # Example usage: | ||
| # | ||
| # export ARK_SUBDOMAIN="your-subdomain" | ||
| # export ARK_USERNAME="your-username" | ||
| # export ARK_SECRET="your-secret" | ||
| # export ARK_SEND_SECRETS="true" | ||
| # | ||
| # go run . agent \ | ||
| # --agent-config-file examples/encrypted-secrets/config.yaml \ | ||
| # --one-shot \ | ||
| # --output-path output.json | ||
| # | ||
| organization_id: "my-organization" | ||
| cluster_id: "my_cluster" | ||
| period: 1m | ||
| data-gatherers: | ||
| - kind: "k8s-dynamic" | ||
| name: "k8s/secrets" | ||
| config: | ||
| resource-type: | ||
| version: v1 | ||
| resource: secrets | ||
| # Filter out common system secret types to focus on application secrets | ||
| field-selectors: | ||
| - type!=kubernetes.io/service-account-token | ||
| - type!=kubernetes.io/dockercfg | ||
| - type!=kubernetes.io/dockerconfigjson | ||
| - type!=kubernetes.io/basic-auth | ||
| - type!=kubernetes.io/ssh-auth | ||
| - type!=bootstrap.kubernetes.io/token | ||
| - type!=helm.sh/release.v1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| #!/usr/bin/env bash | ||
| # test.sh - Test script for the encrypted secrets example | ||
| # | ||
| # This script demonstrates running the disco agent with encrypted secrets enabled. | ||
| # It will run in one-shot mode and output to a local file for inspection. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| # Colors for output | ||
| RED='\033[0;31m' | ||
| GREEN='\033[0;32m' | ||
| YELLOW='\033[1;33m' | ||
| NC='\033[0m' # No Color | ||
|
|
||
| echo -e "${GREEN}=== Encrypted Secrets Example Test ===${NC}\n" | ||
|
|
||
| echo -e "${GREEN}Testing agent with Kubernetes secrets${NC}" | ||
| echo "" | ||
|
|
||
| # Enable encrypted secrets | ||
| export ARK_SEND_SECRETS="true" | ||
|
|
||
| # Check Kubernetes connectivity | ||
| if ! kubectl cluster-info &> /dev/null; then | ||
| echo -e "${RED}Error: Unable to connect to Kubernetes cluster${NC}" | ||
| echo "Please ensure your kubeconfig is configured correctly." | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo -e "${GREEN}✓ Connected to Kubernetes cluster${NC}" | ||
| CONTEXT=$(kubectl config current-context) | ||
| echo " Context: ${CONTEXT}" | ||
| echo "" | ||
|
|
||
| # Check for secrets | ||
| SECRET_COUNT=$(kubectl get secrets --all-namespaces --no-headers 2>/dev/null | wc -l | tr -d ' ') | ||
| echo "Found ${SECRET_COUNT} secrets in cluster" | ||
| echo "" | ||
|
|
||
| # Run the agent in one-shot mode with output to file | ||
| OUTPUT_FILE="output.json" | ||
| echo -e "${GREEN}Running disco agent with encrypted secrets enabled...${NC}" | ||
| echo "Command: go run ../.. agent --agent-config-file config.yaml --one-shot --output-path ${OUTPUT_FILE}" | ||
| echo "" | ||
|
|
||
| if go run ../.. agent \ | ||
| --agent-config-file config.yaml \ | ||
| --one-shot \ | ||
| --output-path "${OUTPUT_FILE}"; then | ||
|
|
||
| echo "" | ||
| echo -e "${GREEN}✓ Agent completed successfully${NC}" | ||
|
|
||
| # Check if output file was created | ||
| if [ -f "${OUTPUT_FILE}" ]; then | ||
| echo -e "${GREEN}✓ Output file created: ${OUTPUT_FILE}${NC}" | ||
| else | ||
| echo -e "${RED}✗ Output file was not created${NC}" | ||
| exit 1 | ||
| fi | ||
| else | ||
| echo "" | ||
| echo -e "${RED}✗ Agent failed${NC}" | ||
| exit 1 | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ import ( | |
| "sigs.k8s.io/controller-runtime/pkg/manager" | ||
|
|
||
| "github.com/jetstack/preflight/api" | ||
| "github.com/jetstack/preflight/internal/envelope/rsa" | ||
| "github.com/jetstack/preflight/pkg/client" | ||
| "github.com/jetstack/preflight/pkg/datagatherer" | ||
| "github.com/jetstack/preflight/pkg/datagatherer/k8sdynamic" | ||
|
|
@@ -181,6 +182,25 @@ func Run(cmd *cobra.Command, args []string) (returnErr error) { | |
| if isDynamicGatherer { | ||
| dynDg.ExcludeAnnotKeys = config.ExcludeAnnotationKeysRegex | ||
| dynDg.ExcludeLabelKeys = config.ExcludeLabelKeysRegex | ||
|
|
||
| // Check if secret encryption is enabled via environment variable | ||
| // When enabled, secret data will be kept for encryption instead of being redacted | ||
| encryptSecrets := strings.ToLower(os.Getenv("ARK_SEND_SECRETS")) | ||
|
|
||
| if encryptSecrets == "true" { | ||
| // TODO(@SgtCoDFish): this will fetch a key from JWKS endpoint when that endpoint is available | ||
| key, keyID, err := rsa.LoadHardcodedPublicKey() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Help me understand something. Does this mean that the public keys will only be loaded once at startup? Will there be an option to refresh them while the agent is still running?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That won't be the case when JWKS is actually available - we'll fetch regularly. I was intending to fetch before every upload attempt. I just did a one-off here because there's only one key that never changes! |
||
| if err == nil { | ||
| encryptor, err := rsa.NewEncryptor(keyID, key) | ||
| if err == nil { | ||
| dynDg.Encryptor = encryptor | ||
| } else { | ||
| log.Error(err, "Failed to create encryptor for secret encryption, secrets will not be sent to backend") | ||
| } | ||
| } else { | ||
| log.Error(err, "Failed to load public key for secret encryption, secrets will not be sent to backend") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| log.V(logs.Debug).Info("Starting DataGatherer", "name", dgConfig.Name) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: this defaults to false for now (while the feature is in development) but the planned default for this is "true" (i.e. this will be an opt-out feature, not opt-in)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just my 2c. Although the name of the flag refers to secrets, even now secrets are being sent, but only their metadata. Perhaps we should clarify that this concerns the private data of the secrets.