Ansible-vault decrypt: обходимся без Ansible

от автора

Исходные данные

Дано:

  • конвейер CI/CD, реализованный, к примеру, в GitLab. Для корректной работы ему требуются, как это очень часто бывает, некие секреты — API-токены, пары логи/пароль, приватные SSH-ключи — да всё, о чём только можно подумать;

  • работает этот сборочный конвейер, как это тоже часто бывает, на базе контейнеров. Соответственно, чем меньше по размеру образы — тем лучше, чем меньше в них всякой всячины — тем лучше.

Требуется консольная утилита, которая:

  • занимает минимум места;

  • умеет расшифровывать секреты, зашифрованные ansible-vault;

  • не требует никаких внешних зависимостей;

  • умеет читать ключ из файла.

Я думаю, что люди, причастные к созданию сборочных конвейеров, по достоинству смогут оценить каждое из этих требований. Ну а что у меня получилось в результате — читайте далее.

На всякий случай сразу напоминаю, что по действующему законодательству разработка средств криптографической защиты информации в РФ — лицензируемая деятельность. Иначе говоря, без наличия лицензии вы не можете просто так взять и продавать получившееся решение.

По поводу допустимости полных текстов расшифровщиков в статьях вроде этой — надеюсь, что компетентные в этом вопросе читатели смогут внести свои уточнения в комментариях.

Начнём сначала

Итак, предположим, что у нас на Linux-хосте с CentOS 7 уже установлен Ansible, к примеру, версии 2.9 для Python версии 3.6. Установлен, конечно же, с помощью virtualenv в каталог «/opt/ansible«. Дальше для целей удовлетворения чистого научного любопытства возьмём какой-нибудь YaML-файл, и зашифруем его с помощью утилиты ansible-vault:

ansible-vault encrypt vaulted.yml --vault-password-file=.password

Этот вызов, как можно догадаться, зашифрует файл vaulted.yml с помощью пароля, который хранится в файле .password.

Итак, что получается после зашифровывания файла с помощью утилиты ansible-vault? На первый взгляд — белиберда какая-то, поэтому спрячу её под спойлер:

Содержимое файла vaulted.yml
$ANSIBLE_VAULT;1.1;AES256 61373536353963313739366536643661313861663266373130373730666634343337356536333664 3365393033623439356364663537353365386464623836640a356464633264626330383232353362 63613135373638393665663962303530323061376432333931306161303966633338303565666337 6465393837636665300a633732313730626265636538363339383237306264633830653665343639 30353863633137313866393566643661323536633666343837623130363966613363373962343630 34386234633236363363326436666630643937313630346230386538613735366431363934316364 37346337323833333165386534353432386663343465333836643131643237313262386634396534 38316630356530626430316238383364376561393637363262613666373836346262666536613164 66316638343162626631623535323666643863303231396432666365626536393062386531623165 63613934323836303536613532623864303839313038336232616134626433353166383837643165 643439363835643731316238316439633039

Ну а как именно эта белиберда работает «под капотом» — давайте разбираться.

Открываем файл /opt/ansible/lib/python3.6/site-packages/ansible/parsing/vault/__init__.py, и в коде метода encrypt класса VaultLib видим следующий вызов:

VaultLib.encrypt
 ...  b_ciphertext = this_cipher.encrypt(b_plaintext, secret)  ...

То есть результирующее содержимое нашего файла будет создано в результате вызова метода encrypt некоторого класса. Какого именно — в общем-то, невелика загадка, ниже по файлу есть всего один класс с именем VaultAES256.

Смотрим в его метод encrypt:

VaultAES256.encrypt
@classmethod def encrypt(cls, b_plaintext, secret):     if secret is None:         raise AnsibleVaultError('The secret passed to encrypt() was None')     b_salt = os.urandom(32)     b_password = secret.bytes     b_key1, b_key2, b_iv = cls._gen_key_initctr(b_password, b_salt)      if HAS_CRYPTOGRAPHY:         b_hmac, b_ciphertext = cls._encrypt_cryptography(b_plaintext, b_key1, b_key2, b_iv)     elif HAS_PYCRYPTO:         b_hmac, b_ciphertext = cls._encrypt_pycrypto(b_plaintext, b_key1, b_key2, b_iv)     else:         raise AnsibleError(NEED_CRYPTO_LIBRARY + '(Detected in encrypt)')      b_vaulttext = b'\n'.join([hexlify(b_salt), b_hmac, b_ciphertext])     # Unnecessary but getting rid of it is a backwards incompatible vault     # format change     b_vaulttext = hexlify(b_vaulttext)     return b_vaulttext

