Sổ tay kỹ thuật

Mã hoá secret trong repo – SOPS + age và ansible-vault

how-to#sops#ansible#secrets#git· cập nhật 2026-11-06

Dùng khi cần commit mật khẩu thiết bị, token API vào repo automation ở dạng mã hoá, chia quyền giải mã theo môi trường và dùng trực tiếp trong playbook.

Sửa trang này
Mục lục

Tóm tắt: SOPS + age: mỗi người/CI có một cặp key age, .sops.yaml khai báo file nào mã hoá cho public key nào, SOPS chỉ mã hoá giá trị nên diff vẫn đọc được. Ansible đọc trực tiếp qua collection community.sops. ansible-vault: không cần tool ngoài, mã hoá cả file hoặc từng biến, phân môi trường bằng --vault-id. So sánh tổng quan ở Quản lý secret.

Luồng hoạt động

flowchart LR A["age-keygen"] -->|"public key"| B[".sops.yaml creation_rules"] B --> C["sops -e -i secrets.sops.yaml"] C --> D["git commit (đã mã hoá)"] D --> E["CI / máy chạy ansible"] F["SOPS_AGE_KEY_FILE (private key)"] --> E E -->|"community.sops giải mã"| G["Playbook"]

Cấu hình

SOPS + age: tạo key

# Cài: tải binary sops và age từ GitHub releases, hoặc apt install age (Ubuntu 22.04+)
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# In ra: Public key: age1...  (public key, được phép commit)
chmod 600 ~/.config/sops/age/keys.txt

SOPS tự tìm private key ở ~/.config/sops/age/keys.txt trên Linux; nơi khác thì đặt SOPS_AGE_KEY_FILE. Trong CI, đưa nội dung key vào CI secret và expose qua biến SOPS_AGE_KEY.

File keys.txt là private key. Backup nó vào password manager; mất key = mất khả năng giải mã. Không commit, không để trong thư mục repo.

.sops.yaml

.sops.yaml
creation_rules:
  # prod: chỉ team lead + CI prod giải mã được
  - path_regex: inventories/prod/.*\.sops\.ya?ml$
    encrypted_regex: ^(ansible_password|ansible_become_password|.*_token|.*_key|tacacs_.*)$
    age: >-
      age1<PUBKEY_LEAD>,
      age1<PUBKEY_CI_PROD>
 
  # lab và mọi file .sops.* còn lại
  - path_regex: .*\.sops\.(ya?ml|json|env)$
    age: >-
      age1<PUBKEY_LEAD>,
      age1<PUBKEY_DEV1>,
      age1<PUBKEY_CI_LAB>
  • SOPS dùng rule đầu tiên khớp path_regex, nên đặt rule cụ thể lên trước.
  • encrypted_regex chỉ mã hoá key khớp regex, các key khác để plaintext (dễ review). Bỏ đi thì mọi giá trị đều bị mã hoá.
  • Thay age1<...> bằng public key thật (chuỗi bắt đầu bằng age1).

Lệnh SOPS

Mục đích Lệnh
Mã hoá file tại chỗ sops -e -i inventories/prod/group_vars/all/secrets.sops.yaml
Sửa (mở $EDITOR, tự mã hoá lại khi lưu) sops inventories/prod/group_vars/all/secrets.sops.yaml
Xem bản giải mã ra stdout sops -d secrets.sops.yaml
Lấy một giá trị sops -d --extract '["netbox_token"]' secrets.sops.yaml
Thêm/bớt người sau khi sửa .sops.yaml sops updatekeys secrets.sops.yaml
Đổi data key (sau khi thu hồi người) sops -r -i secrets.sops.yaml
Chạy lệnh với secret làm env, không ghi file sops exec-env secrets.sops.env 'terraform plan'

SOPS ≥ 3.9 có thêm dạng subcommand sops encrypt / sops decrypt / sops edit; dạng cờ -e/-d ở trên vẫn dùng được.

Dùng SOPS trong Ansible

ansible-galaxy collection install community.sops
ansible.cfg
[defaults]
vars_plugins_enabled = host_group_vars,community.sops.sops

