SSD Nodes Learn 🎉 VPS dari $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Cara Guna Ansible Vault Untuk Sulitkan Data Rahsia

Ketahui cara melindungi kata laluan dan token API dalam repositori git menggunakan Ansible Vault. Panduan ini merangkumi penyulitan fail vars, rekey, dan pengurusan akses.

Perkara yang dilindungi oleh Ansible Vault dan perkara yang tidak dilindungi

Ansible Vault menyulitkan rahsia di dalam repositori playbook anda, jadi apa yang disimpan oleh git ialah teks sifer dan bukannya kata laluan teks biasa. Perintah ansible-vault menyulitkan sama ada keseluruhan fail atau satu nilai tunggal di dalam fail, menggunakan kunci simetri yang diperoleh daripada kata laluan yang anda pilih. Ansible menyahsulit kandungan tersebut dalam memori apabila play dijalankan, jadi pemboleh ubah tersebut berkelakuan seperti mana-mana pemboleh ubah lain.

Model tersebut mempunyai satu sempadan yang jelas. Vault melindungi rahsia semasa dalam simpanan di dalam repositori dan tiada yang lain. Sebaik sahaja sesuatu tugas dijalankan, nilai tersebut menjadi teks biasa dalam memori, dalam templat yang dirender, dalam argumen modul, dan dalam output larian melainkan anda menghentikannya. Sesiapa sahaja yang boleh menjalankan playbook tersebut memegang kata laluan vault, jadi vault memberikan anda kerahsiaan daripada orang di luar pasukan, bukan kawalan akses bagi setiap individu di dalamnya.

Jika anda belum menulis playbook lagi, mulakan dengan playbook Ansible pertama terhadap VPS dan kembali semula apabila playbook tersebut memerlukan kata laluan.

Menyulitkan keseluruhan fail, atau satu rentetan tunggal?

ansible-vault encrypt menggantikan fail dengan teks sifer. Fail tersebut menjadi satu blok teks base64 di bawah baris pengepala yang bermula dengan $ANSIBLE_VAULT. Gunakannya apabila fail tersebut tidak mengandungi apa-apa selain rahsia.

ansible-vault encrypt_string menyulitkan satu nilai dan mencetak cebisan YAML yang anda tampal ke dalam fail vars biasa. Nama pemboleh ubah kekal boleh dibaca dan hanya nilainya yang menjadi teks sifer. Gunakannya apabila rahsia diletakkan bersebelahan dengan tetapan teks biasa.

Perbezaan yang penting dalam kerja harian ialah diff. Fail vault disulitkan semula dengan salt rawak yang baharu setiap kali anda menyimpannya, jadi setiap bait teks sifer akan berubah. git diff kemudian menunjukkan satu blok yang tidak boleh dibaca digantikan dengan blok lain yang tidak boleh dibaca, yang bermaksud penyemak tidak dapat mengetahui sama ada anda menukar satu kata laluan atau menulis semula fail tersebut. Dengan encrypt_string, setiap rahsia merupakan bloknya sendiri di dalam fail teks biasa, jadi diff menunjukkan dengan tepat pemboleh ubah mana yang berubah dan membiarkan bahagian fail yang lain tidak terusik.

Bentuk sebaris (inline) mempunyai kos, dan ia timbul semasa waktu pertukaran (rotation): ansible-vault rekey tidak menyentuh blok sebaris. Pilih bentuk fail apabila senarai rahsia panjang dan jarang berubah. Pilih bentuk sebaris apabila fail mencampurkan rahsia dengan pemboleh ubah biasa dan anda mahu semakan kod mempunyai makna.

Susun atur group_vars yang menunjukkan perkara yang dilindungi

Ansible memuatkan group_vars/<group>.yml, dan ia juga memuatkan setiap fail di dalam direktori group_vars/<group>/. Bentuk direktori adalah yang anda perlukan, kerana ia membolehkan satu kumpulan membawa fail teks biasa dan fail yang disulitkan secara bersebelahan.

inventory/
  hosts.ini
