-
-
Notifications
You must be signed in to change notification settings - Fork 559
fix(role): Add automatic SCRAM-SHA-256 password escaping and docs #797
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
Open
Copilot
wants to merge
8
commits into
main
Choose a base branch
from
copilot/fix-703
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a7a620f
Initial plan
Copilot b7551a6
Add automatic SCRAM-SHA-256 password escaping and comprehensive docum…
Copilot e86ab3b
Potential fix for code scanning alert no. 5: Incomplete string escapi…
ramereth 3d40e50
Potential fix for code scanning alert no. 6: Incomplete string escapi…
bmhughes f588465
Use nil_or_empty? function for password check in escape_password_for_sql
Copilot f743322
Fix backslash escaping in SCRAM-SHA-256 password handling
Copilot 623bf6e
Fix markdownlint issues in SCRAM-SHA-256 documentation
Copilot 2380d89
Merge branch 'main' into copilot/fix-703
damacus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
# SCRAM-SHA-256 Authentication | ||
|
||
SCRAM-SHA-256 (Salted Challenge Response Authentication Mechanism) is a password authentication method in PostgreSQL that provides better security than traditional MD5 authentication. | ||
|
||
## Overview | ||
|
||
SCRAM-SHA-256 authentication offers several advantages: | ||
- **Stronger security**: Uses SHA-256 instead of MD5 | ||
- **Salt protection**: Prevents rainbow table attacks | ||
- **Iteration count**: Makes brute force attacks more difficult | ||
- **Mutual authentication**: Both client and server verify each other | ||
|
||
## Password Format | ||
|
||
SCRAM-SHA-256 passwords have this specific format: | ||
``` | ||
damacus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
SCRAM-SHA-256$<iteration_count>:<salt>$<StoredKey>:<ServerKey> | ||
``` | ||
|
||
Example: | ||
``` | ||
damacus marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
SCRAM-SHA-256$4096:27klCUc487uwvJVGKI5YNA==$6K2Y+S3YBlpfRNrLROoO2ulWmnrQoRlGI1GqpNRq0T0=:y4esBVjK/hMtxDB5aWN4ynS1SnQcT1TFTqV0J/snls4= | ||
``` | ||
|
||
## Usage with Chef | ||
|
||
### Creating Users with SCRAM-SHA-256 Passwords | ||
|
||
When you have a pre-computed SCRAM-SHA-256 password hash: | ||
|
||
```ruby | ||
postgresql_role 'secure_user' do | ||
encrypted_password 'SCRAM-SHA-256$4096:27klCUc487uwvJVGKI5YNA==$6K2Y+S3YBlpfRNrLROoO2ulWmnrQoRlGI1GqpNRq0T0=:y4esBVjK/hMtxDB5aWN4ynS1SnQcT1TFTqV0J/snls4=' | ||
login true | ||
action :create | ||
end | ||
``` | ||
|
||
### Automatic Character Escaping | ||
|
||
The cookbook automatically handles escaping of special characters (`$`) in SCRAM-SHA-256 passwords. You don't need to manually escape these characters - the cookbook will handle this transparently. | ||
|
||
**Before (manual escaping required):** | ||
```ruby | ||
bmhughes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
postgresql_role 'user1' do | ||
# Manual escaping was required | ||
password 'SCRAM-SHA-256$4096:salt$key:server'.gsub('$', '\$') | ||
action [:create, :update] | ||
end | ||
``` | ||
|
||
**Now (automatic escaping):** | ||
```ruby | ||
bmhughes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
postgresql_role 'user1' do | ||
# No manual escaping needed | ||
encrypted_password 'SCRAM-SHA-256$4096:salt$key:server' | ||
action [:create, :update] | ||
end | ||
``` | ||
|
||
## Configuring Authentication | ||
|
||
To use SCRAM-SHA-256 authentication, configure the access method: | ||
|
||
```ruby | ||
postgresql_access 'scram authentication' do | ||
type 'host' | ||
database 'all' | ||
user 'myuser' | ||
address '127.0.0.1/32' | ||
auth_method 'scram-sha-256' | ||
end | ||
``` | ||
|
||
## Password Generation | ||
|
||
### Using PostgreSQL | ||
|
||
Generate a SCRAM-SHA-256 password directly in PostgreSQL: | ||
|
||
```sql | ||
-- Set password for existing user (PostgreSQL will hash it) | ||
ALTER ROLE myuser PASSWORD 'plaintext_password'; | ||
|
||
-- Check the generated hash | ||
SELECT rolpassword FROM pg_authid WHERE rolname = 'myuser'; | ||
``` | ||
|
||
### Using Ruby | ||
|
||
Generate a SCRAM-SHA-256 hash using the `scram-sha-256` gem: | ||
|
||
```ruby | ||
require 'scram-sha-256' | ||
|
||
# Generate hash with default iteration count (4096) | ||
password_hash = ScramSha256.hash_password('my_plain_password') | ||
|
||
# Generate hash with custom iteration count | ||
password_hash = ScramSha256.hash_password('my_plain_password', 8192) | ||
``` | ||
|
||
### Using Python | ||
|
||
Generate a SCRAM-SHA-256 hash using Python: | ||
|
||
```python | ||
import hashlib | ||
import hmac | ||
import base64 | ||
import secrets | ||
|
||
def generate_scram_sha256(password, salt=None, iterations=4096): | ||
if salt is None: | ||
salt = secrets.token_bytes(16) | ||
|
||
# Implementation details would go here | ||
# This is a simplified example | ||
pass | ||
``` | ||
|
||
## Common Use Cases | ||
|
||
### Control Panel Integration | ||
|
||
When integrating with control panels that pre-hash passwords: | ||
|
||
```ruby | ||
# Control panel provides pre-hashed password | ||
hashed_password = control_panel.get_user_password_hash(username) | ||
|
||
postgresql_role username do | ||
encrypted_password hashed_password | ||
login true | ||
createdb user_permissions.include?('createdb') | ||
action [:create, :update] | ||
end | ||
``` | ||
|
||
### Migration from MD5 | ||
|
||
When migrating from MD5 to SCRAM-SHA-256: | ||
|
||
```ruby | ||
# First, configure SCRAM-SHA-256 authentication | ||
postgresql_access 'upgrade to scram' do | ||
type 'host' | ||
database 'all' | ||
user 'all' | ||
address '127.0.0.1/32' | ||
auth_method 'scram-sha-256' | ||
end | ||
|
||
# Users will need to reset their passwords | ||
# The new passwords will automatically use SCRAM-SHA-256 | ||
``` | ||
|
||
## Troubleshooting | ||
|
||
### Common Issues | ||
|
||
1. **Password mangling**: If you see passwords with missing `$` characters, ensure you're using this cookbook version that includes automatic escaping. | ||
|
||
2. **Authentication failures**: Verify that: | ||
- The `pg_hba.conf` is configured for `scram-sha-256` | ||
- The password hash format is correct | ||
- The user has login privileges | ||
|
||
3. **Iteration count**: Higher iteration counts (e.g., 8192 or 16384) provide better security but require more CPU time. | ||
|
||
### Debugging | ||
|
||
Check the PostgreSQL logs for authentication details: | ||
|
||
```bash | ||
tail -f /var/log/postgresql/postgresql-*.log | ||
``` | ||
|
||
Verify user configuration: | ||
|
||
```sql | ||
SELECT rolname, rolcanlogin, rolpassword | ||
FROM pg_authid | ||
WHERE rolname = 'your_username'; | ||
``` | ||
|
||
## Security Recommendations | ||
|
||
1. **Use high iteration counts**: 4096 is the minimum; consider 8192 or higher for sensitive applications. | ||
2. **Enforce SCRAM-SHA-256**: Disable MD5 authentication entirely when possible. | ||
3. **Regular password rotation**: Implement password rotation policies. | ||
4. **Monitor authentication**: Log and monitor authentication attempts. | ||
|
||
## References | ||
|
||
- [PostgreSQL SCRAM-SHA-256 Documentation](https://www.postgresql.org/docs/current/auth-password.html) | ||
- [RFC 7677: SCRAM-SHA-256 and SCRAM-SHA-256-PLUS](https://tools.ietf.org/html/rfc7677) | ||
- [PostgreSQL Security Best Practices](https://www.postgresql.org/docs/current/auth-methods.html) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
require 'spec_helper' | ||
require_relative '../../libraries/sql/role' | ||
|
||
# Mock the dependencies for testing | ||
module PostgreSQL | ||
module Cookbook | ||
module Utils | ||
def nil_or_empty?(value) | ||
value.nil? || value.empty? | ||
end | ||
end | ||
|
||
module SqlHelpers | ||
module Connection | ||
end | ||
end | ||
end | ||
end | ||
|
||
class Utils | ||
end | ||
|
||
describe 'PostgreSQL::Cookbook::SqlHelpers::Role' do | ||
let(:test_class) do | ||
Class.new do | ||
include PostgreSQL::Cookbook::SqlHelpers::Role | ||
include PostgreSQL::Cookbook::Utils | ||
end | ||
end | ||
|
||
let(:instance) { test_class.new } | ||
|
||
describe '#escape_password_for_sql' do | ||
context 'with SCRAM-SHA-256 passwords' do | ||
let(:scram_password) { 'SCRAM-SHA-256$4096:27klCUc487uwvJVGKI5YNA==$6K2Y+S3YBlpfRNrLROoO2ulWmnrQoRlGI1GqpNRq0T0=:y4esBVjK/hMtxDB5aWN4ynS1SnQcT1TFTqV0J/snls4=' } | ||
|
||
it 'escapes dollar signs in SCRAM-SHA-256 passwords' do | ||
result = instance.send(:escape_password_for_sql, scram_password) | ||
expect(result).to eq('SCRAM-SHA-256\$4096:27klCUc487uwvJVGKI5YNA==\$6K2Y+S3YBlpfRNrLROoO2ulWmnrQoRlGI1GqpNRq0T0=:y4esBVjK/hMtxDB5aWN4ynS1SnQcT1TFTqV0J/snls4=') | ||
end | ||
|
||
it 'handles SCRAM-SHA-256 passwords with multiple dollar signs' do | ||
password = 'SCRAM-SHA-256$1024:salt$key1$key2' | ||
result = instance.send(:escape_password_for_sql, password) | ||
expect(result).to eq('SCRAM-SHA-256\$1024:salt\$key1\$key2') | ||
end | ||
end | ||
|
||
context 'with non-SCRAM passwords' do | ||
it 'does not modify MD5 passwords' do | ||
md5_password = 'md5c5e1324c052bd9e8471c44a3d2bda0c8' | ||
result = instance.send(:escape_password_for_sql, md5_password) | ||
expect(result).to eq(md5_password) | ||
end | ||
|
||
it 'does not modify plain text passwords' do | ||
plain_password = 'my$plain$password' | ||
result = instance.send(:escape_password_for_sql, plain_password) | ||
expect(result).to eq(plain_password) | ||
end | ||
|
||
it 'does not modify other hash types' do | ||
other_hash = 'sha256$somehash$value' | ||
result = instance.send(:escape_password_for_sql, other_hash) | ||
expect(result).to eq(other_hash) | ||
end | ||
end | ||
|
||
context 'with edge cases' do | ||
it 'handles nil passwords' do | ||
result = instance.send(:escape_password_for_sql, nil) | ||
expect(result).to be_nil | ||
end | ||
|
||
it 'handles empty passwords' do | ||
result = instance.send(:escape_password_for_sql, '') | ||
expect(result).to eq('') | ||
end | ||
|
||
it 'handles passwords that start with SCRAM-SHA-256 but have no dollar signs' do | ||
password = 'SCRAM-SHA-256-invalid' | ||
result = instance.send(:escape_password_for_sql, password) | ||
expect(result).to eq(password) | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.