Блог

Если поломался pip на macOS

Например, ругается на zlib, хотя он установлен:

      File "/usr/local/Cellar/python@3.7/3.7.9_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/distutils/command/build_ext.py", line 340, in run
        self.build_extensions()
      File "/private/var/folders/3r/04yr8dd57896ccmx1f86ptnr0000gn/T/pip-install-lmm5ssql/Pillow/setup.py", line 694, in build_extensions
        raise RequiredDependencyException(f)
    __main__.RequiredDependencyException: zlib

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/3r/04yr8dd57896ccmx1f86ptnr0000gn/T/pip-install-lmm5ssql/Pillow/setup.py", line 918, in <module>
        raise RequiredDependencyException(msg)
    __main__.RequiredDependencyException:

    The headers or library files could not be found for zlib,
    a required dependency when compiling Pillow from source.

    Please see the install instructions at:
       https://pillow.readthedocs.io/en/latest/installation.html

То можно сделать следующее:

python3 -m pip install --user --force-reinstall pip

После этого у меня все установилось. Если не поможет - то можно понизить версию python через brew и не забывайте про brew doctor.

python

Как делать Mirror Repository (зеркало) на GitLab?

Шаг 1. Создаем репозиторий, который будет зеркалом, например https://gitlab.com/crusat/php-mirror - никаких веток и прочего туда не создаем.

Шаг 2. Заходим в репозиторий, с которого будем делать зеркало (т.е. основной репозиторий), например https://gitlab.com/crusat/php

Шаг 3. В меню находим Settings -> Repository, внутри раскрываем “Mirroring repositories”.

Шаг 4. Заполняем данные

Git repository URL - https://crusat:password@gitlab.com/crusat/php-mirror

Где:
crusat - ваш логин
password - ваш пароль (не бойтесь, после добавления они будут скрыты звездочками).

Password - еще раз вводим ваш пароль.

Жмем кнопку Mirror Repository.

Шаг 5. Должно появиться зеркало. Чтобы проверить - справа жмем кнопку со стрелочками для обновления данных. После этого можно перезагрузить страницу и посмотреть, что данные были отправлены (см. “Last successful update”). После этого идем смотреть в репозиторий https://gitlab.com/crusat/php-mirror, что все действительно успешно обновилось.

gitlab

Меняем шелл с zsh обратно на bash в macos

Все просто, достаточно этой команды:

chsh -s /bin/bash
bash

Как записать структуру данных из Alamofire?

Допустим есть такая структура:

struct ConfigSerializer: Decodable {
    var phone: String = ""
    var facebookUrl: String = ""
    var telegramUrl: String = ""
    var instagramUrl: String = ""
}

И такой запрос:

AF.request("http://someurl/api/config/").response { response in
    guard let data = response.data else { return }
    do {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        let result = try decoder.decode(ConfigSerializer.self, from: data)
        print(result)
    } catch let error {
        print("\(error.localizedDescription)")
    }
}

И при этом если данные будут следующего формата:

{
    "phone": "+7 999 999 99 99",
    "facebook_url": "https://fb.com",
    "telegram_url": "https://t.me",
    "instagram_url": "https://instagram.com"
}

то все запишется корректно (обратите внимание на facebook_url и facebookUrl).

Все.

Если же у вас встречается проблема “The data couldn’t be read because it is missing” - это значит, что данные не совпадают с ответом. Достаточно сделать опциональными переменные в структуре - и вы увидите, где ошибка.

swift

Как понять, шрифт установился или нет в SwiftUI?

Для какой-нибудь вьюшки, например, ContentView добавляем следующее, запускаем и смотрим в консоль:

.onAppear {
    for family in UIFont.familyNames.sorted() {
        let names = UIFont.fontNames(forFamilyName: family)
        print("Family: \(family) Font names: \(names)")
    }
}
swift swiftui

Чиним вход через ЕСИА с ГОСТ-2012

Сухая выдержка без мата “ЧТО ДЕЛАТЬ”.

