Если вы не хотите платить за дополнительные частные репозитории или чтобы в ваших контейнерах копались все подряд, читайте дальше...


В Docker Hub и других реестрах контейнеров существуют ограничения на частные репозитории. Чтобы сохранить образы контейнеров недоступными для публичного скачивания, нужно заплатить, и чем больше частных репозиториев вам нужно, тем выше стоимость. Однако есть способ обойти это ограничение, давайте узнаем как.

TL;DR: Используйте зашифрованные образы.

В Podman есть функция, позволяющая шифровать образы контейнеров, делая их доступными только с определенным ключом. Это делает образы конфиденциальными, даже если они хранятся в общедоступном хранилище. Кроме того, для дополнительной безопасности можно зашифровать сам ключ с помощью пароля. Давайте посмотрим, как это сделать!

Для начала вам нужно установить Podman. Это альтернатива Docker и, на мой взгляд, более функциональная, так что ее стоит иметь в своей системе. Впрочем, не волнуйтесь - вы сможете запускать все с помощью того же Docker. Podman необходим для загрузки и скачивания образов, которые затем будут импортированы в Docker и запущены как обычно. Мы будем использовать как командную строку, так и Ansible для лучшей автоматизации.

Для использования всех возможностей Ansible нам понадобится коллекция Ansible Podman, которая предоставляет широчайшие возможности для автоматизации любых контейнеров и гибкие способы работы со всеми технологиями, связанными с контейнерами - собственно контейнерами, контейнерными сетями, томами, подами, образами, секретами, реестрами и многим другим. Она входит в официальный дистрибутив Ansible, поэтому вы можете использовать ее оттуда, но функции, которые нам нужны, являются новейшими, и скорее всего нам нужно будет установить ее из Ansible Galaxy:

ansible-galaxy collection install containers.podman

или, если вы хотите получить все самое лучшее и свежее, возьмите из мастера:

git clone https://github.com/containers/ansible-podman-collections
cd ansible-podman-collections
ansible-galaxy collection install --force .

Теперь мы готовы!

Генерация ключей шифрования

Сначала давайте изучим функциональность ключей шифрования. Нам нужно сгенерировать открытый и закрытый ключи для шифрования и дешифрования, они также могут быть защищены паролем. Для этого можно использовать образ или бинарник OpenSSL:

mkdir -p keys
openssl genrsa -out ./keys/container_private.pem 2048 && \
openssl rsa -in ./keys/container_private.pem -pubout -out ./keys/container_public.pem

Если у вас не установлен openssl, вы можете использовать контейнер nginx, который мы в любом случае будем использовать для постройки образов контейнера для тестов:

docker run -it -v $PWD/keys:/data:z docker.io/library/nginx sh -c "\
openssl genrsa -out /data/container_private.pem 2048 && \
openssl rsa -in /data/container_private.pem -pubout -out /data/container_public.pem"

Эта же команда работает с podman вместо docker:

podman run -it -v $PWD/keys:/data:z docker.io/library/nginx sh -c "\
openssl genrsa -out /data/container_private.pem 2048 && \
openssl rsa -in /data/container_private.pem -pubout -out /data/container_public.pem"

В Ansible вы можете сгенерировать ключи следующим образом:

- name: Generate SSL keys
  containers.podman.podman_container:
    name: temporary
    state: started
    image: docker.io/library/nginx
    rm: true
    volumes:
      - /tmp/keys:/data:z
    command:
      - bash
      - -c
      - >-
        openssl genrsa -out /data/container_private.pem 2048 &&
        openssl rsa -in /data/container_private.pem -pubout -out /data/container_public.pem

Ключи будут находиться в каталоге /tmp/keys, или вы можете указать свой собственный путь.

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

Далее давайте соберем и зашифруем контейнер.
Создайте Dockerfile:

cat <<EOF> Dockerfile
FROM docker.io/library/nginx
COPY index.html /usr/share/nginx/html/index.html
EOF

echo "test passed" > index.html
podman build . -t docker.io/sshnaidm/something:open

Мы используем Podman для сборки, потому что загрузка зашифрованного образа в реестр может быть выполнено только с помощью инструмента Podman. Однако конвертирование образов между podman и docker очень простое:

docker save docker.io/sshnaidm/something:open | podman load
# или так
podman image push docker.io/sshnaidm/something:close docker-daemon:docker.io/sshnaidm/something:closed

В Ansible создайте, постройте и выложите образ в репозиторий:

- name: Create Dockerfile
  copy:
    content: |
      FROM docker.io/library/nginx
      COPY index.html /usr/share/nginx/html/index.html
    dest: "{{ playbook_dir }}/Dockerfile"

- name: Create index.html
  copy:
    content: "<h1>test passed!</h1>"
    dest: "{{ playbook_dir }}/index.html"

- name: Build and push the image
  containers.podman.podman_image:
    name: docker.io/sshnaidm/something
    tag: closed
    path: "{{ playbook_dir }}"
    state: build
    build:
      file: "{{ playbook_dir }}/Dockerfile"
    push: true
    push_args:
      dest: docker.io/sshnaidm/something:closed
      extra_args: --encryption-key jwe:/absolute/path/to/keys/container_public.pem

Теггирование и выгрузка с шифрованием

Тегировать и выгрузить образ с помощью podman с шифрованием JWE:

cd keys/
podman tag docker.io/sshnaidm/something:open docker.io/sshnaidm/something:closed
podman push --encryption-key jwe:container_public.pem docker.io/sshnaidm/something:closed