То есть перво-наперво генерируется «соль» длиной 32 байта. Затем из побайтного представления пароля и «соли» вызовом _gen_key_initctr генерируется пара ключей (b_key1, b_key2) и вектор инициализации (b_iv).

Генерация ключей

Что же происходит в _gen_key_initctr?

_gen_key_initctr:
@classmethod def _gen_key_initctr(cls, b_password, b_salt):     # 16 for AES 128, 32 for AES256     key_length = 32      if HAS_CRYPTOGRAPHY:         # AES is a 128-bit block cipher, so IVs and counter nonces are 16 bytes         iv_length = algorithms.AES.block_size // 8          b_derivedkey = cls._create_key_cryptography(b_password, b_salt, key_length, iv_length)         b_iv = b_derivedkey[(key_length * 2):(key_length * 2) + iv_length]     elif HAS_PYCRYPTO:         # match the size used for counter.new to avoid extra work         iv_length = 16          b_derivedkey = cls._create_key_pycrypto(b_password, b_salt, key_length, iv_length)         b_iv = hexlify(b_derivedkey[(key_length * 2):(key_length * 2) + iv_length])     else:         raise AnsibleError(NEED_CRYPTO_LIBRARY + '(Detected in initctr)')      b_key1 = b_derivedkey[:key_length]     b_key2 = b_derivedkey[key_length:(key_length * 2)]      return b_key1, b_key2, b_iv

Если по сути, то внутри этого метода вызов _create_key_cryptography на основе пароля, «соли», длины ключа и длины вектора инициализации генерирует некий производный ключ (строка 10 приведённого фрагмента). Далее этот производный ключ разбивается на части, и получаются те самые b_key1, b_key2 и b_iv.

Следуем по кроличьей норе дальше. Что внутри _create_key_cryptography?

_create_key_cryptography:
@staticmethod def _create_key_cryptography(b_password, b_salt, key_length, iv_length):     kdf = PBKDF2HMAC(         algorithm=hashes.SHA256(),         length=2 * key_length + iv_length,         salt=b_salt,         iterations=10000,         backend=CRYPTOGRAPHY_BACKEND)     b_derivedkey = kdf.derive(b_password)      return b_derivedkey

Ничего особенного. Если оставить в стороне всю мишуру, то в итоге вызывается функция библиотеки OpenSSL под названием PBKDF2HMAC с нужными параметрами. Можете, кстати, самолично в этом убедиться, открыв файл /opt/ansible/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/backend.py.

Кстати, длина производного ключа, как видите, специально выбирается таким образом, чтобы хватило и на b_key1, и на b_key2, и на b_iv.

Собственно шифрование

Движемся дальше. Здесь нас встречает вызов _encrypt_cryptography с параметрами в виде открытого текста, обоих ключей и вектора инициализации:

_encrypt_cryptography
@staticmethod def _encrypt_cryptography(b_plaintext, b_key1, b_key2, b_iv):     cipher = C_Cipher(algorithms.AES(b_key1), modes.CTR(b_iv), CRYPTOGRAPHY_BACKEND)     encryptor = cipher.encryptor()     padder = padding.PKCS7(algorithms.AES.block_size).padder()     b_ciphertext = encryptor.update(padder.update(b_plaintext) + padder.finalize())     b_ciphertext += encryptor.finalize()      # COMBINE SALT, DIGEST AND DATA     hmac = HMAC(b_key2, hashes.SHA256(), CRYPTOGRAPHY_BACKEND)     hmac.update(b_ciphertext)     b_hmac = hmac.finalize()      return to_bytes(hexlify(b_hmac), errors='surrogate_or_strict'), hexlify(b_ciphertext)

В принципе, тут нет ничего особенного: шифр инициализируется из вектора b_iv, затем первым ключом b_key1 шифруется исходный текст, а результат этого шифрования хэшируется с помощью второго ключа b_key2.

Полученные в итоге байты подписи и шифртекста преобразуются в строки своих шестнадцатеричных представлений через hexlify. (см. строка 14 фрагмента выше)

Окончательное оформление файла