group_vars/
  all/
    vars.yml
    vault.yml
  web/
    vars.yml
    vault.yml
host_vars/
  db01/
    vars.yml
    vault.yml
playbooks/
  site.yml

Setiap vault.yml disulitkan. Setiap vars.yml adalah teks biasa. Pembaca boleh melihat nilai mana yang dilindungi tanpa membuka apa-apa, kerana nama fail itu sendiri yang memberitahunya.

Separuh kedua corak ini ialah indirection. Di dalam fail yang disulitkan, awalkan setiap pemboleh ubah dengan vault_.

vault_db_password: "a real password"
vault_grafana_admin_token: "a real token"

Kemudian rujuk nama tersebut daripada fail teks biasa di sebelahnya.

db_password: "{{ vault_db_password }}"
grafana_admin_token: "{{ vault_grafana_admin_token }}"

Peranan (roles) dan templat menggunakan db_password dan tidak perlu mengetahui dari mana nilai itu datang, yang mengekalkan pemisahan antara playbook dan peranan dengan kemas. Fail teks biasa vars.yml berfungsi sebagai indeks yang boleh dicari: grep -r vault_ group_vars/ menyenaraikan setiap rahsia yang dijangkakan oleh repositori, tanpa perlu menyahsulit apa-apa. Kosnya ialah satu nama tambahan bagi setiap rahsia, dan kesilapan taip dalam nama vault_ akan muncul semasa masa jalan (run time) sebagai pemboleh ubah yang tidak ditakrifkan dan bukannya sebagai ralat sintaks.

Menyulitkan satu pemboleh ubah dengan encrypt_string

ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
  --stdin-name 'vault_db_password'

Taipkan rahsia tersebut, kemudian tekan Ctrl-D. --stdin-name membaca nilai daripada input standard, yang memastikan ia tidak disimpan dalam fail sejarah shell anda. Bentuk satu lagi meletakkan nilai tersebut pada baris arahan, di mana shell akan merekodkannya:

ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
  'a real password' --name 'vault_db_password'

Walau apa pun caranya, arahan tersebut akan mencetak blok YAML. Tampalkannya ke dalam fail vars tepat seperti yang dicetak, kerana inden di bawah tag !vault adalah sebahagian daripada nilai tersebut.

vault_db_password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          6638643965323633646262656665306333616466396630323136393465356136396436383331
          3131303163306665326539353837343663313762616561306534373963383531613664393332

Tag !vault memberitahu pemuat YAML bahawa skalar tersebut adalah teks sifer dan bukannya teks biasa. Pengepala tersebut membawa versi format, sifer, dan label ID vault yang menyulitkannya. Nilai yang disulitkan tanpa ID vault membawa pengepala 1.1 tanpa label, yang masih berfungsi dan hanya memberikan maklumat yang kurang tentang asal usul kata laluan tersebut.

Di manakah kata laluan vault disimpan?

Di luar repositori. Itu adalah satu-satunya peraturan tanpa pengecualian.

--ask-vault-pass akan meminta input sekali bagi setiap pelaksanaan dan tidak menyimpan apa-apa. Ia sesuai untuk komputer riba, namun tidak sesuai untuk cron job atau CI runner.

Fail kata laluan ialah fail teks biasa yang baris pertamanya mengandungi kata laluan tersebut. Cipta fail kosong dengan keizinan (permissions) yang ketat, kemudian isikan menggunakan editor, supaya kata laluan tidak masuk ke dalam shell history anda:

mkdir -p ~/.ansible
install -m 600 /dev/null ~/.ansible/vault-prod.txt
$EDITOR ~/.ansible/vault-prod.txt

Tujukan mana-mana arahan kepadanya dengan --vault-password-file:

ansible-playbook -i inventory/hosts.ini playbooks/site.yml \
  --vault-password-file ~/.ansible/vault-prod.txt

Mengulang flag tersebut pada setiap arahan mudah untuk dilupakan, jadi tetapkannya sekali dalam ansible.cfg di punca (root) repositori.

[defaults]
inventory = inventory/hosts.ini
vault_password_file = ~/.ansible/vault-prod.txt

