credentialsymlenc
Credentials.yml.enc in Chef Infra: The Definitive Guide to Encrypted Data Bags and Secure Configuration Management
Key Takeaways
- credentials.yml.enc is a Chef Infra encrypted data bag item file that stores sensitive values such as passwords, API keys, and tokens using AES-256 encryption.
- It is paired with a validation key (credentials.key) or a dedicated encryption key stored securely—never commit keys to version control.
- Encrypted data bags outperform plain-text secrets in attributes files because they support server-side decryption at compile time and work with knife and Chef Infra Client seamlessly.
- Common pitfalls include key rotation failures, missing
encryptedformat declaration, and attempting to decrypt without the correct key on the node. - For large-scale environments, consider supplementing encrypted data bags with external secret managers (HashiCorp Vault, AWS Secrets Manager) via custom resources or InSpec-driven delivery.
What Is credentials.yml.enc?
Definition and Role in the Chef Ecosystem
In Chef Infra, credentials.yml.enc is the physical representation of an encrypted data bag item. A data bag is a global JSON variable stored on the Chef server; when a bag item is marked as encrypted, Chef encrypts its fields with AES-256 before persisting them. The resulting .enc file contains the ciphertext and an initialization vector—but never the plaintext secret.
The companion file, typically named credentials.key, holds the symmetric encryption key. Chef Infra Client uses this key at convergence time to decrypt the bag item and make the secrets available to recipes as a Ruby hash. Without the key present on the node or the ability to fetch it from a secure location, any recipe that reads the bag will raise a Chef::Exceptions::EncryptedDataBagItemNotFound or decryption error.
File Structure Breakdown
A typical credentials.yml.enc (or more accurately, a JSON-encoded .enc blob) stores data in this shape:
{
"id": "database",
"encrypted_data": "BASE64_AES256_CIPHERTEXT",
"iv": "BASE64_INITIALIZATION_VECTOR",
"encrypted": true,
"version": "1.2.0"
}
Note that although the filename may carry a .yml extension in some tooling wrappers, the Chef server stores and transmits encrypted data bags as JSON. The .enc suffix indicates that the payload has been processed through the encryption routine, not that the format is YAML.
How Encryption Works Under the Hood
AES-256-CBC with PKCS7 Padding
Chef Infra uses the openssl library in Ruby to perform AES-256-CBC encryption. The process is:
- A 256-bit symmetric key is derived from the contents of
credentials.key. - A random 128-bit initialization vector (IV) is generated per encryption operation.
- The plaintext JSON data bag item is padded with PKCS7 to align to the 16-byte AES block size.
- The cipher text, IV, and metadata are serialized into a single JSON blob and base64-encoded for transport.
Compile-Time vs. Converge-Time Decryption
One of the most powerful aspects of encrypted data bags is that decryption happens on the node during the converge phase, not on the Chef server. This means:
- The Chef server never sees plaintext secrets during a normal run.
- Keys must be distributed to nodes through a secure channel (Puppet-style key distribution, environment variables, or a secret management integration).
- Lazy evaluation (
lazy { node['credentials']['password'] }) is often required in attribute files to defer decryption until converge time.
Credentials.yml.enc vs. Alternative Secret Management Approaches
Understanding where encrypted data bags fit in the broader IaC and configuration management landscape helps teams make informed architectural decisions. The table below compares the most common approaches.
| Approach | Encryption Standard | Key Rotation Support | Server-Side Storage | External Dependency | Best For |
|---|---|---|---|---|---|
| credentials.yml.enc (Chef) | AES-256-CBC | Manual re-encryption required | Chef Server | None (built-in) | Mid-size Chef environments; teams already invested in the Chef ecosystem |
| Chef Vault | AES-256-GCM | Built-in vault item migration | Chef Server + local vault files | chef-vault gem | Environments requiring per-node or per-group secret scoping |
| HashiCorp Vault | AES-256-GCM (transit), KMS-backed | Native lease-based rotation | Vault cluster (external) | Vault Agent sidecar or API calls | Multi-cloud, multi-tool platforms needing a centralized secrets plane |
| AWS Secrets Manager | AES-256 (KMS-managed) | Automatic or scheduled rotation | AWS managed service | AWS SDK / IAM roles | AWS-native deployments with existing KMS infrastructure |
| Environment Variables | None (OS-level protection only) | Restart or reload required | Node process space | None | Development environments; trivial test fixtures |
| Ansible Vault | AES-256 | Re-encrypt manually or via script | Control node / Git (encrypted blobs) | ansible-vault CLI | Ansible-centric shops managing encrypted playbooks or inventories |
Step-by-Step: Creating and Using credentials.yml.enc
Step 1 — Generate or Obtain the Encryption Key
# Generate a 256-bit random key and base64-encode it
openssl rand -base64 32 > credentials.key
# Secure the key file
chmod 600 credentials.key
Step 2 — Create the Encrypted Data Bag Item
knife data bag create database_bags database \
--secret-file credentials.key \
--format json
Alternatively, use the interactive mode:
knife data bag create database_bags database --secret-file credentials.key
Knife will prompt you for each field and write the encrypted JSON to the Chef server.
Step 3 — Reference the Bag in a Recipe
db_creds = data_bag_item('database_bags', 'database')
template '/etc/app/database.yml' do
source 'database.yml.erb'
variables(
username: db_creds['username'],
password: db_creds['password']
)
sensitive true
end
Step 4 — Distribute the Key to Nodes
Nodes must have access to the same credentials.key to decrypt bag