Возвращаемся к строкам 16-20 фрагмента VaultAES256.encrypt: три строки, содержащие «соль», подпись и шифртекст, склеиваются вместе, после чего снова преобразуются в шестнадцатеричное представление (комментарий прямо подсказывает, что это — для обратной совместимости).

Дальше дописывается заголовок (помните, тот самый — $ANSIBLE_VAULT;1.1;AES256), ну и, в общем-то, всё.

Обратный процесс

После того, как мы разобрались в прямом процессе, реализовать обратный будет не слишком сложно — по крайней мере, если выбрать правильный инструмент.

Понятно, что Python нам не подходит, иначе можно было и огород не городить: ansible-vault одинаково хорошо работает в обе стороны. С другой стороны, никто не мешает на базе библиотек Ansible написать что-либо своё — в качестве разминки перед «подходом к снаряду» я так и сделал, и о результате напишу отдельную статью.

Тем не менее, для написания предмета статьи я воспользовался FreePascal. Ввиду того, что языковой холивар темой статьи не является, буду краток: выбрал этот язык, во-первых, потому что могу, а во-вторых — потому что получаемый бинарник удовлетворяет заданным требованиям.

Итак, нам понадобятся: FreePascal версии 3.0.4 (эта версия в виде готовых пакетов — самая свежая, нормально устанавливающаяся в CentOS 7), и библиотека DCPCrypt версии 2.1 (на GitHub). Интересно, что прямо вместе с компилятором (fpc) и обширным набором библиотек в rpm-пакете поставляется консольная среда разработки fp.

К сожалению, «искаропки» модули этой библиотеки не собираются компилятором fpc — в них нужны минимальные правки. С другой стороны, я предполагаю, что без этих правок предмет статьи перестаёт относиться к лицензируемым видам деятельности и начинает представлять чисто академический интерес — именно поэтому выкладываю статью без них.

Часть кода, относящуюся к генерированию производного ключа (реализацию той самой функции PBKDF2), я нашёл в интернете, и поместил в отдельный модуль под названием «kdf».

Вот этот модуль собственной персоной:

kdf.pas
{$MODE OBJFPC}  // ALL CREDITS FOR THIS CODE TO https://keit.co/p/dcpcrypt-hmac-rfc2104/  unit kdf;  interface uses dcpcrypt2,math; function PBKDF2(pass, salt: ansistring; count, kLen: Integer; hash: TDCP_hashclass): ansistring; function CalcHMAC(message, key: string; hash: TDCP_hashclass): string;  implementation function RPad(x: string; c: Char; s: Integer): string; var   i: Integer; begin   Result := x;   if Length(x) < s then     for i := 1 to s-Length(x) do       Result := Result + c; end;  function XorBlock(s, x: ansistring): ansistring; inline; var   i: Integer; begin   SetLength(Result, Length(s));   for i := 1 to Length(s) do     Result[i] := Char(Byte(s[i]) xor Byte(x[i])); end;  function CalcDigest(text: string; dig: TDCP_hashclass): string; var   x: TDCP_hash; begin   x := dig.Create(nil);   try     x.Init;     x.UpdateStr(text);     SetLength(Result, x.GetHashSize div 8);     x.Final(Result[1]);   finally     x.Free;   end; end;  function CalcHMAC(message, key: string; hash: TDCP_hashclass): string; const   blocksize = 64; begin   // Definition RFC 2104   if Length(key) > blocksize then     key := CalcDigest(key, hash);   key := RPad(key, #0, blocksize);   Result := CalcDigest(XorBlock(key, RPad('', #$36, blocksize)) + message, hash);   Result := CalcDigest(XorBlock(key, RPad('', #$5c, blocksize)) + result, hash); end;  function PBKDF1(pass, salt: ansistring; count: Integer; hash: TDCP_hashclass): ansistring; var   i: Integer; begin   Result := pass+salt;   for i := 0 to count-1 do     Result := CalcDigest(Result, hash); end;  function PBKDF2(pass, salt: ansistring; count, kLen: Integer; hash: TDCP_hashclass): ansistring;    function IntX(i: Integer): ansistring; inline;   begin     Result := Char(i shr 24) + Char(i shr 16) + Char(i shr 8) + Char(i);   end;  var   D, I, J: Integer;   T, F, U: ansistring; begin   T := '';   D := Ceil(kLen / (hash.GetHashSize div 8));   for i := 1 to D do   begin     F := CalcHMAC(salt + IntX(i), pass, hash);     U := F;     for j := 2 to count do     begin       U := CalcHMAC(U, pass, hash);       F := XorBlock(F, U);     end;     T := T + F;   end;   Result := Copy(T, 1, kLen); end;  end.

