Руководство прикладного разработчика#

Термины и определения#

Сокращение

Полное наименование

Ceph

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

HTTP

HyperText Transfer Protocol - протокол передачи гипертекста

S3

Облачное хранилище данных

RPC

Remote Procedure Call - система удаленного вызова процедур

АС

Автоматизированная система

ReplicaSet

логическое объединение нескольких SDS кластеров в рамках выполняемой операции

Context UUID

уникальный идентификатор контекста в рамках приложения

Системные требования#

Системное программное обеспечение описано в разделе «Системные требования» документа «Руководство по установке».

Подключение и конфигурирование#

Все ниже указанные манипуляции необходимо производить в файле конфигурации приложения application.yaml Значения необходимо переопределить в соответствии с вашими потребностями и окружением

Определение порта и точки входа в приложение#

server:
  port: <value> # рекомендуемое значение: 8080

spring:
  webflux:
    base-path: <value> # рекомендуемое значение: /

Конфигурирование FSGW#

platform.file-storage-gateway:
  version: <версия> # текущая версия: 2.0
  swagger-ui: # опциональный раздел
    enabled: <enabled> # true / false (по умолчанию, false)
  storage.s3:
    base-path: <path> # базовый path, запросы на который ожидает шлюз (по умолчанию: s3)
    clusters: <описание cluster см. в соответствующем разделе>
    replica-sets: <описание replica set см. в соответствующем разделе>

Добавление кластеров S3 для подключения#

<cluster alias>: # любой удобный для вас alias (будет указываться в рамках replica-set - см. ниже)
  endpoint:
    scheme: <scheme> # http / https
    host: <host>
    port: <port>
  region: <region> # в случае Ceph ни на что не влияет
  users: <описание user см. в соответствующем разделе>
  s3-configuration: # опциональный раздел параметров
    <описание s3 configuration см. в соответствующем разделе>
  http-client-configuration: # опциональный раздел параметров
    <описание http-client-configuration см. в соответствующем разделе>

Добавление пользователей для подключения к S3 кластерам#

Без использования Vault KV совместимого хранилища#
<user alias>: # любой удобный для вас alias (будет указываться в рамках replica-set - см. ниже)
  access-key-id: ${environment parameter name for access key id}
  secret-access-key: ${environment parameter name for secret access key}
С использованием Vault KV совместимого хранилища#
<user alias>: # любой удобный для вас alias (будет указываться в рамках replica-set - см. ниже)
  access-key-id:
    environment-variable-name: access-key
  secret-access-key:
    environment-variable-name: secret-key

access-key и secret-key - ключи из Vault KV совместимого хранилища, по которым будут проинициализированы поля

Параметры работы с S3 кластером#

s3-configuration: # опциональный раздел параметров
  access-style: <value> # virtual-hosted–style / path-style (по умолчанию, path-style)
  chunked-encoding: <value> # true / false (по умолчанию, false)
  checksum-validation: <value> # true / false (по умолчанию, false)

Параметры клиента для подключения к S3 кластерам#

http-client-configuration: # опциональный раздел параметров
  max-concurrency: <value> # по умолчанию: 128
  connection-timeout: <value> # по умолчанию: 30s
  read-timeout: <value> # по умолчанию: 30s
  write-timeout: <value> # по умолчанию: 30s

Все таймауты указываются в стиле "simple" duration formatting.

Параметры ReplicaSet#

<replica set alias>: # любой удобный для вас alias (будет указываться в опциональном заголовке s3 запроса: x-amz-replica-set)
  default: <value> # true / false (опциональный параметр, по умолчанию: false)
  clusters:
    <cluster 1 alias>: # один из тех, что уже был объявлен выше
      user: <user 1 alias> # один из тех, что уже был объявлен выше
    <cluster 2 alias>: # один из тех, что уже был объявлен выше
      user: <user 2 alias> # один из тех, что уже был объявлен выше
  sync-replication:
    min-success-replication-factor: <value> # значение на отрезке: [1, clusters.size]
  cipher: # опциональный раздел параметров
    <описание cipher см. в соответствующем разделе>
  signature: # опциональный раздел параметров
    <описание signature см. в соответствующем разделе>

Не более чем один replica-set может иметь признак default: true.

Параметры шифрования (опционально)#

cipher: # опциональный раздел параметров (ниже указаны значения, используемые по умолчанию)
  block-cipher-suites: # криптонаборы, базирующиеся на блочных алгоритмах шифрования
    AES128:
      mode: GCM
      padding: NoPadding
    AES256:
      mode: GCM
      padding: NoPadding
    GOST3412-2015:
      mode: GCM
      padding: NoPadding
  stream-cipher-suites: # криптонаборы, базирующиеся на потоковых алгоритмах шифрования
    ChaCha20:
      mode: Poly1305

