Security Best Practices
This guide outlines security best practices for implementing and operating Grizzly in production environments.
Key Management
Encryption Key Rotation
Regular key rotation is essential for maintaining strong security posture. Grizzly automates this process while allowing manual control when needed.
Automatic Rotation Strategy
Configure KeyRings to rotate encryption keys based on usage:const ring = await client.ring.create('ProductionData', {
config: {
algos: {
aes256: {
maxEncryptCount: 10000 // Rotate after 10,000 encryptions
}
}
}
})
Manual Rotation Triggers
Rotate keys immediately when:
const ring = await client.ring.get('SensitiveData')
// Rotate on security events
if (securityIncident) {
await ring.rotate()
console.log('Keys rotated due to security incident')
}
// Rotate on personnel changes
if (employeeOffboarding) {
await ring.rotate()
console.log('Keys rotated after access changes')
}
// Rotate on compliance schedule
if (quarterlyRotationDue) {
await ring.rotate()
console.log('Quarterly key rotation completed')
}
When to manually rotate:
- Security incident or suspected compromise
- Employee with key access leaves organization
- Compliance requirements (quarterly, annually)
- Major system updates or migrations
- Before/after third-party data processor engagement
Post-Rotation Verification
Verify rotation was successful:
async function verifyRotation(keyringName, previousKeyId) {
const ring = await client.ring.get(keyringName)
// Verify new key is active
if (ring.activeKey.id === previousKeyId) {
throw new Error('Rotation failed - key unchanged')
}
console.log('Previous key:', previousKeyId)
console.log('New key:', ring.activeKey.id)
// Verify old data still decrypts
const testData = 'Verification test'
const encrypted = await ring.encrypt(testData)
const decrypted = await ring.decrypt(encrypted)
if (decrypted !== testData) {
throw new Error('Post-rotation encryption/decryption failed')
}
console.log('✓ Rotation verified successfully')
}
Customer-Managed Keys (CMK)
Managing your own RSA key pairs provides complete control over encryption keys.
Key Generation Best Practices
Generate keys securely:
# Generate 4096-bit RSA key pair
openssl genrsa -out private.pem 4096
# Extract public key
openssl rsa -in private.pem -pubout -out public.pem
# Verify key strength
openssl rsa -in private.pem -text -noout | grep "Private-Key"
# Should show: Private-Key: (4096 bit)
Security requirements:
- ✅ Use 4096-bit RSA keys
- ✅ Generate keys in secure environment (HSM, offline machine)
- ✅ Never transmit private keys over network
- ✅ Store private keys encrypted at rest
- ❌ Don't use keys smaller than 4096 bits
- ❌ Don't reuse keys across KeyRings
Private Key Storage
Secure storage options (in order of preference):
-
Hardware Security Module (HSM)
-
FIPS 140-2 Level 3 or higher
- Physical tamper protection
-
Key extraction prevention
-
Key Management Service (KMS)
-
AWS KMS, Azure Key Vault, Google Cloud KMS
- Centralized key lifecycle management
-
Access audit trails
-
Encrypted File Storage
-
AES-256 encryption
- Access-controlled storage
- Regular backup to secure location
Example: Encrypted file storage
# Encrypt private key with passphrase
openssl pkcs8 -in private.pem -topk8 -v2 aes256 -out private.encrypted.pem
# Decrypt when needed (requires passphrase)
openssl pkcs8 -in private.encrypted.pem -out private.pem
// Load encrypted key in application
import { readFileSync } from 'fs'
import { execSync } from 'child_process'
function loadPrivateKey(keyPath, passphrase) {
// Decrypt key using passphrase from secure environment variable
const decrypted = execSync(
`openssl pkcs8 -in ${keyPath} -passin pass:${passphrase}`,
{ encoding: 'utf-8' }
)
return decrypted
}
// Load from environment-specific key store
const passphrase = process.env.KEY_PASSPHRASE
const privateKey = loadPrivateKey('./keys/private.encrypted.pem', passphrase)
API Key Management
API Key Lifecycle
Proper API key management is critical for maintaining secure access to the Grizzly platform.
Creation and Scoping
Principle of Least Privilege:
// Don't: Overly permissive (avoid)
{
"accountId": "service-account",
"flags": {
"*.*.*.*": true // Full access to everything
}
}
// Do: Scoped to specific needs
{
"accountId": "service-account",
"flags": {
"keyring.CustomerData.encrypt": true,
"keyring.CustomerData.decrypt": true,
"activity.read": true
}
}
Scoping guidelines:
- Grant only permissions required for specific function
- Use KeyRing-specific flags (
keyring.[name].encrypt) over wildcard - Separate read and write permissions
- Create different API keys for different services/environments
Secure Storage
Never store API keys in:
- Source code
- Version control (git)
- Client-side code
- Unencrypted configuration files
- Application logs
Recommended storage:
// Environment variables
const apiKey = process.env.GRIZZLY_API_KEY
// Secrets management service
import { SecretsManager } from '@aws-sdk/client-secrets-manager'
async function getApiKey() {
const client = new SecretsManager({ region: 'us-west-2' })
const secret = await client.getSecretValue({
SecretId: 'grizzly/api-key'
})
return JSON.parse(secret.SecretString).apiKey
}
// Encrypted configuration
import { readFileSync } from 'fs'
import { decrypt } from './crypto'
const encryptedConfig = readFileSync('./config.enc')
const config = decrypt(encryptedConfig, process.env.CONFIG_KEY)
const apiKey = config.grizzlyApiKey
Rotation Strategy
Rotate API keys regularly and after security events:
async function rotateApiKey(accountId, currentKeyId) {
// Create new API key with same permissions
const currentKey = await getApiKeyDetails(currentKeyId)
const newKey = await fetch('https://tenant.grizzlycbg.io/auth/apikey', {
method: 'POST',
headers: {
'Authorization': `Bearer ${currentKey.key}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
accountId: accountId,
flags: currentKey.flags
})
})
const newKeyData = await newKey.json()
// Update applications to use new key
await updateApplicationConfig(newKeyData.key)
// Monitor for successful switchover
await monitorKeyUsage(newKeyData.keyid, '24h')
// Disable old key after verification
await disableApiKey(currentKeyId)
console.log('API key rotation completed')
}
Rotation schedule:
- Production keys: Every 90 days minimum
- Service accounts: Every 30-60 days
- After security incidents: Immediately
- After personnel changes: Within 24 hours
Revocation and Deactivation
Disable API keys when no longer needed:
// Disable immediately on security incident
async function emergencyKeyRevocation(keyId, reason) {
console.log(`SECURITY: Revoking key ${keyId} - ${reason}`)
// Disable key
await disableApiKey(keyId)
// Audit recent activity
const recentActivity = await queryActivity({
keyId: keyId,
start: Date.now() - (24 * 60 * 60 * 1000), // Last 24h
end: Date.now()
})
// Alert security team
await sendSecurityAlert({
event: 'api_key_revoked',
keyId: keyId,
reason: reason,
recentActivity: recentActivity
})
console.log('Key revoked and security team notified')
}
// Scheduled cleanup of unused keys
async function cleanupUnusedKeys() {
const allKeys = await listApiKeys()
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000)
for (const key of allKeys) {
const lastUsed = await getKeyLastUsed(key.keyid)
if (lastUsed < thirtyDaysAgo) {
console.log(`Disabling unused key: ${key.keyid}`)
await disableApiKey(key.keyid)
}
}
}
Access Control
Role-Based Access Control (RBAC)
Implement least-privilege access using Accounts and API Keys.
Account Structure
Organize Accounts by role and responsibility:
// Example: Multi-tier access structure
// 1. Service Accounts (automated systems)
await createAccount({
uid: 'service-data-processor',
notes: 'Automated data processing service'
})
// 2. Team Accounts (shared team access)
await createAccount({
uid: 'team-finance',
notes: 'Finance team shared access'
})
// 3. Individual Accounts (named users)
await createAccount({
uid: 'user-alice-admin',
notes: 'Alice Smith - Platform Administrator'
})
// 4. External Accounts (third-party processors)
await createAccount({
uid: 'external-vendor-acme',
notes: 'ACME Corp - Data Processing Vendor'
})
Permission Matrices
Define clear permission tiers:
// Permission templates for common roles
const ROLE_TEMPLATES = {
// Read-only auditor
auditor: {
'activity.read': true,
'keyring.*.read': true,
'account.read': true
},
// Application service (encrypt/decrypt only)
application: {
'keyring.AppData.encrypt': true,
'keyring.AppData.decrypt': true
},
// Data administrator (full KeyRing management)
dataAdmin: {
'keyring.create': true,
'keyring.*.read': true,
'keyring.*.encrypt': true,
'keyring.*.decrypt': true,
'keyring.*.rotate': true,
'keyring.*.config.read': true,
'keyring.*.config.write': true
},
// Platform administrator (full access)
platformAdmin: {
'*.*.*.*': true
}
}
// Apply template when creating API key
async function createApiKeyFromTemplate(accountId, roleName) {
const flags = ROLE_TEMPLATES[roleName]
if (!flags) {
throw new Error(`Unknown role: ${roleName}`)
}
return await createApiKey({ accountId, flags })
}
Data Protection
Encryption in Transit
All communication with Grizzly must use TLS 1.2 or higher.
SDK Configuration
// Always use HTTPS
const client = await KeyClient.create({
host: 'https://your-tenant.grizzlycbg.io',
apikey: process.env.GRIZZLY_API_KEY
})
// Never use HTTP in production
// host: 'http://your-tenant.grizzlycbg.io' // INSECURE
Certificate Validation
Ensure TLS certificates are validated:
// Node.js default validates certificates
// Never disable in production:
// NEVER DO THIS
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
// For self-signed certs in development, use proper CA
import https from 'https'
import fs from 'fs'
const agent = new https.Agent({
ca: fs.readFileSync('./ca-cert.pem')
})
// Use custom agent for requests
Data at Rest
Protect encrypted data storage.
Storage Security
Encrypted data protection:
- Store encrypted data in access-controlled systems
- Apply encryption at storage layer (database encryption, disk encryption)
- Implement backup encryption
- Use separate credentials for storage access
// Example: Secure database storage
async function storeEncryptedData(userId, data) {
const ring = await client.ring.get('UserData')
// Encrypt with Asset metadata
const encrypted = await ring.encrypt(data, {
asset: {
id: `user-${userId}`,
type: 'user-record',
classification: 'confidential',
encryptedAt: new Date().toISOString()
}
})
// Store in encrypted database column
await db.query(
'INSERT INTO encrypted_records (user_id, encrypted_data, created_at) VALUES ($1, $2, $3)',
[userId, encrypted, new Date()]
)
}
Compliance and Audit
Activity Monitoring
Track all encryption operations for compliance and security monitoring.
Audit Trail Analysis
// Query activity for security review
async function securityAudit(startDate, endDate) {
const activities = await queryActivity({
start: startDate.getTime(),
end: endDate.getTime(),
limit: 10000
})
const report = {
totalOperations: activities.length,
byAction: {},
byKeyRing: {},
byAccount: {},
failedOperations: [],
suspiciousActivity: []
}
for (const activity of activities) {
// Count by action
report.byAction[activity.action] =
(report.byAction[activity.action] || 0) + 1
// Count by KeyRing
report.byKeyRing[activity.ring] =
(report.byKeyRing[activity.ring] || 0) + 1
// Track failures
if (!activity.success) {
report.failedOperations.push(activity)
}
// Detect suspicious patterns
if (activity.action === 'decrypt' && isAfterHours(activity.createdAt)) {
report.suspiciousActivity.push(activity)
}
}
return report
}
// Automated monitoring
async function setupSecurityAlerts() {
setInterval(async () => {
const last15Min = await queryActivity({
start: Date.now() - (15 * 60 * 1000),
end: Date.now()
})
const failureRate = last15Min.filter(a => !a.success).length / last15Min.length
if (failureRate > 0.10) { // More than 10% failures
await sendSecurityAlert({
type: 'high_failure_rate',
rate: failureRate,
period: '15min'
})
}
}, 15 * 60 * 1000) // Every 15 minutes
}
Compliance Reporting
Generate compliance reports for regulatory requirements:
async function generateComplianceReport(quarter) {
const { startDate, endDate } = getQuarterDates(quarter)
const report = {
period: quarter,
keyRotations: [],
accessChanges: [],
encryptionVolume: {},
auditFindings: []
}
// Key rotation compliance
const allKeyRings = await client.ring.getAll()
for (const name of allKeyRings) {
const ring = await client.ring.get(name)
const rotationHistory = await getKeyRotationHistory(ring.id, startDate, endDate)
report.keyRotations.push({
keyring: name,
rotations: rotationHistory.length,
compliant: rotationHistory.length >= 1 // Quarterly requirement
})
}
// Encryption volume by KeyRing
const activities = await queryActivity({
start: startDate.getTime(),
end: endDate.getTime(),
action: 'encrypt'
})
for (const activity of activities) {
report.encryptionVolume[activity.ring] =
(report.encryptionVolume[activity.ring] || 0) + 1
}
return report
}
Regulatory Compliance
HIPAA Compliance
HIPAA Requirements
For Protected Health Information (PHI):
// Tag PHI data for compliance tracking
async function encryptPHI(patientId, phi) {
const ring = await client.ring.get('Healthcare')
const encrypted = await ring.encrypt(phi, {
asset: {
id: `patient-${patientId}`,
type: 'phi',
classification: 'hipaa-protected',
hipaa: {
purpose: 'treatment',
authorizedBy: 'provider-123',
retentionYears: 7
},
encryptedAt: new Date().toISOString()
}
})
return encrypted
}
// Audit trail for HIPAA compliance
async function hipaаAuditLog(patientId, dateRange) {
const activities = await queryActivity({
asset: `patient-${patientId}`,
start: dateRange.start,
end: dateRange.end
})
// HIPAA requires: Who, What, When, Where
return activities.map(a => ({
who: a.accountId,
what: a.action,
when: new Date(a.createdAt),
where: a.metadata?.ipAddress || 'unknown',
phi: `patient-${patientId}`
}))
}
Key HIPAA controls:
- Encrypt all PHI at rest and in transit
- Maintain audit trails for minimum 6 years
- Implement role-based access controls
- Regular key rotation (quarterly minimum)
- Breach notification procedures
GDPR Compliance
GDPR Requirements
For Personal Data:
// Right to erasure (right to be forgotten)
async function deleteUserData(userId) {
// 1. Find all encrypted data for user
const activities = await queryActivity({
asset: `user-${userId}`
})
// 2. Delete encrypted data from storage
await db.query('DELETE FROM encrypted_records WHERE user_id = $1', [userId])
// 3. Rotate KeyRing if data isolation required
const ring = await client.ring.get('UserData')
await ring.rotate()
// 4. Log deletion for compliance
await logComplianceEvent({
type: 'gdpr_erasure',
userId: userId,
timestamp: new Date(),
recordCount: activities.length
})
console.log(`User ${userId} data erased per GDPR request`)
}
// Data portability
async function exportUserData(userId) {
const activities = await queryActivity({
asset: `user-${userId}`
})
// Decrypt and export user's data
const exportData = {
userId: userId,
exportDate: new Date().toISOString(),
records: []
}
for (const activity of activities) {
if (activity.action === 'encrypt' && activity.encryptedData) {
const decrypted = await client.decrypt(activity.encryptedData)
exportData.records.push({
date: activity.createdAt,
data: decrypted
})
}
}
return exportData
}
Key GDPR controls:
- Data minimization (only encrypt necessary data)
- Purpose limitation (Asset metadata tracks purpose)
- Right to access (query Activity feed by Asset)
- Right to erasure (delete encrypted data + rotate keys)
- Data portability (decrypt and export)
SOC 2 Compliance
SOC 2 Trust Principles
Security, Availability, Confidentiality:
// Implement security controls
const securityControls = {
// Access control
async enforceAccessControl() {
// Principle of least privilege
await reviewApiKeyPermissions()
await disableUnusedKeys()
await enforceStrongAuthentication()
},
// Change management
async trackConfigChanges() {
// Log all KeyRing configuration changes
const ring = await client.ring.get('Production')
ring.on('config:updated', (change) => {
logAuditEvent({
type: 'config_change',
keyring: ring.name,
changes: change,
timestamp: new Date()
})
})
},
// Monitoring and alerting
async setupMonitoring() {
// Real-time security monitoring
await monitorUnauthorizedAccess()
await monitorEncryptionFailures()
await monitorKeyRotation()
},
// Incident response
async incidentResponse(incident) {
// 1. Detect and log
await logSecurityIncident(incident)
// 2. Contain
await revokeCompromisedKeys()
await rotateAffectedKeyRings()
// 3. Investigate
const forensics = await queryActivity({
start: incident.detectedAt - (7 * 24 * 60 * 60 * 1000),
end: incident.detectedAt
})
// 4. Remediate
await implementSecurityFixes()
// 5. Report
await generateIncidentReport(incident, forensics)
}
}
Additional Resources
- KeyRings - KeyRing management and configuration
- API Keys - API key flags and permissions
- Asset Tagging - Metadata and compliance tracking
- Troubleshooting - Security error resolution
- Activity Feed - Audit log queries