Из бросающегося в глаза — обратите внимание, что в Pascal и его потомках отсутствует классическое разделение на заголовочные файлы и файлы собственно с кодом, в этом смысле модульная организация роднит его с Python, и отличает от C.

Однако от питонячьего модуля паскалевский отличается ещё и тем, что «снаружи» доступны только те функции/переменные, которые объявлены в секции interface. То есть по умолчанию внутри модуля ты можешь хоть «на ушах стоять» — снаружи никто не сможет вызвать твои внутренние API. Так устроен язык, а хорошо это или плохо — вопрос вкуса, поэтому оценки оставим в стороне (питонистам передают привет функции/методы, начинающиеся на «_» и «__»).

Заголовочная часть

Код, как обычно, под спойлером.

Заголовочная часть («шапка», header)
program devault; uses   math, sysutils, strutils, getopts, DCPcrypt2, DCPsha256, DCPrijndael, kdf;

Далее нам понадобится пара функций — hexlify и unhexlify (набросаны, конечно, «на скорую руку»). Они являются аналогами соответствующих функций Python — вторая возвращает строку из шестнадцатеричных представлений байтов входного аргумента, а первая — наоборот, переводит строку шестнадцатеричных кодов обратно в байты.

hexlify/unhexlify
function unhexlify(s:AnsiString):AnsiString; var i:integer;     tmpstr:AnsiString; begin   tmpstr:='';   for i:=0 to (length(s) div 2)-1 do     tmpstr:=tmpstr+char(Hex2Dec(Copy(s,i*2+1,2)));   unhexlify:=tmpstr; end;  function hexlify(s:AnsiString):AnsiString; var i:integer;     tmpstr:AnsiString; begin   tmpstr:='';   for i:=1 to (length(s)) do     tmpstr:=tmpstr+IntToHex(ord(s[i]),2);   hexlify:=tmpstr; end;

Назначение функций showbanner(), showlicense() и showhelp() очевидно из названий, поэтому я просто приведу их без комментариев.

