Анклавы AWS Nitro являются изолированными средами выполнения. Они позволяют нам вычислять конфиденциальные и/или частные данные.

В этом блоге мы рассмотрим, как запустить HTTP-сервер в Go на Nitro Enclave. Окончательный код доступен на Гитхаб.


Создание сервера

Мы начнем с HTTP-сервера, работающего за пределами анклава.

package main

import (
    "io"
    "log"
    "net/http"
)

const PORT = ":8888"

func main() {
    handler := func(w http.ResponseWriter, req *http.Request) {
        io.WriteString(w, "Hello, regular server!\\n")
    }

    http.HandleFunc("https://dev.to/", handler)
    log.Println("listening on", PORT)
    log.Fatal(http.ListenAndServe(PORT, nil))
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Мы запустим и протестируем наш анклав в Docker. Это будет самый простой способ позже запустить наш код в анклаве. Мы можем использовать следующий Dockerfile.

FROM golang:1.18-alpine

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY *.go ./

RUN go build -o /enclave-server

EXPOSE 8888

CMD [ "/enclave-server" ]
Войти в полноэкранный режим

Выйти из полноэкранного режима


Настройка инстанса EC2 с поддержкой анклава

Далее давайте запустим наш сервер внутри анклава. Для этого нам нужен инстанс EC2 с поддержкой Nitro Enclave. Вы можете следить за инструкции от АВС получить изображение и установка интерфейса командной строки Nitro Enclaves


Создание нашего файла образа анклава (EIF)

Интерфейс командной строки Nitro может превращать файлы Dockerfile в файлы образов анклава. Затем мы передадим файл образа Enclave команде run-enclave.

Сначала мы создадим наш образ докера

docker build -t hello-nitro .
Войти в полноэкранный режим

Выйти из полноэкранного режима

Затем создайте наш EIF

nitro-cli build-enclave --docker-uri hello-nitro --output-file hello-nitro.eif
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вы должны увидеть вывод, который выглядит примерно так

Start building the Enclave Image...
Using the locally available Docker image...
Enclave Image successfully created.
{
  "Measurements": {
    "HashAlgorithm": "Sha384 { ... }",
    "PCR0": "bf9b5743dc00c63972e7cc06da44bda4a18e40a80173c5a5203651853509f37c1e4c6794fa028e29e60081b007175b09",
    "PCR1": "bcdf05fefccaa8e55bf2c8d6dee9e79bbff31e34bf28a99aa19e6b29c37ee80b214a414b7607236edf26fcb78654e63f",
    "PCR2": "ad8dfc09b5415f3a1d5586d7de878e87785669371a590aa4e392e14f2fc70aeba5bd53d04ef0043447e9ad04cd0c4893"
  }
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Затем мы запускаем наш EIF

nitro-cli run-enclave --eif-path hello-nitro.eif --memory 2000 --cpu-count 2 --enclave-cid 16 --debug-mode
Войти в полноэкранный режим

Выйти из полноэкранного режима

Обратите внимание, что сейчас мы будем работать в режиме отладки. Режим отладки позволяет нам видеть вывод консоли. Анклав небезопасен, когда мы используем режим отладки, но он очень полезен при прототипировании. Мы также запустили анклав с контекстным идентификатором (CID) 16. Это не обязательно, но гарантирует, что вы сможете скопировать и вставить некоторые команды, которые ссылаются на этот CID ниже.

Мы видим, что наш анклав работает с describe-enclaves команда

$ nitro-cli describe-enclaves
[
  {
    "EnclaveName": "hello-nitro",
    "EnclaveID": "i-02d0ff88a0e47b51d-enc183d6a008ea4b9a",
    "ProcessID": 26734,
    "EnclaveCID": 16,
    "NumberOfCPUs": 2,
    "CPUIDs": [
      1,
      17
    ],
    "MemoryMiB": 2048,
    "State": "RUNNING",
    "Flags": "DEBUG_MODE",
    "Measurements": {
      "HashAlgorithm": "Sha384 { ... }",
      "PCR0": "bf9b5743dc00c63972e7cc06da44bda4a18e40a80173c5a5203651853509f37c1e4c6794fa028e29e60081b007175b09",
      "PCR1": "bcdf05fefccaa8e55bf2c8d6dee9e79bbff31e34bf28a99aa19e6b29c37ee80b214a414b7607236edf26fcb78654e63f",
      "PCR2": "ad8dfc09b5415f3a1d5586d7de878e87785669371a590aa4e392e14f2fc70aeba5bd53d04ef0043447e9ad04cd0c4893"
    }
  }
]
Войти в полноэкранный режим

Выйти из полноэкранного режима

Обратите внимание, что выходные данные PCR 0, 1 и 2 такие же, как и при построении анклава. Эти значения можно использовать во время Аттестация.

Наконец, поскольку мы используем режим отладки, мы можем увидеть консольный вывод нашего анклава с помощью консольной команды.

$ nitro-cli console --enclave-name hello-enclave
<enclave setup output redacted>
2022/10/14 13:12:41 listening on :8888
Войти в полноэкранный режим

Выйти из полноэкранного режима

Кажется достаточно простым, не так ли? Однако у нас нет возможности запросить эту конечную точку. Единственный способ подключиться к работающему анклаву — через всок. Большой! Как мы это делаем?

Чтобы подключиться к нашему HTTP-серверу, мы внесем две модификации в нашу настройку:

  • Наш сервер go должен прослушивать адрес vsock. По умолчанию http.ListenAndServe будет прослушивать предоставленный сетевой адрес TCP, который не будет работать внутри анклава.
  • Мы хотели бы иметь возможность делать запросы к нашему серверу стандартными инструментами. До сих пор мы использовали cURL, но нам хотелось бы, чтобы любой http-клиент работал без изменений. Мы будем использовать[сокат[([socat[(https://linux.die.net/man/1/сокат) для поддержания этой совместимости.


Слушай на всоке

Мы можем использовать чтобы получить реализацию vsock для net.Listener, которую мы можем передать в http.Serve.

package main

import (
    "io"
    "log"
    "net/http"

    "github.com/mdlayher/vsock"
)

func main() {
    handler := func(w http.ResponseWriter, req *http.Request) {
        io.WriteString(w, "Hello, Enclave!\\n")
    }

    listener, err := vsock.Listen(8888, nil)
    if err != nil {
        log.Fatal(err)
    }

    log.Fatal(http.Serve(listener, http.HandlerFunc(handler)))
}
Войти в полноэкранный режим

Выйти из полноэкранного режима


Прокси HTTP для vsock

Используя socat, мы можем позволить клиентам продолжать делать HTTP-запросы, а socat будет проксировать их на vsock для нас. Изображение доступно на Докер Хаб.

docker run -d -p 8888:8888 --name socat alpine/socat tcp-listen:8888,fork,reuseaddr vsock-connect:16:8888
Войти в полноэкранный режим

Выйти из полноэкранного режима

Если вы не знакомы с socat, эта команда говорит, что нужно прослушивать tcp-соединения на порту 8888 и перенаправлять их на vsock. Параметр fork делает это в новом дочернем процессе, в то время как родитель возвращается к ожиданию новых подключений (полезно, если вы хотите обрабатывать параллельные запросы). reuseaddr позволяет другим сокетам связываться с адресом, если его части уже используются socat.

Второй аргумент описывает, как подключиться к vsock. 8888 совпадает с портом, который мы прослушивали на нашем http-сервере. 16 — это идентификатор контекста, который идентифицирует анклав (мы явно установили это выше).

Вы можете найти адрес своего сервера socat, проверив сеть докеров моста. Мой был на 172.17.0.2.

Описание изображения

Вот и все! теперь мы можем запросить в наш анклав

$ curl 172.17.0.2:9090
Hello, Enclave!
Войти в полноэкранный режим

Выйти из полноэкранного режима


Заключение и следующие шаги

Теперь у нас есть HTTP-сервер, работающий внутри анклава. Теперь мы можем размещать конфиденциальные рабочие нагрузки за этими конечными точками HTTP.

В будущих блогах мы рассмотрим запуск нашего сервера по протоколу HTTPS, чтобы у вызывающих абонентов было безопасное соединение непосредственно с анклавом. Мы также рассмотрим возможность запуска инстансов EC2 с поддержкой анклава в Kubernetes для более готовой среды.


Бессовестная вилка

Спасибо за чтение! Я работаю полный рабочий день в анклавах в https://capeprivacy.com/. Мы пытаемся сделать анклавы доступными и удобными для всех разработчиков и специалистов по данным. Cape Privacy в настоящее время находится в стадии открытого бета-тестирования. Я хотел бы услышать любые отзывы, комментарии или вопросы, которые у вас есть по продукту. Проверьте это!