Output Format

Each discovered asset is returned as a JSON object with the following structure:

{
  "result_type": "scan",
  "plugin_id": "filesystem-scanner-plugin",
  "plugin_version": "1.1.0",
  "type": "cert",
  "timestamp": "2026-03-05T14:30:45+05:30",
  "urn": "urn:cert:sha256:abcd1234efgh5678",
  "url": "ssh://admin@10.1.127.33:22/etc/ssl/certs/server.crt",
  "extra": {
    "file_path": "/etc/ssl/certs/server.crt",
    "keystore_type": "certificate",
    "file_size_bytes": 2048,
    "owner": "root",
    "permissions": "644",
    "created_date": "2025-12-15T10:30:00Z",
    "modified_date": "2025-12-15T10:30:00Z",
    "accessed_date": "2026-03-05T10:15:30Z",
    "is_encrypted": false,
    "cryptographic_algorithm": "RSA",
    "cryptographic_length": "2048"
  },
  "cert_pem": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----"
}

Field Descriptions

Field

Type

Description

result_type

string

Always "scan" for discovery results

plugin_id

string

Always "filesystem-scanner-plugin"

plugin_version

string

Plugin version (e.g., "1.1.0")

type

string

Asset type: "cert", "privkey", "pubkey", or "secret"

timestamp

string

ISO8601 discovery timestamp with timezone

urn

string

Unique identifier (URN) for result deduplication

url

string

SSH locator showing discovery path

extra

object

Asset metadata (see table below)

cert_pem

string

Full PEM content (only for cert type)

pubkey_pem

string

Full PEM content (only for pubkey type)

Metadata Fields (in extra object)

Field

Type

Description

file_path

string

Full filesystem path on remote host

keystore_type

string

Detected type (certificate, java_keystore, pkcs12, etc.)

file_size_bytes

number

File size in bytes

owner

string

File owner username

permissions

string

File permissions (e.g., "644", "600")

created_date

string

File creation timestamp (ISO8601)

modified_date

string

File last modified timestamp (ISO8601)

accessed_date

string

File last accessed timestamp (ISO8601)

is_encrypted

boolean

true if file appears encrypted, false otherwise

cryptographic_algorithm

string

Algorithm if detected (RSA, EC-P256, EdDSA, etc.)

cryptographic_length

string

Key/cert length if detected (2048, 4096, 256, etc.)

Security Notes

  • Private Key Material: Never included in output - only metadata returned for privkey type
  • Secret Content: Never included in output - only metadata returned for keystores/secret type
  • Public Key/Certificate PEM: Always included - these are non-sensitive public data

Security Considerations

Credential Management

  • Password & Private Key Fields: Marked writeOnly in configuration - never logged or exposed in error messages
  • Credentials in Memory: Cleared after SSH authentication completes
  • Best Practice: Use SSH key authentication instead of passwords for better security

SSH Access Control

  • Plugin requires valid SSH credentials with filesystem read permissions
  • Some directories (/root/.ssh, /etc/ssl/private) may need elevated privileges
  • Configure SSH user with minimal necessary permissions
  • Consider using passwordless sudo if root access is needed for certain paths

Output Security

  • Private Keys: Never included in output - only metadata (algorithm, length, owner)
  • Keystores/Secrets: Never included in output - only metadata (size, owner, encryption status)
  • Certificates & Public Keys: Fully included (these are non-sensitive public data)

Scan Configuration

  • maxDepth Limit: Controls recursion depth to prevent excessive scanning and symlink loops
  • Recommended Depth: 6-10 for typical systems, 3-5 for performance-critical environments
  • Custom Paths: Override default paths to focus on specific locations and improve performance

Troubleshooting

SSH Connection Errors

Error: "failed to resolve hostname" - Cause: Target host not reachable or hostname not resolvable - Solution: Verify hostname/IP address and network connectivity bash nslookup hostname ping hostname ssh -v username@hostname

Error: "SSH authentication failed" - Cause: Incorrect username, password, or private key - Solution: Verify credentials and SSH access bash ssh -i your-key.pem username@hostname # For key auth ssh username@hostname # For password auth