Мы загрузили наш зашифрованный образ в Docker Hub. Теперь попробуйте скачать его с помощью docker или podman:

$ podman pull docker.io/sshnaidm/something:closed
Trying to pull docker.io/sshnaidm/something:closed...
Getting image source signatures
Copying blob 37745a742023 [--------------------------------------] 0.0b / 1.4KiB | 0.0 b/s
Copying blob 37745a742023 done   | 
[skipped...]
Error: writing blob: adding layer with blob "sha256:69458e593f618c768281ca90b8ed5eb78df30df39902d948f894fd8c7e80906f": processing tar file(archive/tar: invalid tar header): exit status 1

Не-а, не получается :)

С помощью docker:

$ docker pull docker.io/sshnaidm/something:closed
closed: Pulling from sshnaidm/something
69458e593f61: Extracting [==================================================>]  32.74MB/32.74MB
1a1e276c1a3b: Download complete 
[skipped...]
failed to register layer: archive/tar: invalid tar header

Не-а, не получается :)

Невозможно скачать и использовать образ без ключа. Только имея ключ можно загрузить его, что фактически делает его приватным образом контейнера в публичном репозитории!

Скачивание и запуск зашифрованного образа

Скачать и использовать зашифрованный образ:

cd keys/
podman pull --decryption-key container_private.pem docker.io/sshnaidm/something:closed
# Load it to Docker if you can't use Podman:
podman save docker.io/sshnaidm/something:closed | docker load
docker run -d --name webserver -p 8888:80 docker.io/sshnaidm/something:closed
curl localhost:8888
$ test passed

С помощью Ansible вы можете скачать и запустить его в одной таске:

- name: Run container
  containers.podman.podman_container:
    name: webserver
    image: docker.io/sshnaidm/something:closed
    state: started
    decryption_key: /full/path/to/keys/container_private.pem
    publish:
      - 8888:80

И наконец:

curl localhost:8888
$ test passed

Полезные Bash-скрипты и Ansible Playbook

Для удобства здесь приведены bash скрипты для клиента и сервера, а также плейбук Ansible для сборки и распространения образа из публичного реестра, как если бы он был приватным:

На клиенте создайте образ, сгенерируйте ключи и отправьте его в зашифрованном виде:

# On client, build image, generate keys, and push encrypted
openssl genrsa -out container_private.pem 2048
openssl rsa -in container_private.pem -pubout -out ./keys/container_public.pem

cat <<EOF> Dockerfile
FROM docker.io/library/nginx
COPY index.html /usr/share/nginx/html/index.html
EOF

echo "test passed" > index.html

podman build . -t docker.io/sshnaidm/something:production
podman push --encryption-key jwe:container_public.pem docker.io/sshnaidm/something:production

Скопируйте файл container_private.pem на сервер и выполните там:

podman pull --decryption-key container_private.pem docker.io/sshnaidm/something:production
# In case of Docker:
podman save docker.io/sshnaidm/something:production | docker load
docker run -d --name webserver -p 8888:80 docker.io/sshnaidm/something:production
# In case of Podman:
podman run -d --name webserver -p 8888:80 docker.io/sshnaidm/something:production

Или все это вместе выполните одним Ansible плейбуком:

- hosts: client
  tasks:

    - name: Generate SSL keys
      containers.podman.podman_container:
        name: temporary
        state: started
        image: docker.io/library/nginx
        rm: true
        volumes:
          - /tmp/keys:/data:z
        command:
          - bash
          - -c
          - >-
            openssl genrsa -out

 /data/container_private.pem 2048 &&
            openssl rsa -in /data/container_private.pem -pubout -out /data/container_public.pem

    - name: Create Dockerfile
      copy:
        content: |
          FROM docker.io/library/nginx
          COPY index.html /usr/share/nginx/html/index.html
        dest: "{{ playbook_dir }}/Dockerfile"

    - name: Create index.html
      copy:
        content: "<h1>test passed!</h1>"
        dest: "{{ playbook_dir }}/index.html"

    - name: Build and push image
      containers.podman.podman_image:
        name: docker.io/sshnaidm/something
        tag: production
        path: "{{ playbook_dir }}"
        state: build
        build:
          file: "{{ playbook_dir }}/Dockerfile"
        push: true
        push_args:
          dest: docker.io/sshnaidm/something:production
          extra_args: --encryption-key jwe:/absolute/path/to/keys/container_public.pem

- hosts: server
  tasks:

    - name: Distribute keys
      copy:
        src: /full/path/to/keys/container_private.pem
        dest: /full/path/to/keys/container_private.pem

    - name: Run container with Podman
      containers.podman.podman_container:
        name: webserver
        image: docker.io/sshnaidm/something:production
        state: started
        decryption_key: /full/path/to/keys/container_private.pem
        publish:
          - 8888:80

Сэкономьте на публичных репозиториях и сделайте свои образы контейнеров более защищенными!

П.С. Когда это писалось докерхаб был ещё доступен, поэтому основной фокус теперь все таки на шифровании, особенно если пользоваться различными непонятными реестрами и ненадежными сервисами.

Комментарии (3)


  1. IhnatKlimchuk
    04.06.2024 23:46
    +2

    Чего только не сделаешь ради $5 в месяц...


  1. minaevmike
    04.06.2024 23:46

    Следующий шаг хранить шифрованные файлы на гитхабе?


    1. ArtRoman
      04.06.2024 23:46

      Сразу уж, переходим на хранение в Телеграме )