Tetapan yang sama membaca daripada pemboleh ubah persekitaran ANSIBLE_VAULT_PASSWORD_FILE, iaitu cara CI job biasanya membekalkannya. Job tersebut menulis kata laluan daripada stor kelayakan (credential store) miliknya ke dalam fail di direktori sementara, mengeksport pemboleh ubah tersebut, dan memadam fail itu apabila pelaksanaan tamat. Tambahkan corak nama fail ke dalam .gitignore juga, kerana laluan dalam ansible.cfg akan di-commit, dan lambat-laun seseorang akan mencipta fail sebenar di dalam checkout tersebut.

Jika fail kata laluan boleh laksana (executable), Ansible akan menjalankannya dan membaca kata laluan daripada output standardnya dan bukannya membaca fail tersebut sebagai teks. Begitulah cara anda menarik kata laluan vault daripada system keyring atau pengurus rahsia awan (cloud secret manager) tanpa menulisnya ke cakera langsung. Skrip yang digunakan melalui --vault-id mempunyai keperluan tambahan: namanya mesti berakhir dengan -client atau -client berserta sambungan (extension), ia mesti boleh laksana, ia mesti menerima pilihan --vault-id, dan ia mesti mencetak kata laluan ke output standard.

Dua ID vault: staging dan production

ID vault ialah label yang dilampirkan pada kata laluan vault, ditulis sebagai label@source. Sumbernya ialah prompt, iaitu laluan ke fail kata laluan, atau laluan ke skrip klien. Label membolehkan satu repositori menyimpan rahsia di bawah lebih daripada satu kata laluan, supaya kata laluan staging tidak membuka fail production.

ansible-vault encrypt --vault-id staging@~/.ansible/vault-staging.txt \
  group_vars/staging/vault.yml
ansible-vault encrypt --vault-id prod@~/.ansible/vault-prod.txt \
  group_vars/prod/vault.yml

Hantarkan setiap ID yang mungkin diperlukan oleh sesuatu pelaksanaan:

ansible-playbook playbooks/site.yml \
  --vault-id staging@~/.ansible/vault-staging.txt \
  --vault-id prod@~/.ansible/vault-prod.txt

Atau senaraikan kesemuanya sekali dalam ansible.cfg:

[defaults]
vault_identity_list = staging@~/.ansible/vault-staging.txt, prod@~/.ansible/vault-prod.txt

Satu kelakuan sering mengejutkan pengguna. Secara lalai, label hanyalah petunjuk, bukan kunci. Ansible mencuba setiap rahsia yang dipegangnya terhadap fail tersebut sehingga salah satu daripadanya berjaya menyahsulit fail itu, jadi fail yang dilabelkan staging masih boleh dibuka jika kata laluan production kebetulan merupakan kunci yang betul. Tetapkan vault_id_match = True di bawah [defaults], atau pemboleh ubah persekitaran ANSIBLE_VAULT_ID_MATCH, dan Ansible hanya akan menggunakan rahsia yang labelnya sepadan dengan pengepala fail. Pemeriksaan tersebut memerlukan pengepala 1.2, jadi ia hanya terpakai pada kandungan yang disulitkan dengan ID vault sejak awal lagi.

Dengan lebih daripada satu ID dimuatkan, ansible-vault encrypt tidak lagi mengetahui kata laluan mana yang perlu digunakan untuk menyulitkan. Namakannya dengan --encrypt-vault-id prod, atau tetapkan vault_encrypt_identity dalam ansible.cfg supaya repositori mempunyai tetapan lalai.

Kelebihannya ialah skop penempatan. Tugasan CI yang membuat penempatan staging hanya diberikan kata laluan staging sahaja, jadi runner yang terjejas tidak boleh membaca kelayakan production. Sebaik sahaja anda menjalankan play merentasi sekumpulan pelayan Linux daripada satu mesin kawalan, pemisahan tersebut menjadi penentu antara insiden kecil dan insiden yang sangat besar.