Error: "SSH key format error" - Cause: Private key in unsupported format (OpenSSH, PKCS#8, etc.) - Solution: Convert to PEM format ```bash # Convert OpenSSH format to PEM ssh-keygen -p -N "" -m pem -f ~/.ssh/id_rsa

# Convert PKCS#8 to PEM openssl pkcs8 -in key.p8 -out key.pem -nocrypt ```

Scan Issues

No Assets Found - Cause 1: Specified directories don't contain cryptographic assets - Solution: Verify paths contain files, adjust scanPaths - Cause 2: Permission denied reading files - Solution: Check file permissions, ensure SSH user has read access bash ssh username@hostname ls -la /etc/ssl

Scan Timeout or Slow Performance - Cause 1: maxDepth too high scanning many directory levels - Solution: Reduce maxDepth to 6-8 - Cause 2: scanPaths includes large directories with many files - Solution: Focus paths on specific certificate locations - Cause 3: Network latency or slow SSH connection - Solution: Use fast network, check SSH server performance

Configuration Errors

Error: "invalid port: XXXXX" - Solution: Port must be 1-65535

Error: "invalid maxDepth: XXXXX" - Solution: maxDepth must be 1-20

Error: "either password or privateKey must be provided for SSH authentication" - Solution: Provide either password OR privateKey field for authentication

Error: "only one authentication method allowed: provide either password or privateKey, not both" - Solution: Remove either password or privateKey - you cannot provide both simultaneously

Building & Testing

Prerequisites

  • Go 1.21 or later
  • make command-line tool
  • Docker (optional, for containerized deployment)

Build Plugin

cd go-modules/filesystem-scanner-plugin
make build

Creates executable: filesystem-scanner-plugin

Run Tests

# Run unit tests
make test

# Run full test suite including integration tests
make integration-test

# Run linter
make lint

Build Docker Image

make docker

Creates Docker image: cspd/discovery/plugins/filesystem-scanner-plugin:1.1.0

Clean Build Artifacts

make clean

Implementation Details

Architecture

The plugin is implemented as a single file:

  • filesystem-scanner.go (1,442 lines): CLI interface, SSH client management, filesystem scanning, asset discovery, metadata extraction, configuration parsing and validation

Note: This follows the repository standard where 19 out of 20 plugins use a single hyphenated filename (e.g., aws-certmanager.go, azure-keyvault.go).

How It Works

  1. Configuration Parsing: Reads and validates JSON configuration from stdin
  2. SSH Connection: Connects to remote host using password or key authentication
  3. Auto-Discovery (if no scanPaths provided): Tests common certificate locations for accessibility
  4. Path Preparation: Deduplicates and normalizes scan paths to prevent redundant scanning
  5. Recursive Scanning: Traverses directories up to configured depth, discovering assets
  6. Certificate Chain Detection: Files with multiple certificates are split into separate results
  7. Asset Classification: Identifies file types (certificates, keys, keystores) by pattern matching
  8. PEM Validation: Validates certificate and public key PEM format before including in results
  9. Metadata Extraction: Reads file metadata (owner, permissions, timestamps) via SSH
  10. Content Processing: For PEM files, extracts algorithm/key length from certificate or public key
  11. Result Generation: Creates standardized result objects with URN and metadata
  12. Streaming Output: Returns results in JSONL format (one result per line)

Key Design Features

Retry Logic - All SSH operations retry up to 3 times with 500ms delays - Transient network failures automatically recover - Persistent failures (auth, permissions) fail immediately

Path Deduplication - Overlapping or duplicate scan paths are automatically consolidated - Parent path /etc/ssl eliminates child paths like /etc/ssl/certs - Improves performance and prevents error proliferation

Error Context - All errors include specific details (hostname, file path, operation) - Operators can diagnose issues without enabling debug logging

Graceful Degradation - File content read failures don't prevent metadata extraction - Assets reported with metadata URN even if content unreadable

PEM Validation - Certificates and public keys validated during discovery - Invalid/non-PEM cert files skipped to prevent KCM rejection (fixes 550 vs 549 issue) - OpenSSH public keys automatically converted to PEM format

File Pattern Recognition

The plugin recognizes these patterns:

Java Keystores:    *.jks, *.keystore
PKCS#12:           *.p12, *.pfx
PEM Files:         *.pem
Certificates:      *.crt, *.cer
Private Keys:      *.key
Public Keys:       *.pub, authorized_keys
OpenSSH Keys:      id_rsa, id_ecdsa, id_ed25519
GnuPG:             *.gpg
NSS Databases:     cert*.db, key*.db

URN Generation

Each discovery result gets a unique identifier (URN):

For Certificates:

Format: urn:cert:sha256:<fingerprint>
Example: urn:cert:sha256:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6

Fingerprint: SHA256 hash of DER-encoded certificate
Purpose: Content-based deduplication (same cert = same URN)

For Public Keys:

Format: urn:pubkey:sha256:<fingerprint>
Example: urn:pubkey:sha256:c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8

Fingerprint: SHA256 hash of DER-encoded SubjectPublicKeyInfo (for valid PEM public keys)
Fallback: urn:pubkey:name:<filename>:<version> (if PEM parsing fails)
Purpose: Content-based deduplication for valid public keys

For Private Keys:

Format: urn:privkey:name:<filename>:<version>
Example: urn:privkey:name:id_rsa:a1b2c3d4e5f6g7h8

Filename: Base filename of the private key file
Version: First 16 hex characters of SHA256 hash of full file path
Purpose: Path-based identification (no key material in URN for security)

For Secrets/Keystores:

Format: urn:secret:name:<filename>:<version>
Example: urn:secret:name:keystore.jks:b2c3d4e5f6g7h8i9

Filename: Base filename of the keystore/secret file  
Version: First 16 hex characters of SHA256 hash of full file path
Purpose: Path-based identification (no secret content in URN for security)

Performance Characteristics

Typical scan times on 10GB filesystem:

  • Focused Scan (e.g., /etc/ssl,/etc/pki with depth 4-6): 5-15 seconds
  • Standard Scan (e.g., /etc, /opt, /var/lib with depth 8-10): 30-60 seconds
  • Deep Scan (multiple paths with depth 15-20): 60-120 seconds

Performance depends on: - Number of files in scanned directories - Network latency to SSH server - SSH server I/O performance - Configured depth limit

Limitations

  • No Incremental Scans: Full scan each time - filesystem doesn't provide modification timestamps for filtering
  • SSH-Only: Cannot scan local filesystem, requires SSH access
  • Keystore Decryption: Basic encryption detection only - doesn't attempt password-protected keystore decryption
  • Symlink Handling: Follows symlinks one level only to prevent infinite loops