Параметры работы с подписью (опционально)#

При интеграции с Vault KV совместимом хранилище бинарные секреты должны быть закодированы в base64 и размещены в Key-Value хранилище Vault KV совместимом хранилищем

signature: # опциональный раздел параметров (в его отсутствие функционал работы с ЭП будет недоступен)
  key-store-suites:
    <key store alias>:
      type: <value> # JKS / JCEKS / PKCS12
      location: <путь до файла key store>
      password: ${наименование переменной окружения, хранящей пароль от key store} # опциональный параметр
      private-key:
        alias: <alias, с которым сохранен приватный ключ в key store>
        password: ${наименование переменной окружения, хранящей пароль от private key в key store}
      certificate-aliases:
        - <alias, с которым в key store сохранен публичный ключ, парный к текущему приватному>
        - <alias, с которым в key store сохранен публичный ключ, парный к предыдущему приватному> # если таковой имелся

Конфигурирование crypto-web сервиса (опционально)#

platform:
  utils:
    crypto: # опциональный раздел параметров
      web-service:
        base-path: <path> # базовый path, запросы на который ожидает веб крипто-сервис (по умолчанию: crypto)
        cipher-suites: # опциональный раздел параметров (в его отсутствие будут использованы параметры шифронаборов по умолчанию)
          block-cipher-suites: <описание block-cipher-suites см. ниже>
          stream-cipher-suites: <описание stream-cipher-suites см. ниже>
        signature-suites: # опциональный раздел параметров (в его отсутствие функционал работы с ЭП будет недоступен)
          key-store-suites: <описание key-store-suites см. ниже>

Конфигурирование Platform V Audit SE#

platform.utils:
  audit:
    writer: REST
    rest-writer:
      base-url: <audit service url>

Расширение событий context UUID (опционально)#

platform:
  utils:
    audit:
      meta-model:
        context-uuid:
          value-source:
            http-header: <some-header-name> # заголовок входящего http-запроса, значение которого будет использовано в качестве audit context uuid

Добавление параметров событий Platform V Audit SE (опционально).#

Если требуется добавить произвольные параметры к событиям аудита, то необходимо явным образом указать это в параметрах конфигурации.

platform:
  utils:
    audit:
      meta-model:
        additional-event-parameters:
          SOME_ADDITIONAL_PARAM_1: # код параметра аудита, которым будет обогащен каждый запрос аудита
            value-source:
              http-header: <some-additional-param-header-name-1> # заголовок входящего http-запроса, значение которого будет использовано в параметре
            localization:
              rus: "ваше описание параметра"
              eng: "your parameter description"
          SOME_ADDITIONAL_PARAM_2: # код параметра аудита, которым будет обогащен каждый запрос аудита
            value-source:
              http-header: <some-additional-param-header-name-2> # заголовок входящего http-запроса, значение которого будет использовано в параметре
            localization:
              rus: "ваше описание параметра"
              eng: "your parameter description"

Конфигурирование мониторинга#

По умолчанию все входные точки пакета Spring Boot Actuator включены метрики мониторинга в формате Prometheus и доступны по пути:

/actuator/prometheus

Конфигурирование журналирования#

Компонент поддерживает работу с двумя видами журналирования:

  • локальное - пишет в локальный журнал. Конфигурируется секцией local раздела logging.startup-parameters

  • внешнее - отправка записей во внешнюю систему (продукт Platform V Monitor OPM компонент LOGA). Конфигурируется секцией remote раздела logging.startup-parameters

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

platform.utils:
  logging:
    startup-parameters:
      local:
        enabled: false # true
        level:
          root: INFO # DEBUG / INFO / WARN / ERROR
          name: # указать java пакеты, либо оставить пусто
      remote:
        level:
          root: INFO
          name:
        appender:
          kafka:
            enabled: true
            connection:
              topic: FSGW.LOGGER
              producer:
                "[bootstrap.servers]": localhost:9094

Интеграция с Vault KV совместимое хранилище#

Предварительно необходимые секреты должны быть размещены в KV хранилище Vault KV совместимое хранилище

Приведенный пример актуален для OSE/k8s окружения, предварительно должен быть размещен SA с правами доступа в Vault KV совместимое хранилище

Добавить секцию для подключения к Vault KV совместимое хранилище