Với vars plugin trên, file group_vars/<group>/*.sops.yaml và host_vars/<host>/*.sops.yaml được giải mã tự động như biến thường. Đọc file ngoài group_vars:

playbooks/netbox_sync.yml
- hosts: localhost
  gather_facts: false
  vars:
    sec: "{{ lookup('community.sops.sops', 'secrets/netbox.sops.yaml') | from_yaml }}"
  tasks:
    - name: Lấy danh sách device từ NetBox
      ansible.builtin.uri:
        url: "https://netbox.example.com/api/dcim/devices/?limit=0"
        headers:
          Authorization: "Token {{ sec.netbox_token }}"
      register: devices
      no_log: true

ansible-vault

# Password lưu trong file ngoài repo (hoặc script lấy từ password manager)
install -m 600 /dev/null ~/.ansible/vault-prod.pass   # rồi ghi password vào file
 
# Mã hoá cả file
ansible-vault create --vault-id prod@~/.ansible/vault-prod.pass inventories/prod/group_vars/all/vault.yml
ansible-vault edit   --vault-id prod@~/.ansible/vault-prod.pass inventories/prod/group_vars/all/vault.yml
 
# Mã hoá một biến, dán kết quả vào file vars thường (đọc giá trị từ stdin, không lộ trong history)
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.pass --stdin-name ansible_password
 
# Chạy playbook
ansible-playbook -i inventories/prod site.yml --vault-id prod@~/.ansible/vault-prod.pass
ansible-playbook -i inventories/lab  site.yml --vault-id lab@prompt
Mục đích Lệnh
Xem nội dung ansible-vault view --vault-id prod@<file> vault.yml
Mã hoá / giải mã file có sẵn ansible-vault encrypt ... / ansible-vault decrypt ...
Đổi password ansible-vault rekey --vault-id prod@old.pass --new-vault-id prod@new.pass vault.yml
Đặt mặc định ANSIBLE_VAULT_PASSWORD_FILE=~/.ansible/vault-prod.pass hoặc vault_identity_list trong ansible.cfg

Quy ước dễ grep: biến bí mật đặt trong vault.yml với tiền tố vault_ (vd vault_tacacs_key), file vars.yml cạnh đó tham chiếu tacacs_key: "{{ vault_tacacs_key }}". Tìm biến không cần giải mã.

Chặn commit plaintext

.git/hooks/pre-commit (hoặc dùng pre-commit framework)
#!/bin/sh
for f in $(git diff --cached --name-only | grep -E '\.sops\.(ya?ml|json|env)$'); do
  grep -q '^sops:' "$f" || grep -q '"sops":' "$f" || { echo "Chưa mã hoá: $f"; exit 1; }
done
for f in $(git diff --cached --name-only | grep -E '(^|/)vault\.ya?ml$'); do
  head -1 "$f" | grep -q '^\$ANSIBLE_VAULT' || { echo "Chưa mã hoá: $f"; exit 1; }
done

Kết hợp gitleaks: pre-commit hooks.

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
no matching creation rules found Đường dẫn file không khớp path_regex nào Kiểm tra regex; chạy sops từ thư mục gốc repo
Failed to get the data key required to decrypt Máy không có private key nào khớp recipient trong file Đặt SOPS_AGE_KEY_FILE; nếu là người mới thì cần ai đó updatekeys thêm public key
Đã sửa .sops.yaml nhưng người mới vẫn không giải mã được File cũ chưa được cập nhật recipient sops updatekeys <file> cho từng file
MAC mismatch Sửa file mã hoá bằng editor thường Revert, luôn sửa qua sops <file>
Ansible không thấy biến trong *.sops.yaml Chưa bật vars plugin hoặc chưa cài collection Thêm community.sops.sops vào vars_plugins_enabled
Decryption failed (no vault secrets were found) Thiếu --vault-id / password file Truyền --vault-id, hoặc đặt ANSIBLE_VAULT_PASSWORD_FILE
Secret hiện trong output -v Task không có no_log Thêm no_log: true cho task dùng secret

Tham khảo