Предполагается, что вы уже получили папку (например, “MySite.001”), содержающую файлы header.key, masks.key, masks2.key, name.key, primary.key, primary2.key.

Все это делаем под Windows 10.

  1. Покупаем софт у ЛИССИ P12FromGostCSP http://soft.lissi.ru/ls_product/utils/p12fromcsp/ (на момент написания статьи стоит 2990 руб)
  2. Ждем письмо, при необходимости скорости пишем им в саппорт, почта саппорта указана при покупке.
  3. Присылают письмо с лицензией и ссылкой на скачивание P12FromGostCSP и скачиваете его. Пока не запускаем.
  4. Копируете папку “MySite.001” на флешку (любую).
  5. Устанавливаем триальный КриптоПРО CSP.
  6. Запускаем “Инструменты КриптоПРО”.
  7. Переходим во вкладку Контейнеры, он подгрузит контейнер “MySite.001” с флешки (у него будет уже нормальное имя), жмем Импортировать. Пароль обычно 12345678.
  8. Запускаем P12FromGostCSP, он подгрузит ключ из КриптоПРО. Жмем Экспортировать, выбираем папку.
  9. Все, у вас есть корректный PFX файл, который содержит сертификат и закрытый ключ.
  10. Теперь устанавливайте docker (если его еще нет).
  11. Из директории где лежит p12.pfx выполняете команду (потребуется ввести пароль): docker run –rm -v `pwd`:`pwd` -w `pwd` -it rnix/openssl-gost openssl pkcs12 -in p12.pfx -nocerts -out key.pem -nodes
  12. В директории должен появиться закрытый ключ key.pem.
  13. Далее выполняете из той же директории команду: docker run –rm -v `pwd`:`pwd`-w `pwd` -it rnix/openssl-gost openssl pkcs12 -in p12.pfx -nokeys -out cert.pem
  14. Появится файл сертификата cert.pem.
  15. Далее необходимо сгенерировать публичный ключ из сертификата (необходим для проверки подписи): docker run –rm -v `pwd`:`pwd` -w `pwd` rnix/openssl-gost openssl x509 -pubkey -noout -in cert.pem > pubkey.pem
  16. Готово, эти ключи понимает openssl с поддержкой ГОСТ-2012. Рекомендую использовать сборку php-fpm отсюда: https://github.com/rnixik/docker-openssl-gost
  17. Также потребуется применить эти файлы к вашему коду. Тут уже сами - у каждого свой код.

Для проверки используйте следующий код.

Подпись

Создайте файл с любым текстовым содержимым, например file.txt:

Some Data

Далее необходимо подписать файл (выходом будет бинарный формат, поэтому делаем base64):

docker run --rm -v `pwd`:`pwd` -w `pwd` rnix/openssl-gost openssl dgst -md_gost12_256 -sign key.pem file.txt | base64 > signed.txt

Проверяйте, что файл появился и у него есть содержимое.

Проверка

Далее, раскодируем из base64 в бинарный формат:

base64 -D -in signed.txt > signed.bin

И проверяем подпись, сравнивая с исходным файлом и подписью:

docker run --rm -v `pwd`:`pwd` -w `pwd` rnix/openssl-gost openssl dgst -md_gost12_256 -verify pubkey.pem -signature signed.bin file.txt

////////////////

Также обратите внимание на этот репозиторий (сделал REST-сервис для подписи и проверки подписи): https://github.com/crusat/openssl-gost-rest

////////////////

Источник: боль, кровь и мои слезы.

openssl

Разбираемся в терминологии шифрования

Если нужна поддержка ГОСТ-2012 в openssl, то используйте LibreSSL - он подменяет openssl и в нем есть.

Установка:

brew install libressl

Или апгрейд:

brew upgrade libressl

Проверяем:

$ openssl ciphers | grep GOST
ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:GOST2012256-GOST89-GOST89:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:GOST2001-GOST89-GOST89:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA256:CAMELLIA256-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA256:CAMELLIA128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:RC4-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA

PKCS#12 - один из стандартов семейства Public-Key Cryptography Standards (PKCS), опубликованных RSA Laboratories. Он определяет формат файла, используемый для хранения и/или транспортировки закрытого ключа (en:Private key), цепочки доверия от сертификата пользователя до корневого сертификата удостоверяющего центра и списка отзыва сертификатов (CRL). Источник: https://ru.wikipedia.org/wiki/PKCS12

PKCS - В криптографии PKCS относится к группе Public Key Cryptography Standards (Стандарты криптографии с открытым ключом), разработанным и опубликованным RSA Security. Таблица https://ru.wikipedia.org/wiki/PKCS

ASN.1 - (англ. Abstract Syntax Notation One) — в области телекоммуникаций и компьютерных сетей язык для описания абстрактного синтаксиса данных (ASN.1), используемый OSI. Стандарт записи, описывающий структуры данных для представления, кодирования, передачи и декодирования данных. Он обеспечивает набор формальных правил для описания структуры объектов, которые не зависят от конкретной машины. Источник: https://ru.wikipedia.org/wiki/ASN.1

X.509 - стандарт ITU-T для инфраструктуры открытого ключа (англ. Public key infrastructure, PKI) и инфраструктуры управления привилегиями (англ. Privilege Management Infrastructure). X.509 определяет стандартные форматы данных и процедуры распределения открытых ключей с помощью соответствующих сертификатов с цифровыми подписями. Источник: https://ru.wikipedia.org/wiki/X.509

Общеупотребительные расширения файлов сертификатов
.CER — сертификат, или набор сертификатов, закодированных по стандарту CER.
.DER — сертификат, закодированный по стандарту DER.
.PEM — PEM-сертификат, закодированный по стандарту DER и использующий Base64 и помещенный между «----- BEGIN CERTIFICATE -----» и «----- END CERTIFICATE -----».
.P7B, .P7C — PKCS #7 содержит несколько сертификатов или CRL.
.P12 — PKCS #12 содержит блок, хранящий одновременно и закрытый ключ, и сертификат (в зашифрованном виде).
.PFX — PFX, предшественник PKCS #12, также содержит блок закрытого ключа и сертификат.

Источник: https://ru.wikipedia.org/wiki/X.509

openssl

Сменные цветовые темы на SwiftUI

Привет всем!

Долго молчу, потому что изучаю swift.

Написал gist сменных тем для приложения на SwiftUI - выбранная тема меняет интерфейс сразу и после перезапуска остается такой же, которая была выбрана (используется UserDefaults).

Исходник тут тоже положу, но за последней версией лучше идти на гитхаб https://gist.github.com/crusat/177b5bc7d206f466bd8acbd66708588c

Пример можно посмотреть здесь: https://crusat.ru/media/uploads/2020/05/24/RPReplay_Final1590305369.MP4

Файл ContentView.swift:

import SwiftUI

struct Theme: Hashable {
    var colorPrimary: Color = Color.primary
    var name: String? = nil
    var publicName: String = "System"
}

var themes: [Theme] = [
    Theme(colorPrimary: Color.primary, name: nil, publicName: "System"),
    Theme(colorPrimary: Color.purple, name: "purple", publicName: "Purple"),
    Theme(colorPrimary: Color.orange, name: "orange", publicName: "Orange"),
    Theme(colorPrimary: Color.red, name: "red", publicName: "Red"),
    Theme(colorPrimary: Color.green, name: "green", publicName: "Green"),
    Theme(colorPrimary: Color.blue, name: "blue", publicName: "Blue"),
    Theme(colorPrimary: Color.pink, name: "pink", publicName: "Pink"),
    Theme(colorPrimary: Color.yellow, name: "yellow", publicName: "Yellow"),
]

func getTheme(themeName: String?) -> Theme {
    if themeName != nil {
        for theme in themes {
            if themeName! == theme.name {
                return theme
            }
        }
    }
    return themes[0]
}