spring:
  config:
    import: optional:vault://DEV_DZO/A/DEV/FSGW/KV/S3-IFT #имя секрета в рамках KV хранилища 
  cloud:
    vault:
      enable: true
      kubernetes:
        kubernetes-path: os/stands # путь авторизации для конкретного инстанса Vault KV совместимое хранилище
        role: role-ga-secman-fex-users # имя роли доступа
      authentication: KUBERNETES # тип аутентификации
      namespace: DEV # имя неймспейса конкретного инстанса Vault KV совместимое хранилище
      transitEngineCacheTTL: 30S #время кеширования ключа в режиме fsgw-tse-key, по умолчанию 0.
      transitEngineCacheSize: 100 #размер кеша, по умолчанию 0
      config.lifecycle: # параметры обновления конфигурации
        enabled: true
        min-renewal: 10s
        expiry-threshold: 10s
      uri: http://somedomain.ru #URL до конкретного инстанса Vault KV совместимое хранилище

Миграция на текущую версию#

Дополнительных действий для миграции не требуется, т.к. FSGW реализует поведение согласно протоколу S3 и является прозрачным S3 шлюзом для файловых интеграций.

Быстрый старт#

  1. Настройте сервис согласно разделу "Подключение и конфигурирование".

  2. Запустите приложение командой java --add-opens java.base/jdk.internal.misc=ALL-UNNAMED -XX:MaxRAMPercentage=90.0 -Dio.netty.tryReflectionSetAccessible=true -Dspring.config.additional-location="file:/opt/conf/file-storage-gateway/file-storage-gateway-config.yml" -jar /opt/file-storage-gateway/file-storage-gateway.jar.

  3. Выполните GET-запрос по адресу http request /actuator/health.

  4. Получите ответ со статусом 200 и содержимым:

    {
      "status":"UP",
      "components":{
          "diskSpace":{
            "status":"UP",
            "details":{
                "total":<значение вашей ФС>,
                "free":<значение вашей ФС>,
                "threshold":<значение вашей ФС>,
                "exists":true
            }
          },
          "ping":{
            "status":"UP"
          }
      }
    }
    

Где:

  • file-storage-gateway.jar - файл приложения.

  • /opt/conf/file-storage-gateway/file-storage-gateway-config.yml - путь до файла конфигурации.

Использование программного компонента#

File Storage Gateway является прозрачным шлюзом S3, который имитирует шифрование на стороне сервера с использованием клиентского ключа(SSE-C) и подписи (открепленная подпись). Реализует следующий список операций:

  • ListBuckets

  • HeadBucket

  • CreateBucket

  • DeleteBucket

  • PutObject

  • GetObject

  • HeadObject

  • CopyObject

  • DeleteObject

  • ListObjects

  • CreateMultipartUpload

  • UploadPart

  • AbortMultipartUpload

  • CompleteMultipartUpload

Для работы с FSGW рекомендуется использование Amazon S3 SDK.

Далее рассмотрим работу с S3 SDK на примере Amazon S3 SDK for Java 2.0.

Подключите зависимость#

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>2.X.Y</version>
</dependency>
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>netty-nio-client</artifactId>
    <version>2.X.Y</version>
</dependency>

Не рекомендуется использовать Amazon S3 SDK for Java 1.0.

Все дальнейшие примеры будут базироваться на использовании Amazon S3 SDK v2.

Настройте клиента#

@Configuration
public class S3AsyncClientConfiguration {
    @Bean
    public S3AsyncClient s3AsyncGatewayClient() {
        return S3AsyncClient.builder()
                .region(Region.of("FSGW"))
                .httpClient(buildSdkAsyncHttpClient())
                .endpointOverride(getS3GatewayEndpoint())
                .serviceConfiguration(getS3Configuration())
                .credentialsProvider(getAwsCredentialsProvider())
                .overrideConfiguration(buildClientOverrideConfiguration())
                .build();
    }

    private S3Configuration getS3Configuration() {
        return S3Configuration.builder()
                .pathStyleAccessEnabled(true)
                .chunkedEncodingEnabled(false)
                .checksumValidationEnabled(false)
                .build();
    }

    public AwsCredentialsProvider getAwsCredentialsProvider() {
        return AnonymousCredentialsProvider.create();
    }

    private URI getS3GatewayEndpoint() {
        return UriComponentsBuilder.newInstance()
                .scheme("http")
                .host("fsgwHost") // в случае sidecar - "localhost", в случае service - адрес сервиса в k8s
                .port(fsgwPort)   // по умолчанию, 8080
                .path("fsgwPath") // по умолчанию, "/s3"
                .build()
                .toUri();
    }

    private SdkAsyncHttpClient buildSdkAsyncHttpClient() {
        return NettyNioAsyncHttpClient.builder()
                .writeTimeout(Duration.ofSeconds(30))
                .readTimeout(Duration.ofSeconds(30))
                .connectionTimeout(Duration.ofSeconds(1))
                .maxConcurrency(128)
                .buildWithDefaults(AttributeMap.builder()
                        .put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, true)
                        .build()
                );
    }

    private ClientOverrideConfiguration buildClientOverrideConfiguration() {
        return ClientOverrideConfiguration.builder()
                .retryPolicy(RetryPolicy.none())
                .build();
    }
}