Menukar kunci (rekeying) akan menukar kata laluan vault dan menyulitkan semula kandungan di bawah kata laluan baharu. Tindakan ini tidak membatalkan apa-apa. Sesiapa yang pernah memegang kata laluan lama masih boleh menyahsulit mana-mana salinan repositori yang mereka simpan, termasuk setiap komit lama dalam salinan tersebut. Oleh itu, anggap kata laluan vault telah terbocor sebaik sahaja pemegangnya berhenti kerja, dan lakukan penggiliran mengikut urutan ini.

  1. Tukar kelayakan sebenar pada pelayan dan dalam perkhidmatan pihak ketiga. Langkah ini adalah langkah yang benar-benar membatalkan akses.
  2. Masukkan nilai baharu ke dalam fail vault dengan ansible-vault edit.
  3. Tukar kunci setiap fail yang disulitkan kepada kata laluan vault yang baharu.
  4. Berikan kata laluan vault baharu kepada mereka yang masih memerlukannya, melalui saluran yang bukan repositori tersebut.
ansible-vault rekey --vault-id prod@~/.ansible/vault-prod-old.txt \
  --new-vault-id prod@prompt \
  group_vars/prod/vault.yml host_vars/db01/vault.yml

rekey menerima beberapa fail dalam satu arahan, dan --new-vault-id prod@prompt meminta kata laluan baharu sekali sahaja dan bukannya membacanya daripada cakera. Kekalkan label yang sama melainkan anda mempunyai sebab untuk menukarnya, kerana label tersebut ditulis ke dalam pengepala setiap fail yang ditulis semula oleh arahan itu.

Di sinilah bentuk inline memberikan kesan. ansible-vault rekey beroperasi pada fail yang disulitkan sepenuhnya, jadi blok !vault yang berada di dalam fail vars teks biasa tidak akan disentuh. Cari fail tersebut terlebih dahulu, kemudian jana semula setiap satu dengan encrypt_string di bawah kata laluan baharu:

grep -rl '!vault' group_vars/ host_vars/

Itulah pertukaran yang perlu dibuat sepenuhnya. Blok inline memberikan anda diff yang boleh dibaca dan memerlukan satu pusingan manual semasa waktu penggiliran. Fail yang disulitkan sepenuhnya boleh digilirkan dengan satu arahan tetapi tidak memberikan maklumat berguna semasa semakan.

Mengapa rahsia masih muncul dalam output anda

Vault selesai sebaik sahaja nilai dinyahsulit. Ansible melaporkan hasil sesuatu tugasan, dan modul yang memaparkan argumennya akan membawa kelayakan tersebut ke dalam laporan itu. Pelaksanaan verbose, --diff pada tugasan templat, tugasan gagal yang memaparkan argumennya, atau pemalam panggil balik (callback plugin) yang menulis output ke fail, masing-masing akan menyimpan teks biasa (plaintext). Menyulitkan fail tidak memberi kesan kepada mana-mana perkara tersebut.

no_log: true ialah suisnya. Tetapkan ia pada mana-mana tugasan yang menerima kelayakan.

- name: Write the application environment file
  ansible.builtin.template:
    src: app.env.j2
    dest: /etc/myapp/app.env
    owner: myapp
    group: myapp
    mode: "0600"
  no_log: true

Ansible kemudian akan menyekat hasil tugasan tersebut daripada output, supaya log merekodkan bahawa tugasan telah dijalankan tanpa merekodkan apa yang dikendalikannya. Tetapkan ia pada gelung (loop) khususnya, kerana gelung melaporkan satu hasil bagi setiap item, dan gelung ke atas senarai kelayakan akan melaporkan keseluruhan senarai tersebut.

