Skip to content

Commit

Permalink
terraform: encrypt backup state
Browse files Browse the repository at this point in the history
  • Loading branch information
siddarthkay committed Feb 6, 2025
1 parent 4101e8a commit eae044d
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 3 deletions.
2 changes: 2 additions & 0 deletions ansible/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ ansible localhost -m debug -a 'var=groups'
ansible all -o -m debug -a 'var=ansible_host' | columns -t
```

A backup of the Terraform state is created at `.terraform/terraform.tfstate.backup`. It is symetrically encrypted using [Fernet algorithm](https://cryptography.io/en/latest/fernet/) with a key generated from haed `CONSUL_HTTP_TOKEN` and can be decrypted by using [`decrypt_tf_backup.py`](https://github.com/status-im/infra-utils/blob/master/terraform/decrypt_tf_backup.py) script.

# Variables

Ansible variables can be provided to Ansible using the `--extra-vars`/`-e` flag. An example of such a flag is:
Expand Down
37 changes: 34 additions & 3 deletions ansible/terraform.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
import re
import traceback
from subprocess import Popen, PIPE
from cryptography.fernet import Fernet
import base64
from pathlib import Path
import hashlib

TERRAFORM_DIR = os.environ.get('ANSIBLE_TF_DIR', os.getcwd())
TERRAFORM_ENV = os.path.join(TERRAFORM_DIR, '.terraform/environment')
Expand Down Expand Up @@ -402,10 +406,37 @@ def _execute_shell():
return json.loads(out_cmd)


def _get_encryption_key():
"""Generate encryption key using CONSUL_HTTP_TOKEN"""
key_file = Path(TERRAFORM_DIR) / '.terraform' / '.state_key'

consul_token = os.environ.get('CONSUL_HTTP_TOKEN')
if not consul_token:
raise ValueError("CONSUL_HTTP_TOKEN environment variable is required for state encryption")

# Generate a fixed-length key using SHA256
hashed = hashlib.sha256(consul_token.encode()).digest()
# Convert to URL-safe base64 encoding as required by Fernet
key = base64.urlsafe_b64encode(hashed)

return key

def _backup_tf(tfstate):
# Crates a state backup in case we lose Consul
with open(TERRAFORM_BPK, 'w') as f:
f.write(json.dumps(tfstate.state_json))
"""Creates an encrypted state backup"""
try:
key = _get_encryption_key()
cipher = Fernet(key)

state_data = json.dumps(tfstate.state_json).encode()
encrypted_data = cipher.encrypt(state_data)

backup_path = Path(TERRAFORM_BPK)
backup_path.parent.mkdir(parents=True, exist_ok=True)
backup_path.write_bytes(encrypted_data)
backup_path.chmod(0o600)

except Exception as e:
sys.stderr.write(f"Warning: Failed to create encrypted state backup: {str(e)}\n")

def _backup_ansible(inventory):
# Crates a state backup in Ansible inventory format
Expand Down

0 comments on commit eae044d

Please sign in to comment.