Выполните запрос к компоненту FSGW#

@Service
public class SomeService {
    @Autowired
    private S3AsyncClient s3AsyncGatewayClient;

    public void putObject(String bucket, String objectKey, Publisher<ByteBuffer> objectContent, long objectContentLength) {
        CompletableFuture<PutObjectResponse> putObjectResponseFuture = s3AsyncGatewayClient.putObject(
                PutObjectRequest.builder()
                        .bucket(bucket)
                        .key(objectKey)
                        .contentLength(objectContentLength)
                        .build(),
                AsyncRequestBody.fromPublisher(objectContent)
        );

        /*...*/

        if (!putObjectResponse.sdkHttpResponse().isSuccessful()) {
            // ToDo: do something
        }
        // ToDo: do something
    }
}

Добавьте к запросу метаданные#

PutObjectRequest.builder()
    .metadata(Map.of("ключ вашей меты", "значение"))
    // другие поля
    .build()

Используйте конкретный ReplicaSet#

PutObjectRequest.builder()
    .overrideConfiguration(builder -> {
        builder.putHeader("x-amz-replica-set", "alias replica-set из файла конфигурации");
    })
    // другие поля
    .build()

Зашифруйте содержимое#

//В случае самостоятельной генерации ключа
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
byte[] key = keyGenerator.generateKey().getEncoded();

//В случае получения ключа от HashiCorp Vault в составе FSGW
HttpRequest httpRequest = HttpRequest.newBuilder().uri(String.format("%s/generate/cipher/key?algorithm=AES256", "http://8080/crypto/")).build();
HttpResponse<byte[]> httpResponse = HttpClient.newHttpClient().send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());
byte[] key = httpResponse.body();


//Непосредственно подстановка ключа в запрос
PutObjectRequest.builder()
    .sseCustomerAlgorithm("AES256") // или другой иной алгоритм, например: "AES128" / "GOST3412-2015" / "ChaCha20"
    .sseCustomerKey(base64EncodedKey)
    .sseCustomerKeyMD5(base64EncodedMd5CalculatedByKey)
    // другие поля
    .build()

Подпишите объект#

PutObjectRequest.builder()
    .overrideConfiguration(builder -> {
        builder.putHeader("x-amz-server-side-signature-suite", "key-store"); // или "key-store"
        builder.putHeader("x-amz-server-side-signature-object-key", objectKey + ".sign");
    })
    // другие поля
    .build()

Если требуется дополнительно шифрование, необходимо указать порядок наложения подписи x-amz-server-side-signature-order.

PutObjectRequest.builder()
    .overrideConfiguration(builder -> {
        builder.putHeader("x-amz-server-side-signature-suite", "key-store"); // или "key-store"
        builder.putHeader("x-amz-server-side-signature-object-key", objectKey + ".sign");
        builder.putHeader("x-amz-server-side-signature-order", "before-encryption"); // или "after-encryption"
    })
    // другие поля
    .build()

Рассчитайте digest (результат hash-функции) содержимого объекта (S3 ServerSideHash)#

PutObjectRequest.builder()
    .overrideConfiguration(builder -> {
        builder.putHeader("x-amz-server-side-hash-algorithm", "GOST3411-2012-256"); // или "GOST3411-2012-512"
    })
    // другие поля
    .build()

Если рассчитать digest требуется одновременно с шифрованием, то также требуется указание порядка наложения ЭП:

PutObjectRequest.builder()
    .overrideConfiguration(builder -> {
        builder.putHeader("x-amz-server-side-hash-algorithm", "GOST3411-2012-256"); // или "GOST3411-2012-512"
        builder.putHeader("x-amz-server-side-hash-order", "before-encryption"); // или "after-encryption"
    })
    // другие поля
    .build()

Извлечение digest из ответа:

PutObjectResponse putObjectResponse = ...;
String base64EncodedDigest = putObjectResponse.sdkHttpResponse().headers().get("server-side-hash").get(0);
byte[] digest = Base64.getDecoder().decode(base64EncodedDigest);

Часто встречающиеся проблемы и пути их устранения#

  • проблемы с S3-совместимым хранилищем:

    • решение: Убедиться что для пользователя (access-key которого указан в конфигурации) разрешены операции чтения и записи в bucket, использующемся в интеграционном взаимодействии. Убедиться, что Bucket не переполнен и передаваемые файлы не занимают все свободное место в bucket.