Empat tempat lain di mana rahsia yang dinyahsulit boleh terdedah, yang mana tiada satu pun dilindungi oleh no_log:

  • Fail yang dijana daripada templat mewarisi mode dan owner yang anda berikan kepadanya. Tetapkan mode: "0600" dan pemilik khusus pada mana-mana perkara yang menyimpan kelayakan, atau rahsia tersebut akan berakhir dengan boleh dibaca oleh semua orang (world readable) pada hos sasaran.
  • Rahsia yang dihantar kepada ansible.builtin.command atau ansible.builtin.shell akan muncul dalam senarai proses pada hos sasaran semasa arahan dijalankan, di mana mana-mana pengguna tempatan boleh membacanya. Hantarkannya melalui fail atau pemboleh ubah persekitaran (environment variable) sebaliknya.
  • Cache fakta (fact caching) menulis fakta yang dikumpul ke cakera pada mesin kawalan, jadi pemboleh ubah berdaftar yang menyimpan rahsia boleh berakhir dalam fail cache yang tidak dianggap sensitif oleh sesiapa pun.
  • Rahsia yang sama biasanya wujud di tempat kedua, seperti fail persekitaran yang dibaca oleh kontena. Peraturan di sana adalah berasingan, dan menjauhkan kelayakan daripada fail env Compose merangkumi aspek tersebut.

no_log menjadikan penyahpepijatan (debugging) lebih sukar, yang memang merupakan tujuan sebenarnya. Alih keluar ia buat sementara waktu pada hos ujian apabila tugasan tidak berfungsi dengan betul, dan pasang semula sebelum perubahan tersebut sampai ke pengeluaran (production).

Membaca dan menyunting fail yang disulitkan tanpa meninggalkan teks biasa

ansible-vault view group_vars/prod/vault.yml menyahsulit ke dalam pager dan tidak menulis apa-apa ke cakera. ansible-vault edit menyahsulit ke dalam fail sementara, membuka $EDITOR anda, dan menyulitkan semula apabila anda menutupnya. Utamakan kedua-duanya berbanding ansible-vault decrypt, yang meninggalkan fail teks biasa di dalam working tree. Fail vault yang telah dinyahsulit dan tersilap staging adalah cara paling lazim bagi kelayakan sebenar sampai ke repositori awam.

Git boleh memaparkan diff yang boleh dibaca untuk fail yang disulitkan sepenuhnya dengan menyahsulitnya semasa proses berjalan:

git config --local diff.ansible-vault.textconv "ansible-vault view --vault-password-file ~/.ansible/vault-prod.txt"
printf '%s\n' 'group_vars/**/vault.yml diff=ansible-vault' >> .gitattributes

Fahami fungsi arahan tersebut sebelum mengaktifkannya. git diff kini akan mencetak rahsia pengeluaran ke dalam terminal anda, yang meletakkannya ke dalam scrollback dan mana-mana perkongsian skrin. Ini merupakan kemudahan setempat untuk seorang pengguna pada satu mesin, jadi pastikan git config kekal setempat, dan jangkakan checkout orang lain berkelakuan berbeza melainkan mereka menetapkan perkara yang sama.

Apabila Vault bukan lagi alat yang sesuai

Vault ialah format fail dengan satu kata laluan bagi setiap label, dan bentuk tersebut menentukan had penggunaannya. Beralihlah kepada stor rahsia (secret store) sebenar apabila mana-mana keadaan berikut berlaku.

  • Anda memerlukan akses bagi setiap individu. Semua orang yang menjalankan playbook memegang kata laluan yang sama, dan ID vault memisahkan akses mengikut persekitaran, bukan mengikut individu.
  • Anda memerlukan jejak audit. Vault tidak merekodkan maklumat tentang siapa yang menyahsulit apa, atau bila ia dilakukan.
  • Anda memerlukan penggiliran mengikut jadual. Vault tidak mempunyai tarikh luput dan tiada pengurusan versi, jadi tiada apa yang memberitahu anda bahawa bukti kelayakan belum ditukar dalam tempoh dua tahun.
  • Aplikasi itu sendiri memerlukan rahsia tersebut semasa masa jalan (run time). Servis yang membaca kata laluan pangkalan datanya semasa but tidak sepatutnya membacanya daripada repositori penempatan anda.