showbanner() / showlicense() / showhelp()
showbanner()
procedure showbanner(); begin   WriteLn(stderr, 'DeVault v1.0');   Writeln(stderr, '(C) 2021, Sergey Pechenko. All rights reserved');   Writeln(stderr, 'Run with "-l" option to see license'); end;
showlicense()
procedure showlicense(); begin   WriteLn(stderr,'Redistribution and use in source and binary forms, with or without modification,');   WriteLn(stderr,'are permitted provided that the following conditions are met:');   WriteLn(stderr,'* Redistributions of source code must retain the above copyright notice, this');   WriteLn(stderr,'   list of conditions and the following disclaimer;');   WriteLn(stderr,'* Redistributions in binary form must reproduce the above copyright notice, ');   WriteLn(stderr,'   this list of conditions and the following disclaimer in the documentation');   WriteLn(stderr,'   and/or other materials provided with the distribution.');   WriteLn(stderr,'* Sergey Pechenko''s name may not be used to endorse or promote products');   WriteLn(stderr,'   derived from this software without specific prior written permission.');   WriteLn(stderr,'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"');   WriteLn(stderr,'AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,');   WriteLn(stderr,'THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE');   WriteLn(stderr,'ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE');   WriteLn(stderr,'FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES');   WriteLn(stderr,'(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;');   WriteLn(stderr,'LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON');   WriteLn(stderr,'ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT');   WriteLn(stderr,'(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,');   WriteLn(stderr,'EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.');   WriteLn(stderr,'Commercial license can be obtained from author'); end;
showhelp()
procedure showhelp(); begin   WriteLn(stderr,'Usage:');   WriteLn(stderr,Format('%s <-p password | -w vault_password_file> [-f secret_file]',[ParamStr(0)]));   WriteLn(stderr,#09'"password" is a text string which was used to encrypt your secured content');   WriteLn(stderr,#09'"vault_password_file" is a file with password');   WriteLn(stderr,#09'"secret_file" is a file with encrypted content');   WriteLn(stderr,'When "-f" argument is absent, stdin is read by default'); end;

Дальше объявляем переменные и константы, которые будут использоваться в коде. Привожу их здесь только для полноты текста, потому что комментировать тут особо нечего.

Переменные и константы
var secretfile, passwordfile, pass, salt, b_derived_key, b_key1, b_key2, b_iv,     hmac_new, cphrtxt, fullfile, header, tmpstr, hmac:Ansistring;     Cipher: TDCP_rijndael;     key, vector, data, crypt: RawByteString;     fulllist: TStringArray;     F: Text;     c: char;     opt_idx: LongInt;     options: array of TOption; const KEYLENGTH=32; // for AES256 const IV_LENGTH=128 div 8; const CONST_HEADER='$ANSIBLE_VAULT;1.1;AES256';

Код

Ну, почти код — всё ещё вспомогательная функция, которая в рантайме готовит массив записей для разбора параметров командной строки. Почему она здесь — потому что работает с переменными, объявленными в секции vars выше.

preparecliparams()
procedure preparecliparams(); begin   SetLength(options, 6);   with options[1] do     begin       name:='password';       has_arg:=Required_Argument;       flag:=nil;       value:=#0;     end;   with options[2] do     begin       name:='file';       has_arg:=Required_Argument;       flag:=nil;       value:=#0;     end;   with options[3] do     begin       name:='passwordfile';       has_arg:=Required_Argument;       flag:=nil;       value:=#0;     end;   with options[4] do     begin       name:='version';       has_arg:=No_Argument;       flag:=nil;       value:=#0;     end;   with options[5] do     begin       name:='license';       has_arg:=No_Argument;       flag:=nil;       value:=#0;     end;   with options[6] do     begin       name:='help';       has_arg:=No_Argument;       flag:=nil;       value:=#0;     end; end;

А вот теперь точно код самой утилиты:

Весь остальной код
begin   repeat     c:=getlongopts('p:f:w:lh?',@options[1],opt_idx);     case c of       'h','?' : begin showhelp(); halt(0); end;       'p' : pass:=optarg;       'f' : secretfile:=optarg;       'w' : passwordfile:=optarg;       'v' : begin showbanner(); halt(0); end;       'l' : begin showlicense(); halt(0); end;       ':' : writeln ('Error with opt : ',optopt); // not a mistake - defined in getops unit      end;   until c=endofoptions;   if pass = '' then // option -p not set     if passwordfile <> '' then       try         Assign(F,passwordfile);         Reset(F);         Readln(F,pass);         Close(F);       except         on E: EInOutError do         begin           Close(F);           writeln(stderr, 'Password not set and password file cannot be read, exiting');           halt(1);         end;       end     else       begin // options -p and -w are both not set           writeln(stderr, 'Password not set, password file not set, exiting');           showhelp();           halt(1);       end;   try     Assign(F,secretfile);     Reset(F);   except     on E: EInOutError do     begin       writeln(stderr, Format('File %s not found, exiting',[secretfile]));       halt(1);     end;   end;   readln(F,header);   if header<>CONST_HEADER then     begin       writeln(stderr, 'Header mismatch');       halt(1);     end;   fullfile:='';   while not EOF(F) do     begin     Readln(F,tmpstr);     fullfile:=fullfile+tmpstr;     end;   Close(F);   fulllist:=unhexlify(fullfile).Split([#10],3);   salt:=fulllist[0];   hmac:=fulllist[1];   cphrtxt:=fulllist[2];   salt:=unhexlify(salt);   cphrtxt:=unhexlify(cphrtxt);   b_derived_key:=PBKDF2(pass, salt, 10000, 2*32+16, TDCP_sha256);   b_key1:=Copy(b_derived_key,1,KEYLENGTH);   b_key2:=Copy(b_derived_key,KEYLENGTH+1,KEYLENGTH);   b_iv:=Copy(b_derived_key,KEYLENGTH*2+1,IV_LENGTH);   hmac_new:=lowercase(hexlify(CalcHMAC(cphrtxt, b_key2, TDCP_sha256)));   if hmac_new<>hmac then     begin     writeln(stderr, 'Digest mismatch - file has been tampered with, or an error has occured');     Halt(1);     end;   SetLength(data, Length(crypt));   Cipher := TDCP_rijndael.Create(nil);   try     Cipher.Init(b_key1[1], 256, @b_iv[1]);     Cipher.DecryptCTR(cphrtxt[1], data[1], Length(data));     Cipher.Burn;   finally     Cipher.Free;   end;   Writeln(data); end.

Дальше будет странная таблица, но, кажется, это — самый удобный способ рассказа об исходном коде.

Стр.

Назначение

2-13

разбор параметров командной строки с отображением нужных сообщений;

14-34

проверка наличия пароля в параметрах, при отсутствии — попытка прочесть пароль из файла, при невозможности — останавливаем работу;

35-44

попытка прочесть зашифрованный файл, указанный в параметрах;

Небольшой чит: по умолчанию имя файла (переменная secretfile) равно пустой строке; в этом случае вызов Assign(F, secretfile) в строке 36 свяжет переменную F с stdin

45-50

проверка наличия в файле того самого заголовка $ANSIBLE_VAULT;1.1;AES256;

51-57

читаем всё содержимое зашифрованного файла и закрываем его;

58-63

разбираем файл на части: «соль», дайджест, шифртекст — всё отдельно; при этом все три части нужно будет ещё раз прогнать через unhexlify (помните примечание в VaultAES256.encrypt?)

64-73

вычисление производного ключевого материала; разбиение его на части; расчёт дайджеста; проверка зашифрованного файла на корректность дайждеста;

74-83

подготовка буфера для расшифрованного текста; расшифровка; затирание ключей в памяти случайными данными; вывод расшифрованного содержимого в поток stdout

Интересная информация для питонистов

Кстати, вы же слышали, что в Python 3.10 наконец-то завезли оператор case (PEP-634)? Интересно, что его ввёл сам BDFL, и произошло это примерно через 14 лет после того, как по результатам опроса на PyCon 2007 первоначальный PEP-3103 был отвергнут.

Собственно, теперь всё на месте, осталось собрать:

[root@ansible devault]# time fpc devault.pas -Fudcpcrypt_2.1:dcpcrypt_2.1/Ciphers:dcpcrypt_2.1/Hashes -MOBJFPC

Здесь имейте в виду, что форматирование Хабра играет злую шутку — никакого разрыва строки после первого минуса нет.

Вывод компилятора
Free Pascal Compiler version 3.0.4 [2017/10/02] for x86_64 Copyright (c) 1993-2017 by Florian Klaempfl and others Target OS: Linux for x86-64 Compiling devault.pas Compiling ./dcpcrypt_2.1/DCPcrypt2.pas Compiling ./dcpcrypt_2.1/DCPbase64.pas Compiling ./dcpcrypt_2.1/Hashes/DCPsha256.pas Compiling ./dcpcrypt_2.1/DCPconst.pas Compiling ./dcpcrypt_2.1/Ciphers/DCPrijndael.pas Compiling ./dcpcrypt_2.1/DCPblockciphers.pas Compiling kdf.pas Linking devault /usr/bin/ld: warning: link.res contains output sections; did you forget -T? 3784 lines compiled, 0.5 sec  real    0m0.543s user    0m0.457s sys     0m0.084s

Вроде неплохо: 3,8 тысячи строк кода собраны до исполняемого файла за 0.6 сек. На выходе — статически связанный бинарник, которому для работы от системы требуется только ядро. Ну то есть для запуска достаточно просто скопировать этот бинарник в файловую систему — и всё. Кстати, я забыл указать его размер: 875К. Никаких зависимостей, компиляций по несколько минут и т.д.

Ах да, чуть не забыл самое интересное! Запускаем, предварительно сложив пароль в файл «.password»:

[root@ansible devault]# ./devault -w .password -f vaulted.yml --- collections: - name: community.general   scm: git   src: https://github.com/ansible-collections/community.general.git   version: 1.0.0

Вот такой нехитрый YaML я использовал в самом начале статьи для создания зашифрованного файла.

Исходный код для самостоятельного изучения можно взять здесь.

Хотите ещё Ansible? (осторожно, денежные вопросы!)

Теперь у вас есть возможность сделать пожертвование автору статьи — оно дополнительно замотивирует меня чаще выбираться на прогулку с чашкой кофе и булкой с корицей, чтобы отдохнуть перед написанием следующей статьи и обдумать её содержание.

Если же хотите систематизировать и углубить свои знания Ansible — я провожу тренинги по Ansible, пишите мне в Telegram.

ссылка на оригинал статьи https://habr.com/ru/post/554148/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *