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 |
|---|---|---|
| string | Always "scan" for discovery results |
| string | Always "filesystem-scanner-plugin" |
| string | Plugin version (e.g., "1.1.0") |
| string | Asset type: "cert", "privkey", "pubkey", or "secret" |
| string | ISO8601 discovery timestamp with timezone |
| string | Unique identifier (URN) for result deduplication |
| string | SSH locator showing discovery path |
| object | Asset metadata (see table below) |
| string | Full PEM content (only for |
| string | Full PEM content (only for |
Metadata Fields (in extra object)
Field | Type | Description |
|---|---|---|
| string | Full filesystem path on remote host |
| string | Detected type (certificate, java_keystore, pkcs12, etc.) |
| number | File size in bytes |
| string | File owner username |
| string | File permissions (e.g., "644", "600") |
| string | File creation timestamp (ISO8601) |
| string | File last modified timestamp (ISO8601) |
| string | File last accessed timestamp (ISO8601) |
| boolean | true if file appears encrypted, false otherwise |
| string | Algorithm if detected (RSA, EC-P256, EdDSA, etc.) |
| string | Key/cert length if detected (2048, 4096, 256, etc.) |
Security Notes
- Private Key Material: Never included in output - only metadata returned for
privkeytype - Secret Content: Never included in output - only metadata returned for keystores/
secrettype - Public Key/Certificate PEM: Always included - these are non-sensitive public data
Security Considerations
Credential Management
- Password & Private Key Fields: Marked
writeOnlyin 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
sudoif 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
makecommand-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
- Configuration Parsing: Reads and validates JSON configuration from stdin
- SSH Connection: Connects to remote host using password or key authentication
- Auto-Discovery (if no scanPaths provided): Tests common certificate locations for accessibility
- Path Preparation: Deduplicates and normalizes scan paths to prevent redundant scanning
- Recursive Scanning: Traverses directories up to configured depth, discovering assets
- Certificate Chain Detection: Files with multiple certificates are split into separate results
- Asset Classification: Identifies file types (certificates, keys, keystores) by pattern matching
- PEM Validation: Validates certificate and public key PEM format before including in results
- Metadata Extraction: Reads file metadata (owner, permissions, timestamps) via SSH
- Content Processing: For PEM files, extracts algorithm/key length from certificate or public key
- Result Generation: Creates standardized result objects with URN and metadata
- 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/pkiwith depth 4-6): 5-15 seconds - Standard Scan (e.g.,
/etc,/opt,/var/libwith 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