diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5401b79 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_size = 4 +indent_style = space +trim_trailing_whitespace = true +max_line_length = 120 diff --git a/.env b/.env new file mode 100644 index 0000000..0804923 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +SERVICE_NAME=kds +OTP_VERSION=24.3.4 +REBAR_VERSION=3.18 +THRIFT_VERSION=0.14.2.3 diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 0000000..ff53b0e --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,21 @@ +name: Build and publish Docker image + +on: + push: + branches: + - 'master' + - 'epic/**' + pull_request: + branches: ['**'] + +env: + REGISTRY: ghcr.io + +jobs: + build-push: + runs-on: ubuntu-latest + steps: + - uses: valitydev/action-deploy-docker@v2 + with: + registry-username: ${{ github.actor }} + registry-access-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml new file mode 100644 index 0000000..7a895db --- /dev/null +++ b/.github/workflows/erlang-checks.yml @@ -0,0 +1,38 @@ +name: Erlang CI Checks + +on: + push: + branches: + - 'master' + - 'epic/**' + pull_request: + branches: ['**'] + +jobs: + setup: + name: Load .env + runs-on: ubuntu-latest + outputs: + otp-version: ${{ steps.otp-version.outputs.version }} + rebar-version: ${{ steps.rebar-version.outputs.version }} + thrift-version: ${{ steps.thrift-version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - run: grep -v '^#' .env >> $GITHUB_ENV + - id: otp-version + run: echo "::set-output name=version::$OTP_VERSION" + - id: rebar-version + run: echo "::set-output name=version::$REBAR_VERSION" + - id: thrift-version + run: echo "::set-output name=version::$THRIFT_VERSION" + + run: + name: Run checks + needs: setup + uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.9 + with: + otp-version: ${{ needs.setup.outputs.otp-version }} + rebar-version: ${{ needs.setup.outputs.rebar-version }} + use-thrift: true + thrift-version: ${{ needs.setup.outputs.thrift-version }} diff --git a/.gitignore b/.gitignore index d52ca29..8b14b84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,13 @@ -# general -log +# Build artifacts /_build/ -*~ +*.o +*.beam +*.plt + +# Run artifacts erl_crash.dump -.tags* -*.sublime-workspace -.DS_Store -*_thrift.[eh]rl - -# jenkins -\#* -.\#* -.tags* -Dockerfile -docker-compose.yml - -# compile -compile_commands.json +log +# make stuff +/.image.* +Makefile.env diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6bc1e5e..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "build_utils"] - path = build_utils - url = https://github.com/rbkmoney/build_utils.git - branch = master diff --git a/CODEOWNERS b/CODEOWNERS index aad7ee2..abc9e49 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @rbkmoney/appsec +* @valitydev/appsec diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ec0732d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +ARG OTP_VERSION + +# Build the release +FROM docker.io/library/erlang:${OTP_VERSION} AS builder +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Install thrift compiler +ARG THRIFT_VERSION +ARG TARGETARCH +RUN wget -q -O- "https://github.com/valitydev/thrift/releases/download/${THRIFT_VERSION}/thrift-${THRIFT_VERSION}-linux-${TARGETARCH}.tar.gz" \ + | tar -xvz -C /usr/local/bin/ + +# Copy sources +RUN mkdir /build +COPY . /build/ + +# Build the release +WORKDIR /build +RUN rebar3 compile && \ + rebar3 as prod release + +# Make a runner image +FROM docker.io/library/erlang:${OTP_VERSION}-slim + +ARG SERVICE_NAME + +# Set env +ENV CHARSET=UTF-8 +ENV LANG=C.UTF-8 + +# Set runtime +WORKDIR /opt/${SERVICE_NAME} + +COPY --from=builder /build/_build/prod/rel/${SERVICE_NAME} /opt/${SERVICE_NAME} + +RUN echo "#!/bin/sh" >> /entrypoint.sh && \ + echo "exec /opt/${SERVICE_NAME}/bin/${SERVICE_NAME} foreground" >> /entrypoint.sh && \ + chmod +x /entrypoint.sh +ENTRYPOINT [] +CMD ["/entrypoint.sh"] + +EXPOSE 8022 diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..e4cfa53 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,17 @@ +ARG OTP_VERSION + +FROM docker.io/library/erlang:${OTP_VERSION} +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Install thrift compiler +ARG THRIFT_VERSION +ARG TARGETARCH +RUN wget -q -O- "https://github.com/valitydev/thrift/releases/download/${THRIFT_VERSION}/thrift-${THRIFT_VERSION}-linux-${TARGETARCH}.tar.gz" \ + | tar -xvz -C /usr/local/bin/ + +# Set env +ENV CHARSET=UTF-8 +ENV LANG=C.UTF-8 + +# Set runtime +CMD ["/bin/bash"] diff --git a/Dockerfile.sh b/Dockerfile.sh deleted file mode 100755 index 8cb9267..0000000 --- a/Dockerfile.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -cat < -COPY _build/prod/rel/kds /opt/kds -CMD /opt/kds/bin/kds foreground -EXPOSE 8022 8023 -# A bit of magic below to get a proper branch name -# even when the HEAD is detached (Hey Jenkins! -# BRANCH_NAME is available in Jenkins env). -LABEL com.rbkmoney.$SERVICE_NAME.parent=$BASE_IMAGE_NAME \ - com.rbkmoney.$SERVICE_NAME.parent_tag=$BASE_IMAGE_TAG \ - com.rbkmoney.$SERVICE_NAME.build_img=build \ - com.rbkmoney.$SERVICE_NAME.build_img_tag=$BUILD_IMAGE_TAG \ - com.rbkmoney.$SERVICE_NAME.commit_id=$(git rev-parse HEAD) \ - com.rbkmoney.$SERVICE_NAME.commit_number=$(git rev-list --count HEAD) \ - com.rbkmoney.$SERVICE_NAME.branch=$( \ - if [ "HEAD" != $(git rev-parse --abbrev-ref HEAD) ]; then \ - echo $(git rev-parse --abbrev-ref HEAD); \ - elif [ -n "$BRANCH_NAME" ]; then \ - echo $BRANCH_NAME; \ - else \ - echo $(git name-rev --name-only HEAD); \ - fi) -WORKDIR /opt/kds -EOF - diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index b65c98b..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,22 +0,0 @@ -#!groovy -// -*- mode: groovy -*- - -def finalHook = { - runStage('store CT logs') { - archive '_build/test/logs/' - } -} - -build('kds', 'docker-host', finalHook) { - checkoutRepo() - loadBuildUtils() - - def pipeErlangService - runStage('load pipeline') { - env.JENKINS_LIB = "build_utils/jenkins_lib" - env.SH_TOOLS = "build_utils/sh" - pipeErlangService = load("${env.JENKINS_LIB}/pipeErlangService.groovy") - } - - pipeErlangService.runPipe(false) -} diff --git a/Makefile b/Makefile index 17bc7c0..02051b7 100644 --- a/Makefile +++ b/Makefile @@ -1,71 +1,94 @@ -REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3) -SUBMODULES = build_utils -SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES)) +# HINT +# Use this file to override variables here. +# For example, to run with podman put `DOCKER=podman` there. +-include Makefile.env -UTILS_PATH := build_utils -TEMPLATES_PATH := . +# NOTE +# Variables specified in `.env` file are used to pick and setup specific +# component versions, both when building a development image and when running +# CI workflows on GH Actions. This ensures that tasks run with `wc-` prefix +# (like `wc-dialyze`) are reproducible between local machine and CI runners. +DOTENV := $(shell grep -v '^\#' .env) -# Name of the service -SERVICE_NAME := kds -# Service image default tag -SERVICE_IMAGE_TAG ?= $(shell git rev-parse HEAD) -# The tag for service image to be pushed with -SERVICE_IMAGE_PUSH_TAG ?= $(SERVICE_IMAGE_TAG) - -# Base image for the service -BASE_IMAGE_NAME := service-erlang -BASE_IMAGE_TAG := ef20e2ec1cb1528e9214bdeb862b15478950d5cd - -# Build image tag to be used -BUILD_IMAGE_NAME := build-erlang -BUILD_IMAGE_TAG := aaa79c2d6b597f93f5f8b724eecfc31ec2e2a23b - -CALL_W_CONTAINER := all submodules compile xref lint dialyze test \ - release clean distclean check_format format - -.PHONY: $(CALL_W_CONTAINER) +DOCKER ?= docker +REBAR ?= rebar3 +TEST_CONTAINER_NAME ?= testrunner all: compile --include $(UTILS_PATH)/make_lib/utils_container.mk --include $(UTILS_PATH)/make_lib/utils_image.mk +# Development images -$(SUBTARGETS): %/.git: % - git submodule update --init $< - touch $@ +DEV_IMAGE_TAG = kds-dev +DEV_IMAGE_ID = $(file < .image.dev) -submodules: $(SUBTARGETS) +.PHONY: dev-image clean-dev-image wc-shell test -compile: submodules +dev-image: .image.dev + +.image.dev: Dockerfile.dev .env + $(DOCKER) build . -f Dockerfile.dev --tag $(DEV_IMAGE_TAG) $(DOTENV:%=--build-arg %) + $(DOCKER) image ls -q -f "reference=$(DEV_IMAGE_TAG)" | head -n1 > $@ + +clean-dev-image: +ifneq ($(DEV_IMAGE_ID),) + $(DOCKER) image rm -f $(DEV_IMAGE_TAG) + rm .image.dev +endif + +DOCKER_WC_OPTIONS := -v $(PWD):$(PWD) --workdir $(PWD) +DOCKER_WC_EXTRA_OPTIONS ?= --rm +DOCKER_RUN = $(DOCKER) run -t $(DOCKER_WC_OPTIONS) $(DOCKER_WC_EXTRA_OPTIONS) + +# Utility tasks + +wc-shell: dev-image + $(DOCKER_RUN) --interactive --tty $(DEV_IMAGE_TAG) + +wc-%: dev-image + $(DOCKER_RUN) $(DEV_IMAGE_TAG) make $* + +# Rebar tasks + +rebar-shell: + $(REBAR) shell + +compile: $(REBAR) compile -xref: submodules +xref: $(REBAR) xref lint: - elvis rock -V + $(REBAR) lint -check_format: +check-format: $(REBAR) fmt -c +dialyze: + $(REBAR) as test dialyzer + +release: + $(REBAR) as prod release + +eunit: + $(REBAR) eunit --cover + +common-test: + $(REBAR) ct --cover + +cover: + $(REBAR) covertool generate + format: $(REBAR) fmt -w -dialyze: submodules - $(REBAR) dialyzer - -devrel: submodules - $(REBAR) release - -release: submodules distclean - $(REBAR) as prod release - clean: $(REBAR) clean -distclean: +distclean: clean-build-image rm -rf _build -test: submodules - $(REBAR) do eunit,ct +test: eunit common-test +cover-report: + $(REBAR) cover diff --git a/README.md b/README.md index 96be176..2fccbbf 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,49 @@ -# kds +# KDS -A service that does cds keyring management +**K**eyring **D**istribution **S**ervice. Allows securely managing keyring and provide [CDS](https://github.com/valitydev/cds) with keyring secrets. -## Сборка +## Building -Чтобы запустить проект в режиме разработки и получить стандартный [Erlang shell][2], нужно всего лишь: +To build the project, run the following command: - make wc_shell - rebar3 shell +```bash +$ make compile +``` -## Документация +## Running -Дальнейшую документацию можно почерпнуть, пройдясь по ссылкам в [соответствующем документе](doc/keyring.md). +To enter the [Erlang shell][1] with the project running, run the following command: + +```bash +$ make rebar-shell +``` + +## Development environment + +### Run in a docker container + +You can run any of the tasks defined in the Makefile from inside of a docker container (defined in `Dockerfile.dev`) by prefixing the task name with `wc-`. To successfully build the dev container you need `Docker BuildKit` enabled. This can be accomplished by either installing [docker-buildx](https://docs.docker.com/buildx/working-with-buildx/) locally, or exporting the `DOCKER_BUILDKIT=1` environment variable. + +#### Example + +* This command will run the `compile` task in a docker container: +```bash +$ make wc-compile +``` + +### Run in a docker-compose environment + +Similarly, you can run any of the tasks defined in the Makefile from inside of a docker-compose environment (defined in `docker-compose.yaml`) by prefixing the task name with `wdeps-`. To successfully build the dev container you need `Docker BuildKit` enabled (see `Run in a docker container` section). It *may* also be necessary to export a `COMPOSE_DOCKER_CLI_BUILD=1` environment variable for `docker-compose` container builds to work properly. + +#### Example + +* This command will run the `test` task in a docker-compose environment: +```bash +$ make wdeps-test +``` + +## Documentation + +@TODO Please write a couple of words about what your project does and how it does it. [1]: http://erlang.org/doc/man/shell.html diff --git a/apps/kds/src/kds.app.src b/apps/kds/src/kds.app.src index ff57fde..6a070b1 100644 --- a/apps/kds/src/kds.app.src +++ b/apps/kds/src/kds.app.src @@ -14,9 +14,7 @@ msgpack, lib_combin, jose, - cds_proto, - prometheus, - prometheus_cowboy + cds_proto ]}, {env, []}, {modules, []}, diff --git a/apps/kds/src/kds_crypto.erl b/apps/kds/src/kds_crypto.erl index 9523bdd..71a9931 100644 --- a/apps/kds/src/kds_crypto.erl +++ b/apps/kds/src/kds_crypto.erl @@ -1,7 +1,6 @@ -module(kds_crypto). -include_lib("jose/include/jose_jwk.hrl"). --include_lib("jose/include/jose_jws.hrl"). -export([key/0]). -export([encrypt/2]). diff --git a/apps/kds/src/kds_keyring_initializer.erl b/apps/kds/src/kds_keyring_initializer.erl index bc34d66..b44f03d 100644 --- a/apps/kds/src/kds_keyring_initializer.erl +++ b/apps/kds/src/kds_keyring_initializer.erl @@ -1,6 +1,6 @@ -module(kds_keyring_initializer). --behavior(gen_statem). +-behaviour(gen_statem). -include_lib("shamir/include/shamir.hrl"). diff --git a/apps/kds/src/kds_keyring_management_thrift_handler.erl b/apps/kds/src/kds_keyring_management_thrift_handler.erl index 9c925f3..a85a329 100644 --- a/apps/kds/src/kds_keyring_management_thrift_handler.erl +++ b/apps/kds/src/kds_keyring_management_thrift_handler.erl @@ -7,7 +7,7 @@ %% woody_server_thrift_handler callbacks -export([handle_function/4]). --type encrypted_masterkey_share() :: #cds_EncryptedMasterKeyShare{}. +-type encrypted_masterkey_share() :: #'cds_EncryptedMasterKeyShare'{}. %% %% woody_server_thrift_handler callbacks @@ -30,11 +30,11 @@ handle_function_('StartInit', {Threshold}, _Context, _Opts) -> EncryptedMasterKeyShares -> {ok, encode_encrypted_shares(EncryptedMasterKeyShares)} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - invalid_args -> + throw:invalid_args -> raise(#cds_InvalidArguments{}) end; handle_function_('ValidateInit', {SignedShare}, _Context, _Opts) -> @@ -46,36 +46,36 @@ handle_function_('ValidateInit', {SignedShare}, _Context, _Opts) -> ok -> {ok, {success, #cds_Success{}}} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - {operation_aborted, Reason} -> + throw:{operation_aborted, Reason} -> raise(#cds_OperationAborted{reason = atom_to_binary(Reason, utf8)}) end; handle_function_('CancelInit', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:cancel_init()} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}) end; handle_function_('Lock', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:lock()} catch - {invalid_status, locked} -> + throw:{invalid_status, locked} -> {ok, ok}; - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}) end; handle_function_('StartUnlock', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:start_unlock()} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}) end; handle_function_('ConfirmUnlock', {SignedShare}, _Context, _Opts) -> @@ -87,27 +87,27 @@ handle_function_('ConfirmUnlock', {SignedShare}, _Context, _Opts) -> ok -> {ok, {success, #cds_Success{}}} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - {operation_aborted, Reason} -> + throw:{operation_aborted, Reason} -> raise(#cds_OperationAborted{reason = atom_to_binary(Reason, utf8)}) end; handle_function_('CancelUnlock', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:cancel_unlock()} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}) end; handle_function_('StartRotate', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:start_rotate()} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}) end; handle_function_('ConfirmRotate', {SignedShare}, _Context, _Opts) -> @@ -119,29 +119,29 @@ handle_function_('ConfirmRotate', {SignedShare}, _Context, _Opts) -> ok -> {ok, {success, #cds_Success{}}} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - {operation_aborted, Reason} -> + throw:{operation_aborted, Reason} -> raise(#cds_OperationAborted{reason = atom_to_binary(Reason, utf8)}) end; handle_function_('CancelRotate', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:cancel_rotate()} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}) end; handle_function_('StartRekey', {Threshold}, _Context, _Opts) -> try {ok, kds_keyring_manager:start_rekey(Threshold)} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - invalid_args -> + throw:invalid_args -> raise(#cds_InvalidArguments{}) end; handle_function_('ConfirmRekey', {SignedShare}, _Context, _Opts) -> @@ -153,11 +153,11 @@ handle_function_('ConfirmRekey', {SignedShare}, _Context, _Opts) -> ok -> {ok, {success, #cds_Success{}}} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - {operation_aborted, Reason} -> + throw:{operation_aborted, Reason} -> raise(#cds_OperationAborted{reason = atom_to_binary(Reason, utf8)}) end; handle_function_('StartRekeyValidation', {}, _Context, _Opts) -> @@ -165,9 +165,9 @@ handle_function_('StartRekeyValidation', {}, _Context, _Opts) -> EncryptedMasterKeyShares -> {ok, encode_encrypted_shares(EncryptedMasterKeyShares)} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}) end; handle_function_('ValidateRekey', {SignedShare}, _Context, _Opts) -> @@ -179,18 +179,18 @@ handle_function_('ValidateRekey', {SignedShare}, _Context, _Opts) -> ok -> {ok, {success, #cds_Success{}}} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {invalid_activity, Activity} -> + throw:{invalid_activity, Activity} -> raise(#cds_InvalidActivity{activity = Activity}); - {operation_aborted, Reason} -> + throw:{operation_aborted, Reason} -> raise(#cds_OperationAborted{reason = atom_to_binary(Reason, utf8)}) end; handle_function_('CancelRekey', {}, _Context, _Opts) -> try {ok, kds_keyring_manager:cancel_rekey()} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}) end; handle_function_('GetState', {}, _Context, _Opts) -> @@ -206,9 +206,9 @@ handle_function_('UpdateKeyringMeta', {KeyringMeta}, _Context, _Opts) -> ok -> {ok, ok} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}); - {validation_failed, Reason} -> + throw:{validation_failed, Reason} -> raise(#cds_InvalidKeyringMeta{reason = erlang:atom_to_binary(Reason, utf8)}) end; handle_function_('GetKeyringMeta', {}, _Context, _Opts) -> diff --git a/apps/kds/src/kds_keyring_manager.erl b/apps/kds/src/kds_keyring_manager.erl index 4824a51..42ae179 100644 --- a/apps/kds/src/kds_keyring_manager.erl +++ b/apps/kds/src/kds_keyring_manager.erl @@ -2,8 +2,6 @@ -behaviour(gen_statem). --include_lib("shamir/include/shamir.hrl"). - %% API. -export([start_link/0]). -export([get_keyring/0]). diff --git a/apps/kds/src/kds_keyring_meta.erl b/apps/kds/src/kds_keyring_meta.erl index b99b400..ae88d91 100644 --- a/apps/kds/src/kds_keyring_meta.erl +++ b/apps/kds/src/kds_keyring_meta.erl @@ -16,7 +16,7 @@ -export_type([keyring_meta_diff/0]). -type keyring_meta() :: #{ - current_key_id := non_neg_integer(), + current_key_id := key_id(), version := pos_integer(), keys := #{ key_id() => key_meta() @@ -24,7 +24,7 @@ }. -type keyring_meta_diff() :: #{ - current_key_id => non_neg_integer() | undefined, + current_key_id => key_id() | undefined, keys => #{ key_id() => key_meta_diff() @@ -50,9 +50,9 @@ }. -type key_id() :: kds_keyring:key_id(). --type encoded_keyring_meta() :: #cds_KeyringMeta{}. --type encoded_keyring_meta_diff() :: #cds_KeyringMetaDiff{}. --type encoded_security_parameters() :: #cds_SecurityParameters{}. +-type encoded_keyring_meta() :: #'cds_KeyringMeta'{}. +-type encoded_keyring_meta_diff() :: #'cds_KeyringMetaDiff'{}. +-type encoded_security_parameters() :: #'cds_SecurityParameters'{}. -define(DEFAULT_SEC_PARAMS, #{ deduplication_hash_opts => #{ diff --git a/apps/kds/src/kds_keyring_rekeyer.erl b/apps/kds/src/kds_keyring_rekeyer.erl index f8aa319..04c5362 100644 --- a/apps/kds/src/kds_keyring_rekeyer.erl +++ b/apps/kds/src/kds_keyring_rekeyer.erl @@ -1,6 +1,6 @@ -module(kds_keyring_rekeyer). --behavior(gen_statem). +-behaviour(gen_statem). -include_lib("shamir/include/shamir.hrl"). @@ -48,7 +48,7 @@ -type encrypted_keyring() :: kds_keyring:encrypted_keyring(). -type keyring() :: kds_keyring:keyring(). --type state() :: uninitialized | validation. +-type state() :: uninitialized | confirmation | postconfirmation | validation. -type threshold() :: kds_keysharing:threshold(). diff --git a/apps/kds/src/kds_keyring_rotator.erl b/apps/kds/src/kds_keyring_rotator.erl index b388927..ddb0c40 100644 --- a/apps/kds/src/kds_keyring_rotator.erl +++ b/apps/kds/src/kds_keyring_rotator.erl @@ -1,6 +1,6 @@ -module(kds_keyring_rotator). --behavior(gen_statem). +-behaviour(gen_statem). -include_lib("shamir/include/shamir.hrl"). diff --git a/apps/kds/src/kds_keyring_storage_thrift_handler.erl b/apps/kds/src/kds_keyring_storage_thrift_handler.erl index d1b1681..274ce0c 100644 --- a/apps/kds/src/kds_keyring_storage_thrift_handler.erl +++ b/apps/kds/src/kds_keyring_storage_thrift_handler.erl @@ -28,7 +28,7 @@ handle_function_('GetKeyring', {}, _Context, _Opts) -> Keyring -> {ok, encode_keyring(Keyring)} catch - {invalid_status, Status} -> + throw:{invalid_status, Status} -> raise(#cds_InvalidStatus{status = Status}) end. diff --git a/apps/kds/src/kds_keyring_unlocker.erl b/apps/kds/src/kds_keyring_unlocker.erl index cd4dd8a..949573f 100644 --- a/apps/kds/src/kds_keyring_unlocker.erl +++ b/apps/kds/src/kds_keyring_unlocker.erl @@ -1,6 +1,6 @@ -module(kds_keyring_unlocker). --behavior(gen_statem). +-behaviour(gen_statem). -include_lib("shamir/include/shamir.hrl"). diff --git a/apps/kds/test/kds_ct_stash.erl b/apps/kds/test/kds_ct_stash.erl index efc5fb3..da2ba90 100644 --- a/apps/kds/test/kds_ct_stash.erl +++ b/apps/kds/test/kds_ct_stash.erl @@ -9,9 +9,7 @@ init/1, handle_call/3, handle_cast/2, - handle_info/2, - terminate/2, - code_change/3 + handle_info/2 ]). -spec start() -> pid(). @@ -19,9 +17,9 @@ start() -> {ok, Pid} = gen_server:start(?MODULE, [], []), Pid. --spec stop(pid()) -> _. +-spec stop(pid()) -> ok. stop(Pid) -> - erlang:exit(Pid, normal). + proc_lib:stop(Pid, shutdown, 5000). -spec put(pid(), term(), term()) -> ok. put(Pid, Key, Value) -> @@ -33,29 +31,21 @@ get(Pid, Key) -> %% --spec init(term()) -> {ok, atom()}. +-spec init(term()) -> {ok, map()}. init(_) -> {ok, #{}}. --spec handle_call(term(), pid(), atom()) -> {reply, atom(), atom()}. +-spec handle_call(term(), pid(), map()) -> {reply, atom(), map()}. handle_call({put, Key, Value}, _From, State) -> {reply, ok, State#{Key => Value}}; handle_call({get, Key}, _From, State) -> Value = maps:get(Key, State, undefined), {reply, Value, State}. --spec handle_cast(term(), atom()) -> {noreply, atom()}. +-spec handle_cast(term(), map()) -> {noreply, map()}. handle_cast(_Msg, State) -> {noreply, State}. --spec handle_info(term(), atom()) -> {noreply, atom()}. +-spec handle_info(term(), map()) -> {noreply, map()}. handle_info(_Info, State) -> {noreply, State}. - --spec terminate(term(), atom()) -> atom(). -terminate(_Reason, _State) -> - ok. - --spec code_change(term(), term(), term()) -> {ok, atom()}. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. diff --git a/apps/kds/test/kds_keyring_api_tests_SUITE.erl b/apps/kds/test/kds_keyring_api_tests_SUITE.erl index 428a7bd..3ad89c7 100644 --- a/apps/kds/test/kds_keyring_api_tests_SUITE.erl +++ b/apps/kds/test/kds_keyring_api_tests_SUITE.erl @@ -1,6 +1,5 @@ -module(kds_keyring_api_tests_SUITE). --include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("shamir/include/shamir.hrl"). @@ -70,6 +69,7 @@ groups() -> rekey, rekey_with_timeout, rekey_with_cancel, + rotate, rotate_with_timeout, lock ]}, @@ -454,7 +454,7 @@ rotate(C) -> ), _ = ?assertEqual(ok, kds_keyring_client:start_rotate(root_url(C))), _ = ?assertEqual( - {error, {invalid_activity, {rotation, confirmation}}}, + {error, {invalid_activity, {rotation, validation}}}, kds_keyring_client:start_rotate(root_url(C)) ), _ = ?assertEqual({more_keys_needed, 1}, kds_keyring_client:confirm_rotate(Id1, MasterKey1, root_url(C))), diff --git a/apps/kds/test/kds_keyring_client.erl b/apps/kds/test/kds_keyring_client.erl index 323bfab..3205de9 100644 --- a/apps/kds/test/kds_keyring_client.erl +++ b/apps/kds/test/kds_keyring_client.erl @@ -42,11 +42,11 @@ start_init(Threshold, RootUrl) -> EncryptedShares -> decode_encrypted_shares(EncryptedShares) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_InvalidArguments{reason = Reason} -> + throw:#cds_InvalidArguments{reason = Reason} -> {error, {invalid_arguments, Reason}} end. @@ -56,7 +56,7 @@ start_init(Threshold, RootUrl) -> | {error, {invalid_status, kds_keyring_manager:state()}} | {error, {invalid_activity, {initialization, kds_keyring_initializer:state()}}} | {error, verification_failed} - | {error, {invalid_arguments, binary()}}. + | {error, {operation_aborted, binary()}}. validate_init(ShareholderId, Share, RootUrl) -> SignedShare = encode_signed_share(ShareholderId, Share), try kds_woody_client:call(keyring_management, 'ValidateInit', {SignedShare}, RootUrl) of @@ -65,13 +65,13 @@ validate_init(ShareholderId, Share, RootUrl) -> {more_keys_needed, More} -> {more_keys_needed, More} catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_VerificationFailed{} -> + throw:#cds_VerificationFailed{} -> {error, verification_failed}; - #cds_OperationAborted{reason = Reason} -> + throw:#cds_OperationAborted{reason = Reason} -> {error, {operation_aborted, Reason}} end. @@ -83,9 +83,9 @@ cancel_init(RootUrl) -> try kds_woody_client:call(keyring_management, 'CancelInit', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}} end. @@ -97,9 +97,9 @@ start_unlock(RootUrl) -> try kds_woody_client:call(keyring_management, 'StartUnlock', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}} end. @@ -118,13 +118,13 @@ confirm_unlock(ShareholderId, Share, RootUrl) -> {more_keys_needed, More} -> {more_keys_needed, More} catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_VerificationFailed{} -> + throw:#cds_VerificationFailed{} -> {error, verification_failed}; - #cds_OperationAborted{reason = Reason} -> + throw:#cds_OperationAborted{reason = Reason} -> {error, {operation_aborted, Reason}} end. @@ -135,7 +135,7 @@ cancel_unlock(RootUrl) -> try kds_woody_client:call(keyring_management, 'CancelUnlock', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}} end. @@ -146,7 +146,7 @@ lock(RootUrl) -> try kds_woody_client:call(keyring_management, 'Lock', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}} end. @@ -158,9 +158,9 @@ start_rotate(RootUrl) -> try kds_woody_client:call(keyring_management, 'StartRotate', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}} end. @@ -179,13 +179,13 @@ confirm_rotate(ShareholderId, Share, RootUrl) -> {more_keys_needed, More} -> {more_keys_needed, More} catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_VerificationFailed{} -> + throw:#cds_VerificationFailed{} -> {error, verification_failed}; - #cds_OperationAborted{reason = Reason} -> + throw:#cds_OperationAborted{reason = Reason} -> {error, {operation_aborted, Reason}} end. @@ -196,24 +196,24 @@ cancel_rotate(RootUrl) -> try kds_woody_client:call(keyring_management, 'CancelRotate', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}} end. -spec start_rekey(integer(), woody:url()) -> ok | {error, {invalid_status, kds_keyring_manager:state()}} - | {error, {invalid_activity, {rekeying, kds_keyring_rotator:state()}}} + | {error, {invalid_activity, {rekeying, kds_keyring_rekeyer:state()}}} | {error, {invalid_arguments, binary()}}. start_rekey(Threshold, RootUrl) -> try kds_woody_client:call(keyring_management, 'StartRekey', {Threshold}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_InvalidArguments{reason = Reason} -> + throw:#cds_InvalidArguments{reason = Reason} -> {error, {invalid_arguments, Reason}} end. @@ -221,7 +221,7 @@ start_rekey(Threshold, RootUrl) -> ok | {more_keys_needed, non_neg_integer()} | {error, {invalid_status, kds_keyring_manager:state()}} - | {error, {invalid_activity, {rekeying, kds_keyring_rotator:state()}}} + | {error, {invalid_activity, {rekeying, kds_keyring_rekeyer:state()}}} | {error, verification_failed} | {error, {operation_aborted, binary()}}. confirm_rekey(ShareholderId, Share, RootUrl) -> @@ -232,28 +232,28 @@ confirm_rekey(ShareholderId, Share, RootUrl) -> {more_keys_needed, More} -> {more_keys_needed, More} catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_VerificationFailed{} -> + throw:#cds_VerificationFailed{} -> {error, verification_failed}; - #cds_OperationAborted{reason = Reason} -> + throw:#cds_OperationAborted{reason = Reason} -> {error, {operation_aborted, Reason}} end. -spec start_rekey_validation(woody:url()) -> [kds_keysharing:encrypted_master_key_share()] | {error, {invalid_status, kds_keyring_manager:state()}} - | {error, {invalid_activity, {rekeying, kds_keyring_rotator:state()}}}. + | {error, {invalid_activity, {rekeying, kds_keyring_rekeyer:state()}}}. start_rekey_validation(RootUrl) -> try kds_woody_client:call(keyring_management, 'StartRekeyValidation', {}, RootUrl) of EncryptedShares -> decode_encrypted_shares(EncryptedShares) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}} end. @@ -261,7 +261,7 @@ start_rekey_validation(RootUrl) -> ok | {more_keys_needed, non_neg_integer()} | {error, {invalid_status, kds_keyring_manager:state()}} - | {error, {invalid_activity, {rekeying, kds_keyring_rotator:state()}}} + | {error, {invalid_activity, {rekeying, kds_keyring_rekeyer:state()}}} | {error, verification_failed} | {error, {operation_aborted, binary()}}. validate_rekey(ShareholderId, Share, RootUrl) -> @@ -272,13 +272,13 @@ validate_rekey(ShareholderId, Share, RootUrl) -> {more_keys_needed, More} -> {more_keys_needed, More} catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}}; - #cds_InvalidActivity{activity = Activity} -> + throw:#cds_InvalidActivity{activity = Activity} -> {error, {invalid_activity, Activity}}; - #cds_VerificationFailed{} -> + throw:#cds_VerificationFailed{} -> {error, verification_failed}; - #cds_OperationAborted{reason = Reason} -> + throw:#cds_OperationAborted{reason = Reason} -> {error, {operation_aborted, Reason}} end. @@ -289,7 +289,7 @@ cancel_rekey(RootUrl) -> try kds_woody_client:call(keyring_management, 'CancelRekey', {}, RootUrl) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}} end. @@ -298,7 +298,7 @@ get_state(RootUrl) -> State = kds_woody_client:call(keyring_management, 'GetState', {}, RootUrl), decode_state(State). --spec update_keyring_meta(kds_keyring_meta:keyring_meta(), woody:url()) -> +-spec update_keyring_meta(kds_keyring_meta:keyring_meta_diff(), woody:url()) -> ok | {error, {invalid_keyring_meta, binary()}} | {error, {invalid_status, kds_keyring_manager:state()}}. @@ -307,9 +307,9 @@ update_keyring_meta(KeyringMeta, RootUrl) -> EncodedMeta = kds_keyring_meta:encode_keyring_meta_diff(KeyringMeta), kds_woody_client:call(keyring_management, 'UpdateKeyringMeta', {EncodedMeta}, RootUrl) catch - #cds_InvalidKeyringMeta{reason = Reason} -> + throw:#cds_InvalidKeyringMeta{reason = Reason} -> {error, {invalid_keyring_meta, Reason}}; - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}} end. @@ -318,7 +318,9 @@ get_keyring_meta(RootUrl) -> KeyringMeta = kds_woody_client:call(keyring_management, 'GetKeyringMeta', {}, RootUrl), kds_keyring_meta:decode_keyring_meta(KeyringMeta). --spec get_keyring(woody:url(), term()) -> kds_keyring:keyring(). +-spec get_keyring(woody:url(), term()) -> + kds_keyring:keyring() + | {error, {invalid_status, not_initialized | locked}}. get_keyring(RootUrl, SSLOptions) -> ExtraOpts = #{ transport_opts => #{ @@ -333,7 +335,7 @@ get_keyring(RootUrl, SSLOptions) -> Keyring -> decode_keyring(Keyring) catch - #cds_InvalidStatus{status = Status} -> + throw:#cds_InvalidStatus{status = Status} -> {error, {invalid_status, Status}} end. diff --git a/apps/kds/test/kds_keyring_storage_api_tests_SUITE.erl b/apps/kds/test/kds_keyring_storage_api_tests_SUITE.erl index ad0141c..2f9067d 100644 --- a/apps/kds/test/kds_keyring_storage_api_tests_SUITE.erl +++ b/apps/kds/test/kds_keyring_storage_api_tests_SUITE.erl @@ -1,8 +1,6 @@ -module(kds_keyring_storage_api_tests_SUITE). --include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). --include_lib("shamir/include/shamir.hrl"). -export([all/0]). -export([groups/0]). @@ -14,7 +12,7 @@ -export([rotation_version_check/1]). -export([update_meta_version_check/1]). --type config() :: [{tuple()}]. +-type config() :: [{atom(), term()}]. -spec test() -> _. diff --git a/apps/kds/test/kds_keyring_storage_tests_SUITE.erl b/apps/kds/test/kds_keyring_storage_tests_SUITE.erl index 3f52524..4dab4c2 100644 --- a/apps/kds/test/kds_keyring_storage_tests_SUITE.erl +++ b/apps/kds/test/kds_keyring_storage_tests_SUITE.erl @@ -1,7 +1,5 @@ -module(kds_keyring_storage_tests_SUITE). --include_lib("common_test/include/ct.hrl"). - -export([all/0]). -export([groups/0]). -export([init_per_group/2]). @@ -16,7 +14,7 @@ -export([create_old_format/1]). -export([read_old_format/1]). --type config() :: [tuple()]. +-type config() :: [{atom(), term()}]. -spec all() -> [{group, atom()}]. all() -> @@ -53,7 +51,7 @@ init_per_group(_, C) -> C1 = kds_ct_utils:start_stash(C), kds_ct_utils:start_clear(C1). --spec end_per_group(atom(), config()) -> config(). +-spec end_per_group(atom(), config()) -> _. end_per_group(_, C) -> kds_ct_utils:stop_clear(C). diff --git a/build_utils b/build_utils deleted file mode 160000 index be44d69..0000000 --- a/build_utils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit be44d69fc87b22a0bb82d98d6eae7658d1647f98 diff --git a/config/sys.config b/config/sys.config index dc54b72..38bfbd8 100644 --- a/config/sys.config +++ b/config/sys.config @@ -42,25 +42,25 @@ owner => <<"ndiezel">>, public_keys => #{ enc => - <<" - { - \"use\": \"enc\", - \"kty\": \"RSA\", - \"kid\": \"KUb1fNMc5j9Ei_IV3DguhJh5UOH30uvO7qXq13uevnk\", - \"alg\": \"RSA-OAEP-256\", - \"n\": \"2bxkamUQjD4CN8rcq5BfNLJmRmosb-zY7ajPBJqtiLUTcqym23OkUIA1brBg34clmU2ZQmtd3LWi5kVJk_wr4WsMG_78jHK3wQA-HRhY4WZDZrULTsi4XWpNSwL4dCml4fs536RKy_TyrnpiXg0ug4JVVaEeo7VIZ593mVhCxC8Ev6FK8tZ2HGGOerUXLpgQdhcp9UwaI_l7jgoWNp1f7SuBqv1mfiw4ziC1yvwyXHTKy-37LjLmVB9EVyjqpkwZgzapaOvHc1ABqJpdOrUh-PyOgq-SduqSkMrvqZEdUeR_KbFVxqbxqWJMrqkl2HOJxOla9cHRowg5ObUBjeMoaTJfqie3t6uRUsFEFMzhIyvo6QMYHooxIdOdwpZ4tpzML6jv9o5DPtN375bKzy-UsjeshYbvad1mbrcxc8tYeiQkDZEIM0KeOdHm5C6neEyY6oF4s1vSYBNCnhE5O-R9dmp8Sk5KEseEkOH5u4G2RsIXBA9z1OTDoy6qF21EvRCGzsGfExfkmPAtzbnS-EHHxbMUiio0ZJoZshYo8dwJY6vSN7UsXBgW1v7GvIF9VsfzRmgkl_3rdemYy28DJKC0U2yufePcA3nUJEhtR3UO_tIlHxZvlDSX5eTx4vs5VkFfujNSiPsgH0PEeXABGBFbal7QxU1u0XHXIFwhW5cM8Fs\", - \"e\": \"AQAB\" - } - ">>, + << + "{" + " \"use\": \"enc\"," + " \"kty\": \"RSA\"," + " \"kid\": \"KUb1fNMc5j9Ei_IV3DguhJh5UOH30uvO7qXq13uevnk\"," + " \"alg\": \"RSA-OAEP-256\"," + " \"n\": \"2bxkamUQjD4CN8rcq5BfNLJmRmosb-zY7ajPBJqtiLUTcqym23OkUIA1brBg34clmU2ZQmtd3LWi5kVJk_wr4WsMG_78jHK3wQA-HRhY4WZDZrULTsi4XWpNSwL4dCml4fs536RKy_TyrnpiXg0ug4JVVaEeo7VIZ593mVhCxC8Ev6FK8tZ2HGGOerUXLpgQdhcp9UwaI_l7jgoWNp1f7SuBqv1mfiw4ziC1yvwyXHTKy-37LjLmVB9EVyjqpkwZgzapaOvHc1ABqJpdOrUh-PyOgq-SduqSkMrvqZEdUeR_KbFVxqbxqWJMrqkl2HOJxOla9cHRowg5ObUBjeMoaTJfqie3t6uRUsFEFMzhIyvo6QMYHooxIdOdwpZ4tpzML6jv9o5DPtN375bKzy-UsjeshYbvad1mbrcxc8tYeiQkDZEIM0KeOdHm5C6neEyY6oF4s1vSYBNCnhE5O-R9dmp8Sk5KEseEkOH5u4G2RsIXBA9z1OTDoy6qF21EvRCGzsGfExfkmPAtzbnS-EHHxbMUiio0ZJoZshYo8dwJY6vSN7UsXBgW1v7GvIF9VsfzRmgkl_3rdemYy28DJKC0U2yufePcA3nUJEhtR3UO_tIlHxZvlDSX5eTx4vs5VkFfujNSiPsgH0PEeXABGBFbal7QxU1u0XHXIFwhW5cM8Fs\"," + " \"e\": \"AQAB\"" + "}" + >>, sig => - <<" - { - \"crv\":\"Ed25519\", - \"kid\":\"0S3dDL16upIQpkt5sooMFXeZR4j1O7fZngAWgn_Bmbo\", - \"kty\":\"OKP\", - \"x\":\"tIUiRZkBczC152j1ItfvtITl222ZHLcz6wRqUDa0-Ls\" - } - ">> + << + "{" + " \"crv\":\"Ed25519\"," + " \"kid\":\"0S3dDL16upIQpkt5sooMFXeZR4j1O7fZngAWgn_Bmbo\"," + " \"kty\":\"OKP\"," + " \"x\":\"tIUiRZkBczC152j1ItfvtITl222ZHLcz6wRqUDa0-Ls\"" + "}" + >> } } }} @@ -99,16 +99,6 @@ {disksup_posix_only, true} ]}, - {how_are_you, [ - {metrics_publishers, [ - % {hay_statsd_publisher, #{ - % key_prefix => <<"kds.">>, - % host => "localhost", - % port => 8125 - % }} - ]} - ]}, - {snowflake, [ % 1 second {max_backward_clock_moving, 1000}, diff --git a/elvis.config b/elvis.config index a062515..87a35fa 100644 --- a/elvis.config +++ b/elvis.config @@ -1,55 +1,32 @@ [ {elvis, [ + {verbose, true}, {config, [ #{ - dirs => ["apps/*/src"], + dirs => ["apps/*/src", "apps/*/include"], filter => "*.erl", - ignore => ["_thrift.erl$"], + ruleset => erl_files, rules => [ - {elvis_text_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_text_style, no_tabs}, - {elvis_text_style, no_trailing_whitespace}, - {elvis_style, macro_module_names}, - {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, + {elvis_text_style, line_length, #{limit => 120}}, {elvis_style, nesting_level, #{level => 3}}, - {elvis_style, god_modules, #{limit => 30}}, - {elvis_style, no_if_expression}, - {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, - {elvis_style, used_ignored_variable}, - {elvis_style, no_behavior_info}, - {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, - {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, - {elvis_style, state_record_and_type, #{ - ignore => [ - kds_keyring_initializer, - kds_keyring_manager, - kds_keyring_rekeyer, - kds_keyring_rotator, - kds_keyring_unlocker - ] - }}, - {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 15}}, - {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} + {elvis_style, no_if_expression, disable}, + {elvis_style, state_record_and_type, disable}, + {elvis_style, dont_repeat_yourself, #{min_complexity => 15}} ] }, #{ - dirs => ["apps/*/test"], + dirs => ["apps/**/test"], filter => "*.erl", + ruleset => erl_files, rules => [ - {elvis_text_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_text_style, no_tabs}, - {elvis_text_style, no_trailing_whitespace}, - {elvis_style, macro_module_names}, - {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, + {elvis_text_style, line_length, #{limit => 120}}, {elvis_style, nesting_level, #{level => 3}}, - {elvis_style, no_if_expression}, - {elvis_style, used_ignored_variable}, - {elvis_style, no_behavior_info}, - {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, - {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, - {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 30}} + {elvis_style, no_if_expression, disable}, + % We want to use `ct:pal/2` and friends in test code. + {elvis_style, no_debug_call, disable}, + % Tests are usually more comprehensible when a bit more verbose. + {elvis_style, dont_repeat_yourself, #{min_complexity => 30}}, + {elvis_style, god_modules, disable} ] }, #{ @@ -63,19 +40,22 @@ ruleset => elvis_config }, #{ - dirs => ["."], + dirs => [".", "apps/*"], filter => "rebar.config", + ruleset => rebar_config, rules => [ - {elvis_text_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_text_style, line_length, #{limit => 120}}, {elvis_text_style, no_tabs}, - {elvis_text_style, no_trailing_whitespace} + {elvis_text_style, no_trailing_whitespace}, + %% Temporarily disabled till regex pattern is available + {elvis_project, no_deps_master_rebar, disable} ] }, #{ dirs => ["apps/*/src"], filter => "*.app.src", rules => [ - {elvis_text_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_text_style, line_length, #{limit => 120}}, {elvis_text_style, no_tabs}, {elvis_text_style, no_trailing_whitespace} ] diff --git a/rebar.config b/rebar.config index 83c4880..8985de7 100644 --- a/rebar.config +++ b/rebar.config @@ -1,11 +1,14 @@ -% Common project erlang options. +%% Common project erlang options. {erl_opts, [ + % mandatory debug_info, warnings_as_errors, warn_export_all, warn_missing_spec, warn_untyped_record, warn_export_vars, + + % by default warn_unused_record, warn_bif_clash, warn_obsolete_guard, @@ -14,38 +17,46 @@ warn_unused_import, warn_unused_function, warn_deprecated_function + + % at will + % bin_opt_info + % no_auto_import + % warn_missing_spec_all ]}. -% Common project dependencies. +%% Common project dependencies. {deps, [ {lib_combin, "0.1.5"}, {jsx, "3.1.0"}, - {jose, "1.11.2"}, - {prometheus, "4.8.1"}, - {prometheus_cowboy, "0.1.8"}, - {shamir, {git, "https://github.com/rbkmoney/shamir.git", {branch, master}}}, - {woody, {git, "https://github.com/rbkmoney/woody_erlang.git", {branch, master}}}, - {genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, master}}}, - {erl_health, {git, "https://github.com/rbkmoney/erlang-health.git", {branch, master}}}, - {cds_proto, {git, "https://github.com/rbkmoney/cds-proto.git", {branch, master}}}, - {msgpack, {git, "https://github.com/rbkmoney/msgpack-erlang", {branch, master}}}, - {scoper, {git, "https://github.com/rbkmoney/scoper.git", {branch, master}}} + %% NOTE + %% Pinning to version "1.11.2" from hex here causes constant upgrading and recompilation of the entire project + {jose, {git, "https://github.com/potatosalad/erlang-jose.git", {tag, "1.11.2"}}}, + {shamir, {git, "https://github.com/valitydev/shamir.git", {branch, master}}}, + {woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, master}}}, + {genlib, {git, "https://github.com/valitydev/genlib.git", {branch, master}}}, + {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, master}}}, + {cds_proto, {git, "https://github.com/valitydev/cds-proto.git", {branch, master}}}, + {msgpack, {git, "https://github.com/valitydev/msgpack-erlang", {branch, master}}}, + {scoper, {git, "https://github.com/valitydev/scoper.git", {branch, master}}} ]}. +%% XRef checks {xref_checks, [ undefined_function_calls, undefined_functions, - % locals_not_used, deprecated_functions_calls, deprecated_functions ]}. +% at will +% {xref_warnings, true}. +%% Dialyzer static analyzing {dialyzer, [ {warnings, [ + % mandatory unmatched_returns, error_handling, race_conditions, - %underspecs, unknown ]}, {plt_apps, all_deps} @@ -54,53 +65,61 @@ {profiles, [ {prod, [ {deps, [ - {how_are_you, {git, "https://github.com/rbkmoney/how_are_you.git", {ref, "2fd80134"}}}, - {woody_api_hay, {git, "https://github.com/rbkmoney/woody_api_hay.git", {ref, "4c39134cd"}}}, + % for introspection on production + {recon, "2.5.2"}, + % Because of a dependency conflict, prometheus libs are only included in the prod profile for now + % https://github.com/project-fifo/rebar3_lint/issues/42 + % https://github.com/valitydev/hellgate/pull/2/commits/884724c1799703cee4d1033850fe32c17f986d9e + {prometheus, "4.8.1"}, + {prometheus_cowboy, "0.1.8"}, {logger_logstash_formatter, - {git, "https://github.com/rbkmoney/logger_logstash_formatter.git", {ref, "87e52c755"}}}, - {recon, "2.5.2"} + {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "2c7b716"}}}, + {iosetopts, {git, "https://github.com/valitydev/iosetopts.git", {ref, "edb445c"}}} ]}, {relx, [ {release, {kds, "0.1.0"}, [ + iosetopts, {recon, load}, + {runtime_tools, load}, + {tools, load}, {logger_logstash_formatter, load}, - woody_api_hay, - how_are_you, + prometheus, + prometheus_cowboy, sasl, kds ]}, + {mode, minimal}, {sys_config, "./config/sys.config"}, {vm_args, "./config/vm.args"}, - {mode, minimal}, {extended_start_script, true} ]} + ]}, + {test, [ + {cover_enabled, true}, + {deps, []}, + {dialyzer, [ + {plt_extra_apps, [eunit, common_test]} + ]} ]} ]}. -{plugins, [ +{project_plugins, [ + {rebar3_lint, "1.0.1"}, {erlfmt, "1.0.0"}, - {rebar3_thrift_compiler, {git, "https://github.com/rbkmoney/rebar3_thrift_compiler.git", {tag, "0.3.1"}}} + {covertool, "2.0.4"} ]}. +%% Linter config. +{elvis_output_format, colors}. + {erlfmt, [ {print_width, 120}, - {files, [ - "apps/*/{src,include,test}/*.{hrl,erl,app.src}", - "rebar.config", - "elvis.config" - ]} + {files, ["apps/*/{src,include,test}/*.{hrl,erl}", "rebar.config", "elvis.config", "config/sys.config"]} ]}. -{overrides, [ - {override, rebar3_protobuffs_plugin, [ - {deps, [ - {protobuffs, {git, "https://github.com/basho/erlang_protobuffs.git", {tag, "0.8.2"}}} - ]} - ]}, - {override, protobuffs, [ - {deps, []} - ]}, - {override, hamcrest, [ - {plugins, []} +{covertool, [ + {coverdata_files, [ + "eunit.coverdata", + "ct.coverdata" ]} ]}. diff --git a/rebar.lock b/rebar.lock index ccfff33..26fdd79 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,11 +1,10 @@ {"1.2.0", -[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2}, - {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, +[{<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, {<<"cds_proto">>, - {git,"https://github.com/rbkmoney/cds-proto.git", - {ref,"74a763b2ba3f45753a0c2c73048906ac95ff16e5"}}, + {git,"https://github.com/valitydev/cds-proto.git", + {ref,"ed9f9078049ebcd1439d2ada0479fc8f33ccacf1"}}, 0}, - {<<"certifi">>,{pkg,<<"certifi">>,<<"2.6.1">>},2}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, {git,"https://github.com/rbkmoney/cg_mon.git", {ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}}, @@ -13,98 +12,85 @@ {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},1}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"erl_health">>, - {git,"https://github.com/rbkmoney/erlang-health.git", + {git,"https://github.com/valitydev/erlang-health.git", {ref,"5958e2f35cd4d09f40685762b82b82f89b4d9333"}}, 0}, {<<"genlib">>, - {git,"https://github.com/rbkmoney/genlib.git", - {ref,"2bbc54d4abe0f779d57c8f5911dce64d295b1cd1"}}, + {git,"https://github.com/valitydev/genlib.git", + {ref,"82c5ff3866e3019eb347c7f1d8f1f847bed28c10"}}, 0}, {<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1}, - {<<"hackney">>,{pkg,<<"hackney">>,<<"1.17.4">>},1}, + {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},1}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2}, - {<<"jose">>,{pkg,<<"jose">>,<<"1.11.2">>},0}, + {<<"jose">>, + {git,"https://github.com/potatosalad/erlang-jose.git", + {ref,"991649695aaccd92c8effb1c1e88e6159fe8e9a6"}}, + 0}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}, {<<"lib_combin">>,{pkg,<<"lib_combin">>,<<"0.1.5">>},0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, {<<"msgpack">>, - {git,"https://github.com/rbkmoney/msgpack-erlang", + {git,"https://github.com/valitydev/msgpack-erlang", {ref,"9d56647ed77498c7655da39891c4985142697083"}}, 0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, - {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.8.1">>},0}, - {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0}, - {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1}, {<<"proper">>,{pkg,<<"proper">>,<<"1.3.0">>},1}, - {<<"quantile_estimator">>,{pkg,<<"quantile_estimator">>,<<"0.2.1">>},1}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, {<<"scoper">>, - {git,"https://github.com/rbkmoney/scoper.git", + {git,"https://github.com/valitydev/scoper.git", {ref,"7f3183df279bc8181efe58dafd9cae164f495e6f"}}, 0}, {<<"shamir">>, - {git,"https://github.com/rbkmoney/shamir.git", + {git,"https://github.com/valitydev/shamir.git", {ref,"8ac011408f20891dddecedf15243109bdf479725"}}, 0}, {<<"snowflake">>, - {git,"https://github.com/rbkmoney/snowflake.git", + {git,"https://github.com/valitydev/snowflake.git", {ref,"de159486ef40cec67074afe71882bdc7f7deab72"}}, 1}, {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},2}, {<<"thrift">>, - {git,"https://github.com/rbkmoney/thrift_erlang.git", - {ref,"846a0819d9b6d09d0c31f160e33a78dbad2067b4"}}, + {git,"https://github.com/valitydev/thrift_erlang.git", + {ref,"c280ff266ae1c1906fb0dcee8320bb8d8a4a3c75"}}, 1}, {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, {<<"woody">>, - {git,"https://github.com/rbkmoney/woody_erlang.git", - {ref,"330bdcf71e99c2ea7aed424cd718939cb360ec1c"}}, + {git,"https://github.com/valitydev/woody_erlang.git", + {ref,"3ddacb9296691aa8ddad05498d1fd34b078eda75"}}, 0}]}. [ {pkg_hash,[ - {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, - {<<"certifi">>, <<"DBAB8E5E155A0763EEA978C913CA280A6B544BFA115633FA20249C3D396D9493">>}, + {<<"certifi">>, <<"D4FB0A6BB20B7C9C3643E22507E42F356AC090A1DCEA9AB99E27E0376D695EBA">>}, {<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>}, {<<"cowlib">>, <<"0B9FF9C346629256C42EBE1EEB769A83C6CB771A6EE5960BD110AB0B9B872063">>}, {<<"gproc">>, <<"853CCB7805E9ADA25D227A157BA966F7B34508F386A3E7E21992B1B484230699">>}, - {<<"hackney">>, <<"99DA4674592504D3FB0CFEF0DB84C3BA02B4508BAE2DFF8C0108BAA0D6E0977C">>}, + {<<"hackney">>, <<"C4443D960BB9FBA6D01161D01CD81173089686717D9490E5D3606644C48D121F">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, - {<<"jose">>, <<"F4C018CCF4FDCE22C71E44D471F15F723CB3EFAB5D909AB2BA202B5BF35557B3">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, {<<"lib_combin">>, <<"00F241FDCB6AFFFC1DE109A61A216C07E4E51C52CCB165656565BF660E7C78EB">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, - {<<"prometheus">>, <<"FA76B152555273739C14B06F09F485CF6D5D301FE4E9D31B7FF803D26025D7A0">>}, - {<<"prometheus_cowboy">>, <<"CFCE0BC7B668C5096639084FCD873826E6220EA714BF60A716F5BD080EF2A99C">>}, - {<<"prometheus_httpd">>, <<"F616ED9B85B536B195D94104063025A91F904A4CFC20255363F49A197D96C896">>}, {<<"proper">>, <<"C1ACD51C51DA17A2FE91D7A6FC6A0C25A6A9849D8DC77093533109D1218D8457">>}, - {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, {<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>}, {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, {pkg_hash_ext,[ - {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, - {<<"certifi">>, <<"524C97B4991B3849DD5C17A631223896272C6B0AF446778BA4675A1DFF53BB7E">>}, + {<<"certifi">>, <<"6AC7EFC1C6F8600B08D625292D4BBF584E14847CE1B6B5C44D983D273E1097EA">>}, {<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>}, {<<"cowlib">>, <<"2B3E9DA0B21C4565751A6D4901C20D1B4CC25CBB7FD50D91D2AB6DD287BC86A9">>}, {<<"gproc">>, <<"587E8AF698CCD3504CF4BA8D90F893EDE2B0F58CABB8A916E2BF9321DE3CF10B">>}, - {<<"hackney">>, <<"DE16FF4996556C8548D512F4DBE22DD58A587BF3332E7FD362430A7EF3986B16">>}, + {<<"hackney">>, <<"9AFCDA620704D720DB8C6A3123E9848D09C87586DC1C10479C42627B905B5C5E">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, - {<<"jose">>, <<"98143FBC48D55F3A18DABA82D34FE48959D44538E9697C08F34200FA5F0947D2">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, {<<"lib_combin">>, <<"D565215B9E5B3B1EF0B1A5DEDD0CC41AEA0814D6EAD6191584B5961C9DB874D5">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, - {<<"prometheus">>, <<"6EDFBE928D271C7F657A6F2C46258738086584BD6CAE4A000B8B9A6009BA23A5">>}, - {<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>}, - {<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>}, {<<"proper">>, <<"4AA192FCCDDD03FDBE50FEF620BE9D4D2F92635B54F55FB83AEC185994403CBC">>}, - {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, {<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>}, {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]}