func getCurrentTheme() -> Theme {
    let currentThemeName = UserDefaults.standard.string(forKey: "themeName")
    return getTheme(themeName: currentThemeName)
}

struct ChangeThemeButton: View {
    @Binding var currentThemeName: String?
    var colorName: String
    var themeName: String?

    var body: some View {
        HStack {
            Button(action: {
                UserDefaults.standard.set(self.themeName, forKey: "themeName")
                self.currentThemeName = UserDefaults.standard.string(forKey: "themeName")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "themeUpdated"), object: self)
            }) {
                HStack {
                    Circle()
                    .fill(getTheme(themeName: themeName).colorPrimary)
                    .frame(width: 25, height: 25)

                    Text(self.colorName)

                    if self.currentThemeName == themeName {
                        Image(systemName: "checkmark")
                    }
                }
            }
        }
    }
}

struct SelectThemeView: View {
    @State var currentThemeName = UserDefaults.standard.string(forKey: "themeName")

    var body: some View {
        List {
            ForEach(themes, id: \.self) { theme in
                ChangeThemeButton(currentThemeName: self.$currentThemeName, colorName: theme.publicName, themeName: theme.name)
            }
        }
        .navigationBarTitle("Тема").onAppear {
            self.currentThemeName = UserDefaults.standard.string(forKey: "themeName")
        }
    }
}

struct SettingsView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: SelectThemeView()) {
                    Text("Change theme")
                }
            }
            .navigationBarTitle("Settings")
        }
    }
}

struct ContentView: View {
  @State var currentTheme: Theme = getCurrentTheme()
  @State private var selection = 0

  var body: some View {
    HStack {
      TabView(selection: $selection){
        Text("Hello, world!")
          .tabItem {
            VStack {
                Image(systemName: "gear")
                Text("Settings")
            }
          }
          .tag(0)
        SettingsView()
          .tabItem {
            VStack {
                Image(systemName: "gear")
                Text("Settings")
            }
          }
          .tag(1)
      }
    }
    .accentColor(currentTheme.colorPrimary)
    .onAppear {
        NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "themeUpdated"), object: nil , queue: nil) { (notification) in
            DispatchQueue.main.async {
                self.currentTheme = getCurrentTheme()
            }
        }
    }
  }
}
swift swiftui

HTTPS на localhost

Когда на локалхосте нужен https можно сделать следующее.

Выполняем команду:

openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

Копируем файлы localhost.crt и localhost.key в папку приложения и подключаем.

Также, не забывайте, что может потребоваться добавить сертификат в кейчеин (на маке просто делаем двойной клик по localhost.crt).

Также, может потребоваться перейти по ссылке на сертификат (если допустим подключается апи к другому хосту), то просто открывайте сайт по тому урлу и соглашайтесь через “Дополнительно” и “Перейти на сайт”.

Например, для nodejs код будет выглядеть примерно так (index.js):

const https = require('https');

const HTTPS_OPTIONS = Object.freeze({
    cert: fs.readFileSync('./localhost.crt'),
    key: fs.readFileSync('./localhost.key')
});

const httpsServer = https.createServer(HTTPS_OPTIONS);

Источник: https://letsencrypt.org/ru/docs/certificates-for-localhost/

devops

Docker показывает IP контейнера, а не пользователя

Это в первую очередь касается PHP проектов.

Делаем две вещи. Первое - для reverse proxy (если используется) у nginx дописываем в раздел следующее:

server {
    ...
    location / {
        ...
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        ...
    }
    ...
}

И рестартим nginx.

После этого у нас для переменной $_SERVER['HTTP_X_REAL_IP'] будет правильный IP-адрес клиента, а для $_SERVER['REMOTE_ADDR'], скорее всего, останется неправильной.

Второе, что делаем - надо прокинуть правильную переменную в fastcgi_param. Идем во внутренний nginx и исправляем конфиг:

server {
    ...
    location ~ \.php$ {
        ...
        fastcgi_param REMOTE_ADDR $http_x_real_ip;
        ...
    }
    ...
}
devops nginx php