Corak tersebut kemudiannya diterbalikkan. Ansible berhenti menyimpan rahsia dan mula mendapatkannya semasa masa jalan melalui pemalam carian (lookup plugin), terhadap HashiCorp Vault (produk berbeza dengan nama yang mengelirukan), pengurus rahsia pembekal awan, atau keyring pada mesin kawalan. Repositori menyimpan laluan, stor menyimpan nilai, dan stor tersebut menyimpan log akses. Bagi pasukan kecil, pengurus kata laluan yang dihoskan sendiri dengan API, seperti pelayan Vaultwarden, melaksanakan tugas yang sama pada skala yang lebih kecil.

Satu bukti kelayakan kekal di luar semua ini. Kunci SSH yang digunakan oleh mesin kawalan anda untuk mencapai pelayan bukanlah masalah vault, kerana Ansible memerlukannya sebelum sebarang play boleh dijalankan. Kendalikannya dengan ejen dan frasa laluan, mengikut garis panduan asas pengurusan kunci SSH.

FAQ

Patutkah saya menyulitkan keseluruhan fail vars atau hanya rentetan rahsia?

Sulitkan keseluruhan fail apabila ia hanya mengandungi rahsia, kerana satu arahan boleh menukar kesemuanya dan susun atur kekal ringkas. Gunakan ansible-vault encrypt_string apabila rahsia diletakkan bersebelahan dengan pemboleh ubah biasa, kerana dengan cara itu hanya nilai yang disulitkan akan berubah dalam diff dan penyemak boleh melihat pemboleh ubah mana yang telah diubah. Pertukarannya ialah pada proses penukaran (rotation). ansible-vault rekey meliputi keseluruhan fail dan membiarkan blok !vault sebaris tidak terusik, jadi blok tersebut perlu dijana semula secara manual di bawah kata laluan baharu.

Di manakah fail kata laluan Ansible Vault patut disimpan?

Di luar repositori, dengan mod 0600, pada laluan seperti ~/.ansible/vault-prod.txt. Tuding kepadanya dengan --vault-password-file, atau tetapkan vault_password_file di bawah [defaults] dalam ansible.cfg, atau tetapkan ANSIBLE_VAULT_PASSWORD_FILE dalam persekitaran. Dalam CI, minta tugasan menulis kata laluan daripada stor kelayakan sendiri ke dalam fail sementara, eksport pemboleh ubah tersebut, dan padam fail apabila tugasan tamat. Jika fail tersebut boleh laksana, Ansible akan menjalankannya dan membaca kata laluan daripada output standard, yang membolehkan anda mendapatkannya daripada keyring dan bukannya menyimpannya pada cakera.

Bagaimanakah cara menggunakan kata laluan vault yang berbeza untuk staging dan production?

Berikan setiap kata laluan label dengan --vault-id staging@/path/to/file dan --vault-id prod@/path/to/file, dan sulitkan fail setiap persekitaran di bawah labelnya sendiri. Berikan kedua-dua ID semasa masa jalan (run time), atau senaraikannya dalam vault_identity_list di bawah [defaults]. Secara lalai, Ansible akan mencuba setiap rahsia yang dimilikinya sehingga satu daripadanya berjaya menyahsulit fail tersebut, jadi tetapkan vault_id_match = True jika anda mahu ia mencuba hanya rahsia yang labelnya sepadan dengan pengepala fail. Dengan beberapa ID dimuatkan, pilih ID penyulitan dengan --encrypt-vault-id.

Adakah Ansible Vault menghalang kata laluan daripada muncul dalam output larian?

Tidak. Vault hanya melindungi rahsia semasa dalam simpanan di dalam repositori. Sebaik sahaja tugasan dijalankan, nilainya adalah teks biasa (plaintext), dan larian verbose atau tugasan yang gagal boleh membawanya ke dalam log. Tambahkan no_log: true pada setiap tugasan yang mengendalikan kelayakan, tetapkan mode dan owner yang ketat pada mana-mana fail yang anda buat templat, dan elakkan menghantar rahsia sebagai argumen arahan, kerana ia boleh dilihat dalam senarai proses pada hos sasaran semasa arahan dijalankan.