diff --git a/.github/assets/logo.jpg b/.github/assets/logo.jpg new file mode 100644 index 00000000..e44d8878 Binary files /dev/null and b/.github/assets/logo.jpg differ diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..3198c0f1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml new file mode 100644 index 00000000..e3098b8b --- /dev/null +++ b/.github/workflows/helm.yaml @@ -0,0 +1,58 @@ +--- +name: helm +on: + - push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: set up helm + uses: azure/setup-helm@v4 + with: + version: v3.13.0 + + - uses: actions/setup-python@v5 + with: + python-version: 3.7 + + - name: set up chart-testing + uses: helm/chart-testing-action@v2.6.1 + + - name: run chart-testing (lint) + run: ct lint --all + + - name: create kind cluster + uses: helm/kind-action@v1.10.0 + + - name: run chart-testing (install) + run: ct install --all + + release: + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: set up helm + uses: azure/setup-helm@v4 + with: + version: v3.13.0 + + - name: login to github container registry using helm + run: | + echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io/snapp-incubator/soteria --username ${{ github.repository_owner }} --password-stdin + + - name: package soteria helm chart + run: | + version=${{ github.ref_name }} + helm package --version "${version##v}" --app-version "${version}" ./charts/soteria + + - name: publish soteria chart to github container registry + run: | + version=${{ github.ref_name }} + helm push "soteria-${version##v}".tgz oci://ghcr.io/snapp-incubator/soteria-chart diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..4e1ca3ac --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,61 @@ +--- +name: test +on: + - push + +jobs: + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + args: --enable-all --timeout=30m + + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + - run: go test -v ./... -covermode=atomic -coverprofile=coverage.out + - uses: codecov/codecov-action@v4.5.0 + with: + files: coverage.out + token: ${{ secrets.CODECOV_TOKEN }} + slug: snapp-incubator/soteria + + docker: + runs-on: ubuntu-latest + needs: + - lint + - test + if: github.event_name != 'pull_request' + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/bake-action@v5 + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + env: + TAG: ${{ github.ref_name }} + with: + push: true + files: 'build/package/docker-bake.json' + - uses: docker/bake-action@v5 + if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + with: + push: true + files: 'build/package/docker-bake.json' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index cdf2ae0b..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -image: registry.snapp.tech/docker/golang:1.11.4-alpine3.8 - -stages: - - lint - - pre-compile - - compile - - test - - build - - release - - deploy - -variables: - BUILD_PATH: "./" - OKD_TEH1_CLUSTER_ADDRESS: "okd.private.teh-1.snappcloud.io" - OKD_TEH2_CLUSTER_ADDRESS: "okd.private.teh-2.snappcloud.io" - OKD_TEH1_REGISTRY: 'registry.apps.private.teh-1.snappcloud.io' - OKD_TEH2_REGISTRY: 'registry.apps.private.teh-2.snappcloud.io' - PROJECT_SERVICE_NAME: 'soteria' - PROJECT_STAGING: 'realtime-staging' - PROJECT_PRODUCTION_TEH1: 'realtime-production' - PROJECT_PRODUCTION_TEH2: 'rtc' - PROJECT_MOZART: 'mozart' - PROJECT_VERSION: "${CI_COMMIT_SHA}" - - -include: - - local: .gitlab/ci/templates/precompile.giltab-ci.yml - - local: .gitlab/ci/templates/compile.giltab-ci.yml - - local: .gitlab/ci/templates/test.giltab-ci.yml - - local: .gitlab/ci/templates/build.giltab-ci.yml - - local: .gitlab/ci/templates/release.giltab-ci.yml - - local: .gitlab/ci/templates/deploy.vm.giltab-ci.yml - - local: .gitlab/ci/templates/deploy.env.giltab-ci.yml - - local: .gitlab/ci/templates/deploy.cloud.giltab-ci.yml - - local: .gitlab/ci/templates/deploy.chart.giltab-ci.yml - - - project: dispatching/igniteg/easy-ci - ref: master - file: "easy-ci.yml" - - - project: templates/gitlab-templates - file: sorush.gitlab-ci.yml diff --git a/.gitlab/ci/env/env.conf b/.gitlab/ci/env/env.conf deleted file mode 100644 index 5bdbaa25..00000000 --- a/.gitlab/ci/env/env.conf +++ /dev/null @@ -1,30 +0,0 @@ -# Application: Soteria -# Last update: $(date) -# -GIN_MODE=release -SOTERIA_ALLOWED_ACCESS_TYPES='sub,pub' -SOTERIA_CACHE_ENABLED='true' -SOTERIA_CACHE_EXPIRATION=600s -SOTERIA_GRPC_PORT='50051' -SOTERIA_HTTP_PORT='9999' -SOTERIA_LOGGER_LEVEL=ERROR -SOTERIA_LOGGER_SENTRY_DSN='' -SOTERIA_LOGGER_SENTRY_ENABLED='false' -SOTERIA_LOGGER_SENTRY_TIMEOUT=500ms -SOTERIA_REDIS_ADDRESS='emqx-redis-01.app.afra.snapp.infra:6379' -SOTERIA_REDIS_IDLE_CHECK_FREQUENCY=60s -SOTERIA_REDIS_IDLE_TIMEOUT=300s -SOTERIA_REDIS_MAX_RETRIES='0' -SOTERIA_REDIS_MAX_RETRY_BACKOFF=512ms -SOTERIA_REDIS_MIN_IDLE_CONNECTIONS='5' -SOTERIA_REDIS_MIN_RETRY_BACKOFF=8ms -SOTERIA_REDIS_PASS='' -SOTERIA_REDIS_POOL_SIZE='10' -SOTERIA_REDIS_POOL_TIMEOUT=4s -SOTERIA_REDIS_READ_TIMEOUT=3s -SOTERIA_REDIS_SET_MEMBER_EXP_TIME=300s -SOTERIA_DRIVER_SALT='___' -SOTERIA_PASSENGER_SALT='___' -SOTERIA_JWT_KEYS_PATH="/var/lib/soteria/jwt/" -SOTERIA_PASSENGER_HASH_LENGTH=15 -SOTERIA_DRIVER_HASH_LENGTH=15 diff --git a/.gitlab/ci/scripts/deploy.sh b/.gitlab/ci/scripts/deploy.sh deleted file mode 100644 index 708a4039..00000000 --- a/.gitlab/ci/scripts/deploy.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash -set -eu - -APP_NAME=soteria -APP_USERNAME=soteria -APP_HOME_PATH=/var/lib/${APP_USERNAME} -APP_PATH=/usr/local/bin/${APP_NAME}/ -APP_GREEN_PATH=/usr/local/bin/${APP_NAME}/${APP_NAME}-green -APP_RELEASES_PATH=${APP_HOME_PATH}/releases -APP_LATEST_RELEASE_PATH=${APP_RELEASES_PATH}/"$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA" -APP_HOSTNAME=${APP_HOSTNAME:?Please specify "APP_HOSTNAME", e.g. "APP_HOSTNAME=emqx-01.app.afra.snapp.infra"}; - -#TOTAL_APP_NODES=5 -UNHEALTHY_NODE_EXISTS=0 - -cat << EOF > /etc/resolv.conf -nameserver 172.16.76.22 -nameserver 172.21.49.230 -EOF - -mkdir -p /root/.ssh && touch /root/.ssh/id_rsa -echo "$DEPLOYER_PRIVATE_KEY" > /root/.ssh/id_rsa -chmod 0600 /root/.ssh/id_rsa - - -echo -e "\e[33m# Deployment on \e[1m$APP_HOSTNAME ...\e[0m" - -if ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" "stat $APP_RELEASES_PATH/$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA/$APP_NAME > /dev/null 2>&1" -then - echo -e "\e[33m# The release has alredy exist.\e[0m" -else - echo -e "\e[33m# Sending Artifact to Server\e[0m" - rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz "./artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz" "$APP_USERNAME@$APP_HOSTNAME:$APP_HOME_PATH" -fi - - -ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" " - mkdir -p ${APP_RELEASES_PATH} ${APP_LATEST_RELEASE_PATH} - - if [ -f "${APP_HOME_PATH}/artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz" ]; then - echo -e '\e[33m# Extract Release\e[0m' - tar -xzf ${APP_HOME_PATH}/artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz -C ${APP_LATEST_RELEASE_PATH} - rm -f ${APP_HOME_PATH}/artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz - fi - - " - -echo -e "\e[33m# Trying to connect to 'http://$APP_HOSTNAME' ...\e[0m" -timeout=60 - - -#done - -echo -echo - -#for i in $(seq 1 ${TOTAL_APP_NODES}); do -# APP_HOSTNAME=$(printf "${APP_NAME}-%02d.app.afra.snapp.infra" ${i}) -echo -e "\e[33m# Connecting to Server\e[0m" - echo -e "\e[33m# Connecting to Server \e[1m$APP_HOSTNAME\e[0m" - ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" " - echo -e '\e[33m# Activating Latest Realese\e[0m' - ln -sf ${APP_LATEST_RELEASE_PATH}/$APP_USERNAME ${APP_PATH} - - echo -e '\e[33m# Restarting $APP_NAME ...\e[0m' - sudo systemctl restart $APP_NAME.service - - " - echo -#done diff --git a/.gitlab/ci/scripts/notify-eye.sh b/.gitlab/ci/scripts/notify-eye.sh deleted file mode 100644 index e6347ca1..00000000 --- a/.gitlab/ci/scripts/notify-eye.sh +++ /dev/null @@ -1,14 +0,0 @@ -# To notify matrix about CI job - -MATRIX_SERVER="https://matrix.snapp.cab" -MATRIX_MSQTYPE=m.text -MX_TXN="`date "+%s"`$(( RANDOM % 9999 ))" - - -BODY="[Gitlab🚀] CI run by *$GITLAB_USER_LOGIN* on the $CI_PROJECT_TITLE project: \n Job name : $CI_JOB_NAME \n Commit : $CI_COMMIT_TITLE \n Job url : $CI_JOB_URL " - -# Post into maint room -curl -X PUT --header 'Content-Type: application/json' --header 'Accept: application/json' -d "{ -\"msgtype\": \"$MATRIX_MSQTYPE\", -\"body\": \"$BODY\" - }" "$MATRIX_SERVER/_matrix/client/unstable/rooms/$MATRIX_ROOM_ID/send/m.room.message/$MX_TXN?access_token=$MATRIX_ACCESS_TOKEN" >/dev/nul 2>&1 diff --git a/.gitlab/ci/scripts/update-environments.sh b/.gitlab/ci/scripts/update-environments.sh deleted file mode 100644 index 50ddbb68..00000000 --- a/.gitlab/ci/scripts/update-environments.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -set -eu - -APP_NAME=soteria -APP_USERNAME=soteria -APP_HOSTNAME="emqx-0${CI_NODE_INDEX}.app.afra.snapp.infra" -APP_HOME_PATH=/var/lib/${APP_USERNAME} -APP_CONFIG_PATH="/etc/soteria" -ENV_DEFAULT_FILE=.gitlab/ci/env/env.conf -ENV_HOSTNAME=$APP_HOSTNAME -ENV_SOTERIA_JWT_KEYS_PATH=${ENV_SOTERIA_JWT_KEYS_PATH} -ENV_THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION=${THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION} -ENV_PASSENGER_JWT_PUBLIC_KEY_PRODUCTION=${ENV_PASSENGER_JWT_PUBLIC_KEY_PRODUCTION} -ENV_DRIVER_JWT_PUBLIC_KEY_PRODUCTION=${DRIVER_JWT_PUBLIC_KEY_PRODUCTION} -ENV_THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION=${THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION} - -cat << EOF > /etc/resolv.conf -nameserver 172.16.76.22 -nameserver 172.21.49.230 -EOF - -mkdir -p /root/.ssh && touch /root/.ssh/id_rsa -echo "$DEPLOYER_PRIVATE_KEY" > /root/.ssh/id_rsa -chmod 0600 /root/.ssh/id_rsa - -sed -i "2s/\(.*\)/\#Last update\:\ $(date)/" $ENV_DEFAULT_FILE - - -while read LINE -do - - - if echo $LINE | egrep -v "^#|^$" >/dev/null 2>&1 - then - - ENV="$(echo $LINE | sed 's/\(.*\)=\(.*\)/\1/g' )" - ENV_NAME="ENV_$ENV" - ENV_VALUE="$(echo $LINE | sed 's/\(.*\)=\(.*\)/\2/g' )" - - echo -e "\e[33m# Ckeck variable \e[1m$ENV_NAME\e[0m" - - if [ -v $ENV_NAME ] - then - - if [ $ENV_VALUE = "'___'" ] - then - - sed -i "s/$ENV\(\s\)*=\(\s\)*'\(.*\)'/$ENV=\'${!ENV_NAME}\'/g" $ENV_DEFAULT_FILE - [ $? -eq 0 ] && echo -e "\e[32m==> Changed variable \e[1m$ENV_NAME\e[0m \e[32mfrom \e[1m$ENV_VALUE\e[0m \e[32mto \e[1m😜\e[0m " - - else - - sed -i "s/$ENV\(\s\)*=\(\s\)*'\(.*\)'/$ENV=\'${!ENV_NAME}\'/g" $ENV_DEFAULT_FILE - [ $? -eq 0 ] && echo -e "\e[32m==> Changed variable \e[1m$ENV_NAME\e[0m \e[32mfrom \e[1m$ENV_VALUE\e[0m \e[32mto \e[1m${!ENV_NAME}\e[0m " - fi - - fi - - fi - -done < $ENV_DEFAULT_FILE - - - - rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz "$ENV_DEFAULT_FILE" "$APP_USERNAME@$APP_HOSTNAME:$APP_CONFIG_PATH/$APP_NAME.conf" - [ $? -eq 0 ] && echo -e "\e[32m# Connecting to Server \e[1m$APP_HOSTNAME\e[0m \e[32m and update \e[1m$APP_CONFIG_PATH/$APP_NAME.conf\e[0m" - echo - - if [ -v UPDATE_ENVIRONMENT_VARIABLE ] - then - ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" " - - echo -e '\e[33m# Restarting $APP_NAME ...\e[0m' - sudo systemctl restart $APP_NAME.service - - " - fi - -## JWT keys - -mkdir jwt -echo "${ENV_DRIVER_JWT_PUBLIC_KEY_PRODUCTION}" > "jwt"/0.pem -echo "${ENV_PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}" > "jwt"/1.pem -echo "${ENV_THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}" > "jwt"/100.pem -echo "${ENV_THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}" > "jwt"/100.private.pem -rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz ./jwt/* "$APP_USERNAME@$APP_HOSTNAME:${ENV_SOTERIA_JWT_KEYS_PATH}" -rm -r jwt - -### Green service config file -# sed -i "1s/\(.*\)/\# Application: $APP_NAME-green /" $ENV_DEFAULT_FILE -sed -i "2s/\(.*\)/\# Last update: $(date) /" $ENV_DEFAULT_FILE -sed -i "s/SOTERIA_HTTP_PORT\(\s\)*=\(\s\)*'\(.*\)'/ENV_NAME=\'9998\'/g" $ENV_DEFAULT_FILE -rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz "$ENV_DEFAULT_FILE" "$APP_USERNAME@$APP_HOSTNAME:$APP_CONFIG_PATH/$APP_NAME-green.conf" diff --git a/.gitlab/ci/templates/build.giltab-ci.yml b/.gitlab/ci/templates/build.giltab-ci.yml deleted file mode 100644 index b2a6553b..00000000 --- a/.gitlab/ci/templates/build.giltab-ci.yml +++ /dev/null @@ -1,23 +0,0 @@ -build: - image: docker:latest - stage: build - variables: - HTTP_PROXY: ${SNAPP_HTTP_PROXY} - HTTPS_PROXY: ${SNAPP_HTTPS_PROXY} - no_proxy: ${SNAPP_NOPROXY} - script: - - export CURRENT_DATETIME=$(TZ=Asia/Tehran date '+%FT%T') - - docker build --build-arg BUILD_DATE=$CURRENT_DATETIME --build-arg VCS_REF=${CI_COMMIT_SHA} --build-arg BUILD_VERSION=${CI_COMMIT_REF_SLUG} -t ${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG} . - after_script: - - docker save -o ${CI_PROJECT_NAME}-${CI_COMMIT_REF_SLUG}.tar ${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG} - artifacts: - name: "docker-image-$CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG" - paths: - - ${CI_PROJECT_NAME}-${CI_COMMIT_REF_SLUG}.tar - expire_in: 1 week - dependencies: - - code-generator - - compile - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE \ No newline at end of file diff --git a/.gitlab/ci/templates/compile.giltab-ci.yml b/.gitlab/ci/templates/compile.giltab-ci.yml deleted file mode 100644 index 03dae838..00000000 --- a/.gitlab/ci/templates/compile.giltab-ci.yml +++ /dev/null @@ -1,28 +0,0 @@ - -.go_mod_config: &go_mod - image: registry.snapp.tech/docker/golang:1.13.5-alpine3.10 - cache: - policy: pull - paths: - - vendor/ - before_script: - - go env -w GOPROXY="https://repo.snapp.tech/repository/goproxy/" - -compile: - <<: *go_mod - stage: compile - variables: - GOOS: "linux" - GOARCH: "amd64" - CGO_ENABLED: 0 - script: - - go mod vendor -v - - go build -mod vendor -v -o ${BUILD_PATH}/${CI_PROJECT_NAME} cmd/soteria/soteria.go - artifacts: - name: "$CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG" - paths: - - ${BUILD_PATH}/${CI_PROJECT_NAME} - expire_in: 1 week - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE diff --git a/.gitlab/ci/templates/deploy.chart.giltab-ci.yml b/.gitlab/ci/templates/deploy.chart.giltab-ci.yml deleted file mode 100644 index 3ac95901..00000000 --- a/.gitlab/ci/templates/deploy.chart.giltab-ci.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -lint:helm:soteria: - stage: lint - extends: .easy_ci:helm:lint - variables: - HELM_CHARTS_DIR: "deployments" - only: - - tags - when: manual - -package:helm:soteria: - stage: build - extends: .easy_ci:helm:package - variables: - HELM_CHARTS_DIR: "deployments" - HELM_PACKAGE_DIR: "helm_packages" - needs: ["lint:helm:soteria"] - artifacts: - name: "HELM_PACKAGES-$CI_COMMIT_REF_SLUG" - paths: - - $HELM_PACKAGE_DIR - expire_in: 1h - only: - - tags - when: manual - -chart:helm::soteria: - stage: release - extends: .easy_ci:helm:push - variables: - HELM_PACKAGE_DIR: "helm_packages" - HELM_REPO_ADDRESS: "$DISPATCHING_HELM_REPO_ADDRESS" - HELM_REPO_USER: "$DISPATCHING_HELM_REPO_USER" - HELM_REPO_PASS: "$DISPATCHING_HELM_REPO_PASS" - needs: ["package:helm:soteria"] - only: - - tags - when: manual diff --git a/.gitlab/ci/templates/deploy.cloud.giltab-ci.yml b/.gitlab/ci/templates/deploy.cloud.giltab-ci.yml deleted file mode 100644 index e82f44a8..00000000 --- a/.gitlab/ci/templates/deploy.cloud.giltab-ci.yml +++ /dev/null @@ -1,124 +0,0 @@ -.deploy_template: &deploy_job_definition - image: openshift/origin-cli:v3.10 - stage: deploy - before_script: - - oc version - - oc login https://${DEPLOY_ADDRESS} --token=${DEPLOY_TOKEN} - - oc project ${DEPLOY_PROJECT} - - - oc process -f .okd/DeploymentConfig.yaml --param APP_IMAGE=${CONTAINER_REGISTRY_IMAGE} --param SERVICE_NAME=${DEPLOY_SERVICE_NAME} | oc apply -f - - - | - oc process -f .okd/ConfigMap.yaml \ - --param SERVICE_NAME="${DEPLOY_SERVICE_NAME}" \ - --param APP_VERSION="${PROJECT_VERSION}" \ - --param REDIS_ADDR="${DEPLOY_REDIS_ADDR}" \ - --param REDIS_PASS="${DEPLOY_REDIS_PASS}" \ - --param TRACER_ENABLED=${DEPLOY_TRACER_ENABLED} \ - --param TRACER_HOST=${DEPLOY_TRACER_HOST} \ - --param CI_COMMIT_REF_SLUG="${CI_COMMIT_REF_SLUG}" | oc apply -f - - - | - oc process -f .okd/Secret.yaml \ - --param SERVICE_NAME="${DEPLOY_SERVICE_NAME}" \ - --param APP_VERSION="${PROJECT_VERSION}" \ - --param CI_COMMIT_REF_SLUG="${CI_COMMIT_REF_SLUG}" \ - --param PASSENGER_HASH_SALT="${DEPLOY_PASSENGER_SALT}" \ - --param PASSENGER_HASH_LENGTH=15 \ - --param DRIVER_HASH_SALT="${DEPLOY_DRIVER_SALT}" \ - --param DRIVER_HASH_LENGTH=15 \ - --param DRIVER_JWT_PUBLIC_KEY="${DEPLOY_DRIVER_JWT_PUBLIC_KEY}" \ - --param PASSENGER_JWT_PUBLIC_KEY="${DEPLOY_PASSENGER_JWT_PUBLIC_KEY}" \ - --param THIRD_PARTY_JWT_PUBLIC_KEY="${DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY}" \ - --param THIRD_PARTY_JWT_PRIVATE_KEY="${DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY}" | oc apply -f - - - | - oc process -f .okd/AutoScaler.yaml \ - --param MIN_REPLICA=${DEPLOY_MIN_REPLICA} \ - --param MAX_REPLICA=${DEPLOY_MAX_REPLICA} \ - --param CPU_UTILIZATION=${DEPLOY_CPU_UTILIZATION} | oc apply -f - - - oc process -f .okd/Route.yaml --param APP_HOST=${DEPLOY_SERVICE_NAME} --param SERVICE_NAME=${DEPLOY_SERVICE_NAME} | oc apply -f - - - oc process -f .okd/Service.yaml --param SERVICE_NAME=${DEPLOY_SERVICE_NAME} | oc apply -f - - script: - - oc rollout latest dc/${DEPLOY_SERVICE_NAME} - only: - - tags - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - when: manual - -deploy:staging:teh_1: - <<: *deploy_job_definition - variables: - DEPLOY_MIN_REPLICA: "1" - DEPLOY_MAX_REPLICA: "2" - DEPLOY_CPU_UTILIZATION: "80" - CONTAINER_REGISTRY_IMAGE: "$PROJECT_STAGING/${PROJECT_SERVICE_NAME}:$CI_COMMIT_REF_SLUG" - DEPLOY_ADDRESS: "${OKD_TEH1_CLUSTER_ADDRESS}" - DEPLOY_PROJECT: "${PROJECT_STAGING}" - DEPLOY_TOKEN: "${OKD_STAGING_TOKEN}" - DEPLOY_PASSENGER_SALT: "${PASSENGER_SALT_DEV}" - DEPLOY_DRIVER_SALT: "${DRIVER_SALT_DEV}" - DEPLOY_SERVICE_NAME: "${PROJECT_SERVICE_NAME}-staging" - DEPLOY_DRIVER_JWT_PUBLIC_KEY: "${DRIVER_JWT_PUBLIC_KEY_MOZART}" - DEPLOY_PASSENGER_JWT_PUBLIC_KEY: "${PASSENGER_JWT_PUBLIC_KEY_MOZART}" - DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY: "${THIRD_PARTY_JWT_PUBLIC_KEY_MOZART}" - DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY: "${THIRD_PARTY_JWT_PRIVATE_KEY_MOZART}" - DEPLOY_REDIS_ADDR: "soteria-redis-haproxy:6379" - DEPLOY_TRACER_ENABLED: "false" - DEPLOY_TRACER_HOST: "localhost" - - only: - - master - - branches - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - needs: ["release:staging"] - -deploy:production:teh_1: - <<: *deploy_job_definition -# Extends the global template so it add an after_script step for deployment announcement - extends: .deployments_after_script - variables: - DEPLOY_MIN_REPLICA: "3" - DEPLOY_MAX_REPLICA: "20" - DEPLOY_CPU_UTILIZATION: "65" - CONTAINER_REGISTRY_IMAGE: "$PROJECT_PRODUCTION_TEH1/$PROJECT_SERVICE_NAME:$CI_COMMIT_REF_SLUG" - DEPLOY_ADDRESS: "${OKD_TEH1_CLUSTER_ADDRESS}" - DEPLOY_PROJECT: "${PROJECT_PRODUCTION_TEH1}" - DEPLOY_TOKEN: "${OKD_PRODUCTION_TOKEN_TEH1}" - DEPLOY_PASSENGER_SALT: "${PASSENGER_SALT_PRODUCTION}" - DEPLOY_DRIVER_SALT: "${DRIVER_SALT_PRODUCTION}" - DEPLOY_SERVICE_NAME: "${PROJECT_SERVICE_NAME}" - DEPLOY_DRIVER_JWT_PUBLIC_KEY: "${DRIVER_JWT_PUBLIC_KEY_PRODUCTION}" - DEPLOY_PASSENGER_JWT_PUBLIC_KEY: "${PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}" - DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY: "${THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}" - DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY: "${THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}" - DEPLOY_REDIS_ADDR: "soteria-redis-haproxy.realtime-production.svc:6379" - CI_DEPLOYMENT_INFRA: "teh-1" - DEPLOY_TRACER_ENABLED: "false" - DEPLOY_TRACER_HOST: "localhost" - needs: ["release:production:teh-1"] - -deploy:production:teh_2: - <<: *deploy_job_definition -# Extends the global template so it add an after_script step for deployment announcement - extends: .deployments_after_script - variables: - DEPLOY_MIN_REPLICA: "3" - DEPLOY_MAX_REPLICA: "20" - DEPLOY_CPU_UTILIZATION: "70" - CONTAINER_REGISTRY_IMAGE: "$PROJECT_PRODUCTION_TEH2/$PROJECT_SERVICE_NAME:$CI_COMMIT_REF_SLUG" - DEPLOY_ADDRESS: "${OKD_TEH2_CLUSTER_ADDRESS}" - DEPLOY_PROJECT: "${PROJECT_PRODUCTION_TEH2}" - DEPLOY_TOKEN: "${OKD_PRODUCTION_TOKEN_TEH2}" - DEPLOY_PASSENGER_SALT: "${PASSENGER_SALT_PRODUCTION}" - DEPLOY_DRIVER_SALT: "${DRIVER_SALT_PRODUCTION}" - DEPLOY_SERVICE_NAME: "${PROJECT_SERVICE_NAME}" - DEPLOY_DRIVER_JWT_PUBLIC_KEY: "${DRIVER_JWT_PUBLIC_KEY_PRODUCTION}" - DEPLOY_PASSENGER_JWT_PUBLIC_KEY: "${PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}" - DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY: "${THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}" - DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY: "${THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}" - DEPLOY_REDIS_ADDR: "soteria-redis-haproxy.realtime-production.svc:6379" - CI_DEPLOYMENT_INFRA: "teh-2" - DEPLOY_TRACER_ENABLED: "false" - DEPLOY_TRACER_HOST: "localhost" - needs: ["release:production:teh-2"] diff --git a/.gitlab/ci/templates/deploy.env.giltab-ci.yml b/.gitlab/ci/templates/deploy.env.giltab-ci.yml deleted file mode 100644 index 8eaf3e89..00000000 --- a/.gitlab/ci/templates/deploy.env.giltab-ci.yml +++ /dev/null @@ -1,16 +0,0 @@ -update:env: - image: registry.snapp.tech/docker/deployer:alpine3.11 - stage: deploy - parallel: 5 - variables: - DEPLOYER_PRIVATE_KEY: ${DEPLOYER_PRIVATE_KEY} - script: - - chmod +x .gitlab/ci/scripts/update-environments.sh - - echo "Update ENV Soteria-0${CI_NODE_INDEX}" - - .gitlab/ci/scripts/update-environments.sh - - chmod +x .gitlab/ci/scripts/notify-eye.sh - - echo "Send notif to Dispatching-alert room ..." - - .gitlab/ci/scripts/notify-eye.sh - rules: - - if: '$UPDATE_ENVIRONMENT_VARIABLE == "true"' - when: manual \ No newline at end of file diff --git a/.gitlab/ci/templates/deploy.vm.giltab-ci.yml b/.gitlab/ci/templates/deploy.vm.giltab-ci.yml deleted file mode 100644 index 4af5c32c..00000000 --- a/.gitlab/ci/templates/deploy.vm.giltab-ci.yml +++ /dev/null @@ -1,45 +0,0 @@ -deploy:production:vm: -# Extends the global template so it add an after_script step for deployment announcement - extends: .deployments_after_script - image: registry.snapp.tech/docker/deployer:alpine3.11 - stage: deploy - parallel: 5 - variables: -# Add a variable to specify where deployment will be happened for announcement - CI_DEPLOYMENT_INFRA: "Afranet" - DEPLOYER_PRIVATE_KEY: ${DEPLOYER_PRIVATE_KEY} - APP_HOSTNAME: "emqx-0${CI_NODE_INDEX}.app.afra.snapp.infra" - before_script: - - tar -C ${BUILD_PATH} -cvzf "artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz" ${CI_PROJECT_NAME} - script: - - chmod +x ./.gitlab/ci/scripts/deploy.sh - - echo "Deploy Soteria-0${CI_NODE_INDEX}" - - ./.gitlab/ci/scripts/deploy.sh - needs: ["compile"] - only: - - tag - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - when: manual - -deploy:env: - image: registry.snapp.tech/docker/deployer:alpine3.11 - stage: deploy - parallel: 5 - variables: - DEPLOYER_PRIVATE_KEY: ${DEPLOYER_PRIVATE_KEY} - script: - - chmod +x .gitlab/ci/scripts/update-environments.sh - - echo "Update ENV Soteria-0${CI_NODE_INDEX}" - - .gitlab/ci/scripts/update-environments.sh - - chmod +x .gitlab/ci/scripts/notify-eye.sh - - echo "Send notif to Dispatching-alert room ..." - - .gitlab/ci/scripts/notify-eye.sh - needs: ['compile'] - only: - - tag - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - when: manual diff --git a/.gitlab/ci/templates/precompile.giltab-ci.yml b/.gitlab/ci/templates/precompile.giltab-ci.yml deleted file mode 100644 index 1dceda4c..00000000 --- a/.gitlab/ci/templates/precompile.giltab-ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -code-generator: - stage: pre-compile - image: - name: namely/protoc-all - entrypoint: [""] - script: - - entrypoint.sh -d ./internal/web/grpc/contracts -l go -o ./internal/web/grpc/contracts --with-gateway --go-source-relative - artifacts: - paths: - - ./internal/web/grpc/contracts - expire_in: 1 week -# cache: -# paths: -# - ./web/grpc/contracts - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE \ No newline at end of file diff --git a/.gitlab/ci/templates/release.giltab-ci.yml b/.gitlab/ci/templates/release.giltab-ci.yml deleted file mode 100644 index 17bc823f..00000000 --- a/.gitlab/ci/templates/release.giltab-ci.yml +++ /dev/null @@ -1,97 +0,0 @@ ---- -release:staging: - extends: .easy_ci:release:docker - variables: - RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH1_REGISTRY" - RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH1_REGISTRY/$PROJECT_STAGING/$CI_PROJECT_NAME" - RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci" - RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_STAGING_TOKEN" - dependencies: - - build - only: - - master - - tags - - branches - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - -release:mozart: - extends: .easy_ci:release:docker - variables: - RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH1_REGISTRY" - RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH1_REGISTRY/$PROJECT_MOZART/$CI_PROJECT_NAME" - RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci" - RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_MOZART_TOKEN" - dependencies: - - build - only: - - master - - tags - - branches - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - when: manual - -release:production:teh-1: - extends: .easy_ci:release:docker - variables: - RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH1_REGISTRY" - RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH1_REGISTRY/$PROJECT_PRODUCTION_TEH1/$CI_PROJECT_NAME" - RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci" - RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_PRODUCTION_TOKEN_TEH1" - dependencies: - - build - only: - - tags - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - -release:production:teh-2: - extends: .easy_ci:release:docker - variables: - RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH2_REGISTRY" - RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH2_REGISTRY/$PROJECT_PRODUCTION_TEH2/$CI_PROJECT_NAME" - RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci" - RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_PRODUCTION_TOKEN_TEH2" - dependencies: - - build - only: - - tags - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - -release:production:okd4:teh-1: - extends: .easy_ci:release:docker - variables: - OKD4_TEH1_REGISTRY: "image-registry.apps.private.okd4.teh-1.snappcloud.io" - RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD4_TEH1_REGISTRY" - RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD4_TEH1_REGISTRY/realtime-production/$CI_PROJECT_NAME" - RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci" - RELEASE_CONTAINER_REGISTRY_PASSWORD: ${OKD4_TEH1_REALTIME_PRODUCTION_TOKEN} - dependencies: - - build - only: - - tags - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE - -release:production:baly: - extends: .easy_ci:release:docker - variables: - BALY_REGISTRY: "registry.cloud.baly.app" - RELEASE_CONTAINER_REGISTRY_ADDRESS: "$BALY_REGISTRY" - RELEASE_CONTAINER_REGISTRY_IMAGE: "$BALY_REGISTRY/baly-dispatching/$CI_PROJECT_NAME" - RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci" - RELEASE_CONTAINER_REGISTRY_PASSWORD: ${BALY_DISPATCHING_PRODUCTION_TOKEN} - dependencies: - - build - only: - - tags - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE diff --git a/.gitlab/ci/templates/test.giltab-ci.yml b/.gitlab/ci/templates/test.giltab-ci.yml deleted file mode 100644 index 58e9c092..00000000 --- a/.gitlab/ci/templates/test.giltab-ci.yml +++ /dev/null @@ -1,25 +0,0 @@ - -.go_mod_config: &go_mod - image: registry.snapp.tech/docker/golang:1.13.5-alpine3.10 - cache: - policy: pull - paths: - - vendor/ - before_script: - - go env -w GOPROXY="https://repo.snapp.tech/repository/goproxy/" - -unit_tests: - <<: *go_mod - stage: test - coverage: '/total:\s*\(statements\)\s*([\d.]+)%/' - script: - - go test -gcflags=-l -v -coverprofile .coverage.out.tmp ./... - - cat .coverage.out.tmp | grep -v "mock.go" > .coverage.out - - rm -rf .coverage.out.tmp - - go tool cover -func .coverage.out - dependencies: - - code-generator - - compile - except: - variables: - - $UPDATE_ENVIRONMENT_VARIABLE \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..b2bc0bd4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,31 @@ +--- +linters: + enable-all: true + disable: + - depguard + # we don't use json with camel-case + - tagliatelle + - nolintlint + # it should improve to support more known patterns + - varnamelen + - ireturn + # deprecated linters + - gomnd + - execinquery + - exportloopref + +linters-settings: + wrapcheck: + ignoreSigs: + - .JSON + - .NewHTTPError + - .Redirect + - .NoContent + - .Errorf( + - errors.New( + - errors.Unwrap( + - .Wrap( + - .Wrapf( + - .WithMessage( + - .WithMessagef( + - .WithStack( diff --git a/.okd/.env b/.okd/.env deleted file mode 100644 index 33e14e43..00000000 --- a/.okd/.env +++ /dev/null @@ -1,80 +0,0 @@ -GJS_DEBUG_TOPICS=JS ERROR;JS LOG -LC_TIME=az_IR -USER=amin -TEXTDOMAIN=im-config -XDG_SEAT=seat0 -XDG_SESSION_TYPE=x11 -SSH_AGENT_PID=6340 -SHLVL=1 -QT4_IM_MODULE=xim -OLDPWD=/home/amin/Development/snapp/soteria/.okd -HOME=/home/amin -LESS=-R -DESKTOP_SESSION=ubuntu -_INTELLIJ_FORCE_SET_GOPATH=/home/amin/go -TERMINAL_EMULATOR=JetBrains-JediTerm -_INTELLIJ_FORCE_SET_GOPROXY=direct -GIO_LAUNCHED_DESKTOP_FILE=/home/amin/.local/share/applications/jetbrains-goland.desktop -ZSH=/home/amin/.oh-my-zsh -LSCOLORS=Gxfxcxdxbxegedabagacad -GTK_MODULES=gail:atk-bridge -GNOME_SHELL_SESSION_MODE=ubuntu -PAGER=less -LC_MONETARY=az_IR -GOROOT=/usr/local/go -DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus -GIO_LAUNCHED_DESKTOP_FILE_PID=15825 -GOFLAGS= -mod=vendor -IM_CONFIG_PHASE=2 -_INTELLIJ_FORCE_SET_PATH=/usr/local/go/bin:/home/amin/.cargo/bin:/home/amin/go/bin:/usr/local/go/bin:/home/amin/.local/bin:/home/amin/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/amin/Development/flutter/bin:/home/amin/go/bin -MANDATORY_PATH=/usr/share/gconf/ubuntu.mandatory.path -GO111MODULE=on -GTK_IM_MODULE=ibus -LOGNAME=amin -_=/home/amin/Development/snapp/soteria/.okd/./deploy.sh -DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path -USERNAME=amin -XDG_SESSION_ID=3 -TERM=xterm-256color -SERVICE_NAME=soteria -GNOME_DESKTOP_SESSION_ID=this-is-deprecated -WINDOWPATH=2 -PATH=/usr/local/go/bin:/home/amin/.cargo/bin:/home/amin/go/bin:/usr/local/go/bin:/home/amin/.local/bin:/home/amin/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/amin/Development/flutter/bin:/home/amin/go/bin -__INTELLIJ_COMMAND_HISTFILE__=/home/amin/.GoLand2019.3/config/terminal/history/history-254 -SESSION_MANAGER=local/amin-ThinkPad-E490:@/tmp/.ICE-unix/6244,unix/amin-ThinkPad-E490:/tmp/.ICE-unix/6244 -S_COLORS=auto -XDG_RUNTIME_DIR=/run/user/1000 -XDG_MENU_PREFIX=gnome- -LC_ADDRESS=az_IR -DISPLAY=:0 -LC_TELEPHONE=az_IR -XDG_CURRENT_DESKTOP=ubuntu:GNOME -LANG=en_US.UTF-8 -XDG_SESSION_DESKTOP=ubuntu -XAUTHORITY=/run/user/1000/gdm/Xauthority -XMODIFIERS=@im=ibus -LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36: -SSH_AUTH_SOCK=/run/user/1000/keyring/ssh -LC_NAME=az_IR -GOPROXY=direct -GOPATH=/home/amin/go -SHELL=/usr/bin/zsh -GDMSESSION=ubuntu -QT_ACCESSIBILITY=1 -LC_MEASUREMENT=az_IR -TEXTDOMAINDIR=/usr/share/locale/ -LC_IDENTIFICATION=az_IR -GPG_AGENT_INFO=/run/user/1000/gnupg/S.gpg-agent:0:1 -GJS_DEBUG_OUTPUT=stderr -_INTELLIJ_FORCE_SET_GOROOT=/usr/local/go -QT_IM_MODULE=ibus -XDG_VTNR=2 -PWD=/home/amin/Development/snapp/soteria/.okd -_INTELLIJ_FORCE_SET_GOFLAGS= -mod=vendor -ZDOTDIR= -XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg -XDG_DATA_DIRS=/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop -CLUTTER_IM_MODULE=xim -_INTELLIJ_FORCE_SET_GO111MODULE=on -LC_NUMERIC=az_IR -LC_PAPER=az_IR diff --git a/.okd/AutoScaler.yaml b/.okd/AutoScaler.yaml deleted file mode 100644 index ab27cb8b..00000000 --- a/.okd/AutoScaler.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-scaler-tpl -objects: -- apiVersion: autoscaling/v1 - kind: HorizontalPodAutoscaler - metadata: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - scaleTargetRef: - kind: DeploymentConfig - name: ${SERVICE_NAME} - apiVersion: v1 - subresource: scale - minReplicas: ${{MIN_REPLICA}} - maxReplicas: ${{MAX_REPLICA}} - targetCPUUtilizationPercentage: ${{CPU_UTILIZATION}} - -parameters: -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" -- name: MIN_REPLICA - displayName: Minimum number of replica - required: true -- name: MAX_REPLICA - displayName: Maximum number of replica - required: true -- name: CPU_UTILIZATION - displayName: Target CPU Utilization Percentage - required: true \ No newline at end of file diff --git a/.okd/ConfigMap.yaml b/.okd/ConfigMap.yaml deleted file mode 100644 index 00e8b4de..00000000 --- a/.okd/ConfigMap.yaml +++ /dev/null @@ -1,71 +0,0 @@ ---- -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-configmap -objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: ${SERVICE_NAME}-env - labels: - app: "${SERVICE_NAME}" - app.snappcloud.io/managed-by: "${MANAGED_BY}" - app.snappcloud.io/version: "${APP_VERSION}" - app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}" - data: - SOTERIA_HTTP_PORT: "9999" - SOTERIA_GRPC_PORT: "50051" - SOTERIA_REDIS_ADDRESS: "${REDIS_ADDR}" - SOTERIA_REDIS_PASS: "${REDIS_PASS}" - SOTERIA_REDIS_POOL_SIZE: "10" - SOTERIA_REDIS_MAX_RETRIES: "0" - SOTERIA_REDIS_READ_TIMEOUT: "3s" - SOTERIA_REDIS_POOL_TIMEOUT: "4s" - SOTERIA_REDIS_MIN_RETRY_BACKOFF: "8ms" - SOTERIA_REDIS_MAX_RETRY_BACKOFF: "512ms" - SOTERIA_REDIS_IDLE_TIMEOUT: "300s" - SOTERIA_REDIS_IDLE_CHECK_FREQUENCY: "60s" - SOTERIA_REDIS_SET_MEMBER_EXP_TIME: "300s" - SOTERIA_REDIS_MIN_IDLE_CONNECTIONS: "5" - SOTERIA_LOGGER_LEVEL: "${LOG_LEVEL}" - SOTERIA_LOGGER_SENTRY_ENABLED: "false" - SOTERIA_LOGGER_SENTRY_DSN: "" - SOTERIA_LOGGER_SENTRY_TIMEOUT: "500ms" - SOTERIA_ALLOWED_ACCESS_TYPES: "sub,pub" - SOTERIA_CACHE_ENABLED: "true" - SOTERIA_CACHE_EXPIRATION: "600s" - SOTERIA_TRACER_ENABLED: "${TRACER_ENABLED}" - SOTERIA_TRACER_SERVICE_NAME: "soteria" - SOTERIA_TRACER_SAMPLER_TYPE: "const" - SOTERIA_TRACER_SAMPLER_PARAM: "1" - SOTERIA_TRACER_HOST: "${TRACER_HOST}" - SOTERIA_TRACER_PORT: "6831" - SOTERIA_MODE: "${GIN_MODE}" - -parameters: -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) -- name: LOG_LEVEL - value: "ERROR" - displayName: Log Level - description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF) -- name: GIN_MODE - value: "release" -- name: REDIS_ADDR - required: true -- name: REDIS_PASS - required: false -- name: TRACER_ENABLED - value: "false" -- name: TRACER_HOST - required: false -- name: MANAGED_BY - displayName: Managed by - required: true - value: "dispatching-team" -- name: APP_VERSION - required: true -- name: CI_COMMIT_REF_SLUG - required: true diff --git a/.okd/DeploymentConfig.yaml b/.okd/DeploymentConfig.yaml deleted file mode 100644 index 4d540a03..00000000 --- a/.okd/DeploymentConfig.yaml +++ /dev/null @@ -1,116 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-deployment-config -objects: - - apiVersion: v1 - kind: DeploymentConfig - metadata: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - selector: - app: ${SERVICE_NAME} - strategy: - rollingParams: - intervalSeconds: 1 - maxSurge: 5 - maxUnavailable: 0 - timeoutSeconds: 600 - updatePeriodSeconds: 10 - type: Rolling - template: - metadata: - annotations: - prometheus.io/scrape: "${METRICS_ENABLED}" - prometheus.io/path: "/metrics" - prometheus.io/port: "9999" - labels: - app: ${SERVICE_NAME} - spec: - containers: - - name: ${SERVICE_NAME}-app - image: "docker-registry.default.svc:5000/${APP_IMAGE}" - imagePullPolicy: Always - ports: - - containerPort: 9999 - protocol: TCP - - containerPort: 50051 - protocol: TCP - resources: - limits: - memory: "${MEMORY_LIMIT}" - cpu: "${CPU_LIMIT}" - requests: - memory: "${MEMORY_REQUEST}" - cpu: "${CPU_REQUEST}" - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 23 - successThreshold: 1 - tcpSocket: - port: 9999 - timeoutSeconds: 2 - livenessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 29 - successThreshold: 1 - tcpSocket: - port: 9999 - timeoutSeconds: 2 - envFrom: - - secretRef: - name: "${SERVICE_NAME}-auth-env" - - configMapRef: - name: "${SERVICE_NAME}-env" - env: - - name: HTTP_PROXY - - name: HTTPS_PROXY - volumeMounts: - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/100.private.pem" - subPath: 100.private.pem - readOnly: true - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/100.pem" - subPath: 100.pem - readOnly: true - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/1.pem" - subPath: 1.pem - readOnly: true - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/0.pem" - subPath: 0.pem - readOnly: true - volumes: - - name: "${SERVICE_NAME}-jwt-keys" - secret: - secretName: "${SERVICE_NAME}-jwt-keys" - dnsPolicy: ClusterFirst - restartPolicy: Always - terminationGracePeriodSeconds: 30 - triggers: [] -parameters: - - name: APP_IMAGE - displayName: Application Docker Image - description: application will pull and run this docker image. It has to be in Snapp Cloud registery - value: "$CONTAINER_REGISTRY_IMAGE" - - name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - - name: MEMORY_LIMIT - value: "1024Mi" - - name: MEMORY_REQUEST - value: "512Mi" - - name: CPU_LIMIT - value: "2" - - name: CPU_REQUEST - value: "1" - - name: METRICS_ENABLED - value: "true" - displayName: Metrics Endpoint? - description: /metrics endpoint will be available for Prometheus diff --git a/.okd/Route.yaml b/.okd/Route.yaml deleted file mode 100644 index 18b499ad..00000000 --- a/.okd/Route.yaml +++ /dev/null @@ -1,62 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-route -objects: -- apiVersion: v1 - kind: Route - metadata: - labels: - app: ${SERVICE_NAME} - router: inter-dc - name: ${SERVICE_NAME}-inter-dc - spec: - host: ${APP_HOST}.apps.inter-dc.${DC}.snappcloud.io - port: - targetPort: 9999-tcp - to: - kind: Service - name: ${SERVICE_NAME} - weight: 100 - wildcardPolicy: None -- apiVersion: v1 - kind: Route - metadata: - annotations: - haproxy.router.openshift.io/ip_whitelist: "${IP_WHITE_LIST}" - labels: - app: ${SERVICE_NAME} - router: "public" - name: "${SERVICE_NAME}-publi" - spec: - host: "${APP_HOST}.apps.public.${DC}.snappcloud.io" - port: - targetPort: 9999-tcp - tls: - termination: edge - insecureEdgeTerminationPolicy: Redirect - to: - kind: Service - name: ${SERVICE_NAME} - weight: 100 - wildcardPolicy: None - -parameters: -- name: APP_HOST - required: true - displayName: Application host -- name: PORT - displayName: Port - description: the port which will be open by your application - value: "9999" -- name: IP_WHITE_LIST - displayName: IP White list - description: The valid IP Addresses which can send request to public route - value: "*" -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" -- name: DC - displayName: Data center - value: "teh-1" diff --git a/.okd/Secret.yaml b/.okd/Secret.yaml deleted file mode 100644 index fc84574c..00000000 --- a/.okd/Secret.yaml +++ /dev/null @@ -1,81 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: "${SERVICE_NAME}-secret" -objects: - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-auth-env" - labels: - app: "${SERVICE_NAME}" - app.snappcloud.io/managed-by: "${MANAGED_BY}" - app.snappcloud.io/version: "${APP_VERSION}" - app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}" - stringData: - SOTERIA_JWT_KEYS_PATH: "${JWT_KEYS_PATH}" - SOTERIA_DRIVER_SALT: "${DRIVER_HASH_SALT}" - SOTERIA_PASSENGER_SALT: "${PASSENGER_HASH_SALT}" - SOTERIA_DRIVER_HASH_LENGTH: "${DRIVER_HASH_LENGTH}" - SOTERIA_PASSENGER_HASH_LENGTH: "${PASSENGER_HASH_LENGTH}" - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-jwt-keys" - labels: - app: "${SERVICE_NAME}" - app.snappcloud.io/managed-by: "${MANAGED_BY}" - app.snappcloud.io/version: "${APP_VERSION}" - app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}" - stringData: - 0.pem: "${DRIVER_JWT_PUBLIC_KEY}" - 1.pem: "${PASSENGER_JWT_PUBLIC_KEY}" - 100.pem: "${THIRD_PARTY_JWT_PUBLIC_KEY}" - 100.private.pem: "${THIRD_PARTY_JWT_PRIVATE_KEY}" - -parameters: - - name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - - name: APP_VERSION - required: true - - name: MANAGED_BY - displayName: Managed by - required: true - value: "dispatching-team" - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - - name: PASSENGER_HASH_SALT - displayName: Passenger hash salt for HashIds - required: true - - name: PASSENGER_HASH_LENGTH - displayName: Minimum length of passenger generated hash for HashIds - - name: DRIVER_HASH_SALT - displayName: Passenger hash salt for HashIds - required: true - - name: DRIVER_HASH_LENGTH - displayName: Minimum length of driver generated hash for HashIds - - name: JWT_KEYS_PATH - displayName: Private Key Path - description: Private key Path - value: "/jwt_pems/" - required: true - - name: THIRD_PARTY_JWT_PRIVATE_KEY - displayName: Third party Private Key - description: Private key of third party's token - required: true - - name: DRIVER_JWT_PUBLIC_KEY - displayName: Driver public key - description: Public Key of driver's token - required: true - - name: PASSENGER_JWT_PUBLIC_KEY - displayName: Passenger public key - description: Public Key of passenger's token - required: true - - name: THIRD_PARTY_JWT_PUBLIC_KEY - displayName: Third party public key - description: Public key of third parties - required: true diff --git a/.okd/Service.yaml b/.okd/Service.yaml deleted file mode 100644 index 8d503d52..00000000 --- a/.okd/Service.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-svc-tpl -objects: -- apiVersion: v1 - kind: Service - metadata: - annotations: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - ports: - - name: 9999-tcp - port: 9999 - protocol: TCP - targetPort: 9999 - - name: 50051-tcp - port: 50051 - protocol: TCP - targetPort: 50051 - selector: - app: ${SERVICE_NAME} - sessionAffinity: None - type: ClusterIP -parameters: -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" -- name: PORT - displayName: Port - description: the port which will be open by your application - value: "9999" diff --git a/.okd/Template.yaml b/.okd/Template.yaml deleted file mode 100644 index c8e629b6..00000000 --- a/.okd/Template.yaml +++ /dev/null @@ -1,218 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: emq-auth-module - annotations: - description: "EMQ Authentication/Autherization (ACL/AUTH)" - iconClass: "go-gopher" - tags: "emq,realtime" -objects: -- apiVersion: v1 - kind: Template - metadata: - name: ${SERVICE_NAME} - objects: - - apiVersion: autoscaling/v1 - kind: HorizontalPodAutoscaler - metadata: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - scaleTargetRef: - kind: DeploymentConfig - name: ${SERVICE_NAME} - apiVersion: v1 - subresource: scale - minReplicas: ${MIN_REPLICA} - maxReplicas: ${MAX_REPLICA} - targetCPUUtilizationPercentage: ${CPU_UTILIZATION} - - apiVersion: v1 - kind: ConfigMap - metadata: - name: "${SERVICE_NAME}-jwt-keys" - labels: - app: "${SERVICE_NAME}" - - - apiVersion: v1 - kind: Template - metadata: - name: ${SERVICE_NAME} - objects: - - apiVersion: v1 - kind: Route - metadata: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - host: ${APP_HOST}.apps.private.teh-1.snappcloud.io - port: - targetPort: ${PORT}-tcp - tls: - termination: edge - insecureEdgeTerminationPolicy: Redirect - to: - kind: Service - name: ${SERVICE_NAME} - weight: 100 - wildcardPolicy: None - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-auth-env" - labels: - app: "${SERVICE_NAME}" - stringData: - CHANNEL_PREFIX: "emqch-" - JWT_BASE_PATH: "/jwt_pems/" - PASSENGER_HASH_SALT: "${PASSENGER_HASH_SALT}" - PASSENGER_HASH_MIN_LENGTH: "${PASSENGER_HASH_MIN_LENGTH}" - DRIVER_HASH_SALT: "${DRIVER_HASH_SALT}" - DRIVER_HASH_MIN_LENGTH: "${DRIVER_HASH_MIN_LENGTH}" - PASSENGER_ISSUER: "0" - DRIVER_ISSUER: "1" - - apiVersion: v1 - kind: Template - metadata: - name: ${SERVICE_NAME}-svc - objects: - - apiVersion: v1 - kind: Service - metadata: - annotations: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - ports: - - name: ${PORT}-tcp - port: ${PORT} - protocol: TCP - targetPort: ${PORT} - selector: - app: ${SERVICE_NAME} - sessionAffinity: None - type: ClusterIP - - apiVersion: v1 - kind: Template - metadata: - name: ${SERVICE_NAME} - objects: - - apiVersion: v1 - kind: DeploymentConfig - metadata: - labels: - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - selector: - app: ${SERVICE_NAME} - strategy: - rollingParams: - intervalSeconds: 1 - maxSurge: 1 - maxUnavailable: 0 - timeoutSeconds: 600 - updatePeriodSeconds: 10 - type: Rolling - template: - metadata: - # annotations: - # prometheus.io/scrape: "true" - # prometheus.io/path: "/metrics" - labels: - app: ${SERVICE_NAME} - spec: - containers: - - name: ${SERVICE_NAME}-app - image: "docker-registry.default.svc:5000/${APP_IMAGE}" - imagePullPolicy: Always - ports: - - containerPort: ${PORT} - protocol: TCP - resources: - limits: - memory: "${MEMORY_LIMIT}" - # cpu: "${CPU_LIMIT}" - requests: - memory: "${MEMORY_REQUEST}" - # cpu: "${CPU_REQUEST}" - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 23 - successThreshold: 1 - tcpSocket: - port: ${PORT} - timeoutSeconds: 2 - livenessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 29 - successThreshold: 1 - tcpSocket: - port: ${PORT} - timeoutSeconds: 2 - envFrom: - - secretRef: - name: "${SERVICE_NAME}-auth-env" - env: - - name: HTTP_PROXY - - name: HTTPS_PROXY - volumeMounts: - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/" - volumes: - - name: "${SERVICE_NAME}-jwt-keys" - configMap: - name: "${SERVICE_NAME}-jwt-keys" - # - name: "readiness-check" - # emptyDir: {} - dnsPolicy: ClusterFirst - restartPolicy: Always - terminationGracePeriodSeconds: 30 - triggers: [] - -parameters: -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: soteria -- name: MIN_REPLICA - displayName: Minimum number of replica - value: 2 -- name: MAX_REPLICA - displayName: Maximum number of replica - value: 80 -- name: CPU_UTILIZATION - displayName: Target CPU Utilization Percentage - value: 80 -- name: PASSENGER_HASH_SALT - displayName: Passenger hash salt for HashIds - required: true -- name: PASSENGER_HASH_MIN_LENGTH - displayName: Minimum length of passenger generated hash for HashIds - value: 15 -- name: DRIVER_HASH_SALT - displayName: Passenger hash salt for HashIds - required: true -- name: DRIVER_HASH_MIN_LENGTH - displayName: Minimum length of driver generated hash for HashIds - value: 15 -- name: APP_HOST - required: true - displayName: Application host -- name: APP_IMAGE - displayName: Application Docker Image - description: application will pull and run this docker image. It has to be in Snapp Cloud registery - value: "bravo-testing/soteria:master" -- name: MEMORY_LIMIT - value: "512Mi" -- name: MEMORY_REQUEST - value: "512Mi" -- name: CPU_LIMIT - value: "600m" -- name: CPU_REQUEST - value: "600m" \ No newline at end of file diff --git a/.okd/deploy.sh b/.okd/deploy.sh deleted file mode 100755 index 76728654..00000000 --- a/.okd/deploy.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env sh - -env > .env - -echo "processing config map..." -oc process -f ./mozart/ConfigMap.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f - - -echo "processing secret..." -oc process -f ./mozart/Secret.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f - - -echo "processing service..." -oc process -f ./mozart/Service.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f - - -echo "processing deployment config..." -oc process -f ./mozart/DeploymentConfig.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f - - -echo "processing route..." -oc process -f ./mozart/Route.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f - - -echo "rolling out $SERVICE_NAME ..." -oc rollout latest dc/$SERVICE_NAME diff --git a/.okd/mozart/ConfigMap.yaml b/.okd/mozart/ConfigMap.yaml deleted file mode 100644 index c08062bc..00000000 --- a/.okd/mozart/ConfigMap.yaml +++ /dev/null @@ -1,58 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-configmap -objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: ${SERVICE_NAME}-env - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: ${MANAGED_BY} - app.snappcloud.io/created-by: mozart - app: ${SERVICE_NAME} - data: - SOTERIA_HTTP_PORT: "9999" - SOTERIA_GRPC_PORT: "50051" - SOTERIA_REDIS_ADDRESS: "${REDIS_ADDR}" - SOTERIA_REDIS_POOL_SIZE: "10" - SOTERIA_REDIS_MAX_RETRIES: "0" - SOTERIA_REDIS_READ_TIMEOUT: "3s" - SOTERIA_REDIS_POOL_TIMEOUT: "4s" - SOTERIA_REDIS_MIN_RETRY_BACKOFF: "8ms" - SOTERIA_REDIS_MAX_RETRY_BACKOFF: "512ms" - SOTERIA_REDIS_IDLE_TIMEOUT: "300s" - SOTERIA_REDIS_IDLE_CHECK_FREQUENCY: "60s" - SOTERIA_REDIS_SET_MEMBER_EXP_TIME: "300s" - SOTERIA_REDIS_MIN_IDLE_CONNECTIONS: "5" - SOTERIA_LOGGER_LEVEL: "${LOG_LEVEL}" - SOTERIA_LOGGER_SENTRY_ENABLED: "false" - SOTERIA_LOGGER_SENTRY_DSN: "" - SOTERIA_LOGGER_SENTRY_TIMEOUT: "500ms" - SOTERIA_ALLOWED_ACCESS_TYPES: "sub,pub" - SOTERIA_CACHE_ENABLED: "true" - SOTERIA_CACHE_EXPIRATION: "600s" - -parameters: - - name: SERVICE_NAME - displayName: Service name - required: true - value: "soteria" - - name: MANAGED_BY - displayName: Created By - required: true - value: dispatching-team - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "dev" - - name: LOG_LEVEL - value: "DEBUG" - displayName: Log Level - description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF) - - name: REDIS_ADDR - required: true - value: "rediscentral:6379" diff --git a/.okd/mozart/DeploymentConfig.yaml b/.okd/mozart/DeploymentConfig.yaml deleted file mode 100644 index 238d545b..00000000 --- a/.okd/mozart/DeploymentConfig.yaml +++ /dev/null @@ -1,151 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-deployment-config -objects: - - apiVersion: v1 - kind: DeploymentConfig - metadata: - labels: - app: ${SERVICE_NAME} - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: ${MANAGED_BY} - app.snappcloud.io/created-by: mozart - name: ${SERVICE_NAME} - spec: - replicas: 1 - selector: - app: ${SERVICE_NAME} - deploymentconfig: ${SERVICE_NAME} - triggers: [] - template: - metadata: - annotations: - prometheus.io/scrape: "${METRICS_ENABLED}" - prometheus.io/path: "/metrics" - labels: - app: ${SERVICE_NAME} - deploymentconfig: ${SERVICE_NAME} - spec: - initContainers: - - name: ${SERVICE_NAME}-app-init - image: docker-registry.default.svc:5000/mozart/${SERVICE_NAME}:${SERVICE_IMAGE_TAG} - command: ["/app/soteria", "accounts", "init", "db.json"] - imagePullPolicy: Always - envFrom: - - secretRef: - name: ${SERVICE_NAME}-auth-env - - configMapRef: - name: "${SERVICE_NAME}-env" - volumeMounts: - - name: "${SERVICE_NAME}-db" - mountPath: "/app/db.json" - subPath: db.json - readOnly: true - containers: - - name: ${SERVICE_NAME}-app - image: docker-registry.default.svc:5000/mozart/${SERVICE_NAME}:${SERVICE_IMAGE_TAG} - command: ["/app/soteria", "serve"] - imagePullPolicy: Always - ports: - - containerPort: 9999 - protocol: TCP - resources: - limits: - memory: "${MEMORY_LIMIT}" - # cpu: "${CPU_LIMIT}" - requests: - memory: "${MEMORY_REQUEST}" - # cpu: "${CPU_REQUEST}" - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 23 - successThreshold: 1 - tcpSocket: - port: 9999 - timeoutSeconds: 2 - livenessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 29 - successThreshold: 1 - tcpSocket: - port: 9999 - timeoutSeconds: 2 - envFrom: - - secretRef: - name: ${SERVICE_NAME}-auth-env - - configMapRef: - name: "${SERVICE_NAME}-env" - env: - - name: LOG_LEVEL - value: "${LOG_LEVEL}" - - name: HTTP_PROXY - - name: HTTPS_PROXY - volumeMounts: - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/100.private.pem" - subPath: 100.private.pem - readOnly: true - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/100.pem" - subPath: 100.pem - readOnly: true - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/1.pem" - subPath: 1.pem - readOnly: true - - name: "${SERVICE_NAME}-jwt-keys" - mountPath: "/jwt_pems/0.pem" - subPath: 0.pem - readOnly: true - volumes: - - name: "${SERVICE_NAME}-jwt-keys" - secret: - secretName: "${SERVICE_NAME}-jwt-keys" - - name: "${SERVICE_NAME}-db" - secret: - secretName: "${SERVICE_NAME}-db" - dnsPolicy: ClusterFirst - restartPolicy: Always - terminationGracePeriodSeconds: 30 -parameters: -- name: SERVICE_IMAGE_TAG - displayName: Application Docker Image - description: application will pull and run this docker image. It has to be in Snapp Cloud registery - value: "dev" -- name: CI_COMMIT_REF_SLUG - displayName: Deployment branch - required: true - value: "dev" -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" -- name: PORT - value: "9999" - displayName: Port - description: the port which will be open by your application -- name: MEMORY_LIMIT - value: "150Mi" -- name: MEMORY_REQUEST - value: "150Mi" -- name: CPU_LIMIT - value: "500m" -- name: CPU_REQUEST - value: "500m" -- name: LOG_LEVEL - value: "DEBUG" - displayName: Log Level - description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF) -- name: METRICS_ENABLED - value: "false" - displayName: Metrics Endpoint? - description: /metrics endpoint will be available for Prometheus -- name: MANAGED_BY - displayName: Managed By - required: true - value: dispatching-team diff --git a/.okd/mozart/Route.yaml b/.okd/mozart/Route.yaml deleted file mode 100644 index 223d1754..00000000 --- a/.okd/mozart/Route.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME} -objects: - - - apiVersion: v1 - kind: Route - metadata: - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: dispatching-team - app.snappcloud.io/created-by: ${CREATED_BY} - app: ${SERVICE_NAME} - name: ${SERVICE_NAME}-tcp - spec: - host: ${SERVICE_NAME}-${SERVICE_NAMESPACE}.apps.private.${CLUSTER_NAME}.snappcloud.io - port: - targetPort: ${SERVICE_NAME}-tcp - tls: - termination: edge - insecureEdgeTerminationPolicy: Allow - to: - kind: Service - name: ${SERVICE_NAME} - weight: 100 - wildcardPolicy: None - -parameters: - - name: CLUSTER_NAME - displayName: Cluster name - required: true - value: "teh-1" - - name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - required: true - value: soteria - - name: SERVICE_NAMESPACE - displayName: Application Unique ID - required: true - value: "snapp-ode-mozart" - - name: CREATED_BY - displayName: Service name - required: true - value: mozart - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "dev" diff --git a/.okd/mozart/Secret.yaml b/.okd/mozart/Secret.yaml deleted file mode 100644 index 9fbb3430..00000000 --- a/.okd/mozart/Secret.yaml +++ /dev/null @@ -1,152 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: "${SERVICE_NAME}-secret" -objects: - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-auth-env" - labels: - app: "${SERVICE_NAME}" - app.snappcloud.io/created-by: "mozart" - app.snappcloud.io/managed-by: "${MANAGED_BY}" - app.snappcloud.io/version: "${APP_VERSION}" - app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}" - stringData: - SOTERIA_JWT_KEYS_PATH: "${JWT_KEYS_PATH}" - SOTERIA_DRIVER_SALT: "${DRIVER_HASH_SALT}" - SOTERIA_PASSENGER_SALT: "${PASSENGER_HASH_SALT}" - SOTERIA_DRIVER_HASH_LENGTH: "${DRIVER_HASH_LENGTH}" - SOTERIA_PASSENGER_HASH_LENGTH: "${PASSENGER_HASH_LENGTH}" - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-jwt-keys" - labels: - app: "${SERVICE_NAME}" - app.snappcloud.io/created-by: "mozart" - app.snappcloud.io/managed-by: "${MANAGED_BY}" - app.snappcloud.io/version: "${APP_VERSION}" - app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}" - stringData: - 0.pem: "${DRIVER_JWT_PUBLIC_KEY}" - 1.pem: "${PASSENGER_JWT_PUBLIC_KEY}" - 100.pem: "${THIRD_PARTY_JWT_PUBLIC_KEY}" - 100.private.pem: "${THIRD_PARTY_JWT_PRIVATE_KEY}" - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-db" - labels: - app: "${SERVICE_NAME}" - app.snappcloud.io/created-by: "mozart" - app.snappcloud.io/managed-by: "${MANAGED_BY}" - app.snappcloud.io/version: "${APP_VERSION}" - app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}" - stringData: - db.json: "${DB}" - - -parameters: - - name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" - - name: APP_VERSION - required: true - value: "dev" - - name: MANAGED_BY - displayName: Managed by - required: true - value: "dispatching-team" - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "dev" - - name: PASSENGER_HASH_SALT - displayName: Passenger hash salt for HashIds - required: true - value: "secret" - - name: PASSENGER_HASH_LENGTH - displayName: Minimum length of passenger generated hash for HashIds - value: "15" - - name: DRIVER_HASH_SALT - displayName: Passenger hash salt for HashIds - required: true - value: "secret" - - name: DRIVER_HASH_LENGTH - displayName: Minimum length of driver generated hash for HashIds - value: "15" - - name: JWT_KEYS_PATH - displayName: Private Key Path - description: Private key Path - value: "/jwt_pems/" - required: true - - name: THIRD_PARTY_JWT_PRIVATE_KEY - displayName: Third Party JWT Private Key - required: true - value: |- - -----BEGIN RSA PRIVATE KEY----- - MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n - gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs - AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB - AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1 - j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP - hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs - hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh - 1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E - pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg - FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT - 6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r - m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ - l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g== - -----END RSA PRIVATE KEY----- - - name: DRIVER_JWT_PUBLIC_KEY - displayName: Driver public key - description: Public Key of driver's token - required: true - value: |- - -----BEGIN PUBLIC KEY----- - MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBk7O6M5p4eYNAwtVU2beGa - W4mhFG94OtYUWDl1E7UUrhUNGf97Eb/45NjQszu0YPERnApJc2RUm2TrS7iq0mHz - Xbwf+CbNF54Q5mjuHcpBKgvFwUUSCCYBftmRc4xbFIH4Oh3nHC2GeukUS9TmJwjM - tJKyU0Ve8BK5BgjhagM7XSs+scE2mxemoWtcs6mJLtBuEgRGMgHW00mSdOcLp/+l - oHpSzRYN92/DomwmmjGVy8Ji0faeHx+r79ZzE0E8Rcc29Yhrg1ymrjfkXg98WjAb - TSv4UAN20lsBDejpnGEZKJrxHZ56gHgaJn6PKKCD6ItJA7y7iraCdBhCfAIUIz/z - AgMBAAE= - -----END PUBLIC KEY----- - - name: PASSENGER_JWT_PUBLIC_KEY - displayName: Passenger public key - description: Public Key of passenger's token - required: true - value: |- - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lNRwyNsDieWs6LvHOJ+ - GyehhRC4Pn5yL5edKP3565F3LtRDMrkzwDRsQbqnUtTea9HCdTdBv+lI8vE17qRi - RQn10IMaIH6e4Aa3OWNClFhuqNOag7VmffsjTOgxHgHpfGAKVF/4BwqOHrdHFbAD - VOiWB1hv9Uc0C5laffGAub7fj+EAI02zlrsNDxYW8vyF2H47N7VWcvgd3RhZpxlG - 8bq9phl7Ja55YmQiT2Ic3/K5tsazg5z9lz6OTrx+JvWbefHFlJpjCLz5yefEaRmX - 9L/zyDMi4jgFTZEWNXC2vIrxwZMFwFhBXEp0PcCbuHJgJIucbRrbwukQC16uHJwP - zQIDAQAB - -----END PUBLIC KEY----- - - name: THIRD_PARTY_JWT_PUBLIC_KEY - displayName: Third party public key - description: Public key of third parties - required: true - value: |- - -----BEGIN PUBLIC KEY----- - MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8 - OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu - GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o - Wsupn2HVhMSjFbE6dwIDAQAB - -----END PUBLIC KEY----- - - name: DB - displayName: Initial database state - description: Initial database state - required: true - value: |- - [{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106544359+03:30","date_modified":"2020-09-29T10:44:15.106544423+03:30"},"username":"driver","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"84df6017-a850-42a3-a06f-22306a006a8d","endpoint":"","topic":"cab_event","access_type":"1"},{"uuid":"df0b884e-41dc-444e-81d4-67f62ae2174c","endpoint":"","topic":"driver_location","access_type":"2"},{"uuid":"1375192a-6fc5-4409-abd8-0d21ef106bee","endpoint":"","topic":"shared_location","access_type":"1"},{"uuid":"d7aeb294-6271-470a-987a-01ea4f035e5d","endpoint":"","topic":"superapp_event","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106560757+03:30","date_modified":"2020-09-29T10:44:15.106560814+03:30"},"username":"passenger","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"a51f1898-3381-456f-a5fe-0e9d80b5bee0","endpoint":"","topic":"cab_event","access_type":"1"},{"uuid":"8de97e3f-e772-4da4-8f15-4dc048f5c06e","endpoint":"","topic":"superapp_event","access_type":"1"},{"uuid":"c41260ed-70eb-4554-bc15-5714fc1bf3f8","endpoint":"","topic":"passenger_location","access_type":"2"},{"uuid":"1375192a-6fc5-4409-abd8-0d21ef106bee","endpoint":"","topic":"shared_location","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106567252+03:30","date_modified":"2020-09-29T10:44:15.106567311+03:30"},"username":"box","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"e591af90-8191-4118-941a-7ef4434414ee","endpoint":"","topic":"box_event","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106569957+03:30","date_modified":"2020-09-29T10:44:15.106570022+03:30"},"username":"colony-subscriber","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"bcbe0a57-1706-4de7-a948-71c56c9700c4","endpoint":"","topic":"driver_location","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106573104+03:30","date_modified":"2020-09-29T10:44:15.106573162+03:30"},"username":"gabriel","password":"password","type":"HeraldUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"305dc844-5df0-42dc-849b-deccfe2dab96","endpoint":"/notification","topic":"","access_type":"2"},{"uuid":"5795227f-10a8-4822-ba5e-a1fc057a0a95","endpoint":"/event","topic":"","access_type":"2"}]},{"meta_data":{"model_name":"user","date_created":"2021-06-23T08:42:24.722521722Z","date_modified":"2021-07-06T18:40:10.074710788Z"},"username":"gossiper","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":30758400000000000,"rules":[{"uuid":"ec6f659e-fe73-41cd-805e-c5285bd92a8d","endpoint":"","topic":"shared_location","access_type":"2"},{"uuid":"a8dc06c4-bb11-4cb9-8da7-84ae34d2d14a","endpoint":"","topic":"driver_location","access_type":"1"},{"uuid":"cda59530-d412-4c6d-bb3b-7a89c6bf58c5","endpoint":"","topic":"passenger_location","access_type":"1"}]}] diff --git a/.okd/mozart/Service.yaml b/.okd/mozart/Service.yaml deleted file mode 100644 index 79d7b005..00000000 --- a/.okd/mozart/Service.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: v1 -kind: Template -objects: - - apiVersion: v1 - kind: Service - metadata: - name: ${SERVICE_NAME} - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: dispatching-team - app.snappcloud.io/created-by: ${CREATED_BY} - app: "${SERVICE_NAME}" - spec: - ports: - - name: ${SERVICE_NAME}-tcp - port: 9999 - protocol: TCP - targetPort: 9999 - - name: 50051-tcp - port: 50051 - protocol: TCP - targetPort: 50051 - selector: - deploymentconfig: ${SERVICE_NAME} -parameters: - - name: SERVICE_NAME - displayName: Service name - required: true - value: "soteria" - - name: CREATED_BY - displayName: Created By - required: true - value: mozart - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "dev" diff --git a/.okd/oldMozart/ConfigMap.yaml b/.okd/oldMozart/ConfigMap.yaml deleted file mode 100644 index ba656114..00000000 --- a/.okd/oldMozart/ConfigMap.yaml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-configmap -objects: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: ${SERVICE_NAME}-jwt-keys - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: bravo - app.snappcloud.io/created-by: ${CREATED_BY} - app: ${SERVICE_NAME} - data: - 100.private.pem: ${BOX_JWT_PRIVATE_KEY} - 100.pem: ${BOX_JWT_PUBLIC_KEY} -parameters: - - name: SERVICE_NAME - displayName: Service name - required: true - value: "soteria" - - name: CREATED_BY - displayName: Created By - required: true - value: mozart - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "qe" - - name: BOX_JWT_PUBLIC_KEY - displayName: Box JWT Public Key - required: true - value: |- - -----BEGIN PUBLIC KEY----- - MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8 - OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu - GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o - Wsupn2HVhMSjFbE6dwIDAQAB - -----END PUBLIC KEY----- - - name: BOX_JWT_PRIVATE_KEY - displayName: Box JWT Private Key - required: true - value: |- - -----BEGIN RSA PRIVATE KEY----- - MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n - gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs - AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB - AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1 - j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP - hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs - hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh - 1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E - pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg - FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT - 6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r - m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ - l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g== - -----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/.okd/oldMozart/DeploymentConfig.yaml b/.okd/oldMozart/DeploymentConfig.yaml deleted file mode 100644 index 8dbdc528..00000000 --- a/.okd/oldMozart/DeploymentConfig.yaml +++ /dev/null @@ -1,151 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-deployment-config -objects: - - apiVersion: v1 - kind: DeploymentConfig - metadata: - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: bravo - app.snappcloud.io/created-by: ${CREATED_BY} - app: ${SERVICE_NAME} - name: ${SERVICE_NAME} - spec: - replicas: 1 - selector: - app: ${SERVICE_NAME} - deploymentconfig: ${SERVICE_NAME} - triggers: [] - template: - metadata: - annotations: - prometheus.io/scrape: "${METRICS_ENABLED}" - prometheus.io/path: "/metrics" - labels: - app: ${SERVICE_NAME} - deploymentconfig: ${SERVICE_NAME} - spec: - containers: - - name: ${SERVICE_NAME}-app - image: docker-registry.default.svc:5000/mozart/${SERVICE_NAME}:master - imagePullPolicy: Always - ports: - - containerPort: 9999 - protocol: TCP - resources: - limits: - memory: "${MEMORY_LIMIT}" - # cpu: "${CPU_LIMIT}" - requests: - memory: "${MEMORY_REQUEST}" - # cpu: "${CPU_REQUEST}" - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 23 - successThreshold: 1 - tcpSocket: - port: 9999 - timeoutSeconds: 2 - livenessProbe: - failureThreshold: 3 - initialDelaySeconds: 10 - periodSeconds: 29 - successThreshold: 1 - tcpSocket: - port: 9999 - timeoutSeconds: 2 - envFrom: - - secretRef: - name: "${SERVICE_NAME}-auth-env" - env: - - name: LOG_LEVEL - value: "${LOG_LEVEL}" - - name: DEBUG - value: "${DEBUG}" - - name: HTTP_PROXY - - name: HTTPS_PROXY - volumeMounts: - - name: "${SERVICE_NAME}-passenger-keys" - mountPath: "/jwt_pems/1.pem" - subPath: "1.pem" - readOnly: true - - name: "${SERVICE_NAME}-driver-keys" - mountPath: "/jwt_pems/0.pem" - subPath: "0.pem" - readOnly: true - - name: "${SERVICE_NAME}-3rd-party-keys" - mountPath: "/jwt_pems/100.private.pem" - subPath: 100.private.pem - readOnly: true - - name: "${SERVICE_NAME}-3rd-party-keys" - mountPath: "/jwt_pems/100.pem" - subPath: 100.pem - readOnly: true - volumes: - - name: "${SERVICE_NAME}-passenger-keys" - secret: - secretName: passengeroauth-keys - items: - - key: passenger-public.pem - path: "1.pem" - - name: "${SERVICE_NAME}-driver-keys" - secret: - secretName: driveroauth-keys - items: - - key: driver-public.pem - path: "0.pem" - - name: "${SERVICE_NAME}-3rd-party-keys" - configMap: - name: ${SERVICE_NAME}-jwt-keys - - # - name: "readiness-check" - # emptyDir: {} - dnsPolicy: ClusterFirst - restartPolicy: Always - terminationGracePeriodSeconds: 30 -parameters: -- name: SERVICE_IMAGE_TAG - displayName: Application Docker Image - description: application will pull and run this docker image. It has to be in Snapp Cloud registery - value: "qe" -- name: CI_COMMIT_REF_SLUG - displayName: Deployment branch - required: true - value: "master" -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" -- name: PORT - value: "9999" - displayName: Port - description: the port which will be open by your application -- name: MEMORY_LIMIT - value: "150Mi" -- name: MEMORY_REQUEST - value: "150Mi" -- name: CPU_LIMIT - value: "500m" -- name: CPU_REQUEST - value: "500m" -- name: DEBUG - value: "0" - displayName: Debug Mode - description: Add more debug info under /pprof route if it's enabled -- name: LOG_LEVEL - value: "DEBUG" - displayName: Log Level - description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF) -- name: METRICS_ENABLED - value: "false" - displayName: Metrics Endpoint? - description: /metrics endpoint will be available for Prometheus -- name: CREATED_BY - displayName: Created By - required: true - value: mozart diff --git a/.okd/oldMozart/Route.yaml b/.okd/oldMozart/Route.yaml deleted file mode 100644 index 08967366..00000000 --- a/.okd/oldMozart/Route.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME} -objects: -- apiVersion: v1 - kind: Route - metadata: - labels: - app: ${SERVICE_NAME} - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: bravo - app.snappcloud.io/created-by: ${CREATED_BY} - name: ${SERVICE_NAME}-tcp - spec: - host: ${SERVICE_NAME}-${SERVICE_NAMESPACE}.apps.private.teh-1.snappcloud.io - port: - targetPort: ${SERVICE_NAME}-tcp - to: - kind: Service - name: ${SERVICE_NAME} - weight: 100 - wildcardPolicy: None - -parameters: -- name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - required: true - value: soteria -- name: SERVICE_NAMESPACE - displayName: Application Unique ID - required: true - value: "snapp-ode-mozart" -- name: CREATED_BY - displayName: Service name - required: true - value: mozart -- name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "qe" diff --git a/.okd/oldMozart/Secret.yaml b/.okd/oldMozart/Secret.yaml deleted file mode 100644 index 1fd334a8..00000000 --- a/.okd/oldMozart/Secret.yaml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: v1 -kind: Template -metadata: - name: ${SERVICE_NAME}-secret-tpl -objects: - - apiVersion: v1 - kind: Secret - type: Opaque - metadata: - name: "${SERVICE_NAME}-auth-env" - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: bravo - app.snappcloud.io/created-by: ${CREATED_BY} - app: ${SERVICE_NAME} - stringData: - CHANNEL_PREFIX: "emqch-" - JWT_BASE_PATH: "/jwt_pems/" - SALT_1: "${PASSENGER_HASH_SALT}" - HASH_LENGTH_1: "${PASSENGER_HASH_MIN_LENGTH}" - SALT_0: "${DRIVER_HASH_SALT}" - HASH_LENGTH_0: "${DRIVER_HASH_MIN_LENGTH}" - ISS_TYPE_0: "driver" - ISS_TYPE_1: "passenger" - ISS_TYPE_100: "third party" - THIRDPARTY_CONFIG: "${VENDOR_APPLICATION_DATA}" - -parameters: - - name: PASSENGER_HASH_SALT - displayName: Passenger hash salt for HashIds - value: "secret" - required: true - - name: PASSENGER_HASH_MIN_LENGTH - displayName: Minimum length of passenger generated hash for HashIds - value: "15" - - name: DRIVER_HASH_SALT - displayName: Passenger hash salt for HashIds - value: "secret" - required: true - - name: DRIVER_HASH_MIN_LENGTH - displayName: Minimum length of driver generated hash for HashIds - value: "15" - - name: VENDOR_APPLICATION_DATA - displayName: Vendor Application Data - description: The configuration of 3rd parties application - required: true - value: "ewogICAiYm94Ijp7CiAgICAgICJ0eXBlIjoic3Vic2NyaWJlciIsCiAgICAgICJ0b2tlbiI6IktKSWlraklLYklZVkdqKVlpaFlVR0lCJiIsCiAgICAgICJ0b3BpY3MiOiJidWNrcywgMTExMSIsCiAgICAgICJleHAiOjg2NDAwCiAgIH0sCiAgICJoZXJhbGQtcHVibGlzaCI6ewogICAgICAidHlwZSI6InB1Ymxpc2giLAogICAgICAidG9rZW4iOiJ1dTBKVlB0aygxbnRHYWN1IiwKICAgICAgInRvcGljcyI6IioiLAogICAgICAiZXhwIjoxNTc2ODAwMDAKICAgfSwKICAgImhlcmFsZC1zdWJzY3JpYmVyIjp7CiAgICAgICJ0eXBlIjoic3Vic2NyaWJlciIsCiAgICAgICJ0b2tlbiI6InV1MEpWUHRrKDFudEdhY3UiLAogICAgICAidG9waWNzIjoiKiIsCiAgICAgICJleHAiOjE1NzY4MDAwMAogICB9LAogICAiY29sb255LXN1YnNjcmliZXIiOnsKICAgICAgInR5cGUiOiJzdWJzY3JpYmVyIiwKICAgICAgInRva2VuIjoidXUwSlZQdGsoMW50R2FjdXpwIiwKICAgICAgInRvcGljcyI6IioiLAogICAgICAiZXhwIjoxNTc2ODAwMDAKICAgfQp9" - - name: SERVICE_NAME - displayName: Service name - description: The name of the application (should be unique in the namespace) - value: "soteria" - - name: CREATED_BY - displayName: Created By - required: true - value: mozart - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "qe" diff --git a/.okd/oldMozart/Service.yaml b/.okd/oldMozart/Service.yaml deleted file mode 100644 index b2f4154c..00000000 --- a/.okd/oldMozart/Service.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: Template -objects: - - apiVersion: v1 - kind: Service - metadata: - name: ${SERVICE_NAME} - labels: - app.snappcloud.io/name: ${SERVICE_NAME} - app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG} - app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG} - app.snappcloud.io/managed-by: bravo - app.snappcloud.io/created-by: ${CREATED_BY} - app: "${SERVICE_NAME}" - spec: - ports: - - name: ${SERVICE_NAME}-tcp - port: 9999 - protocol: TCP - targetPort: 9999 - selector: - deploymentconfig: ${SERVICE_NAME} -parameters: - - name: SERVICE_NAME - displayName: Service name - required: true - value: "soteria" - - name: CREATED_BY - displayName: Created By - required: true - value: mozart - - name: CI_COMMIT_REF_SLUG - displayName: Commit Ref Slug - required: true - value: "qe" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 5a843f7c..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,46 +0,0 @@ -# Change log - -# Unreleased - (yyyy-mm-dd) - ---- - -## July 17, 2021 - -### Added - -- Add superuser token support - -### Changed - -- Refactor internal packages - -### Fixed - -## May 31, 2021 - -### Added - -- Add chat topic in acl logics - -### Changed - -### Fixed - -## May 29, 2021 - -### Added - -- Add location sharing topics acl logic. -- Ignore tokens which contain `is_superuser` flag in their claims. -- Tracing with jeager. jeager configuration is added - -| **Variable Name** | **Type** | **Description** | -| ------------------------------ | -------- | --------------------------------------------- | -| `SOTERIA_TRACER_SAMPLER_TYPE` | string | client sampler type e.g. const, probabilistic | -| `SOTERIA_TRACER_SAMPLER_PARAM` | float | client sampler paramer e.g. 1, 0 | - -For more on sampler please refer to [this](https://www.jaegertracing.io/docs/1.22/sampling/). - -### Changed - -### Fixed diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 057f0305..00000000 --- a/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM alpine -ARG BUILD_DATE -ARG VCS_REF -ARG BUILD_VERSION -RUN echo -e "https://repo.snapp.tech/repository/alpine/v3.12/main\nhttps://repo.snapp.tech/repository/alpine/v3.12/community" > /etc/apk/repositories - -RUN apk --no-cache --update add ca-certificates - -RUN mkdir /app - -COPY ./soteria /app -WORKDIR /app -ENV SOTERIA_BUILD_DATE=${BUILD_DATE} -ENV SOTERIA_VCS_REF=${VCS_REF} -ENV SOTERIA_BUILD_VERSION=${BUILD_VERSION} -CMD ["/app/soteria", "serve"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile deleted file mode 100644 index 7228a4ab..00000000 --- a/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -IMAGE_FLAG := $(shell git rev-parse --abbrev-ref HEAD | tr '/' '-') #get image flag from current branch name. -IMAGE_TAG ?= registry.apps.private.teh-1.snappcloud.io/dispatching-staging/soteria:${IMAGE_FLAG} - -dependencies: - go mod vendor -v - -compile: dependencies - go build --ldflags "-linkmode external -extldflags '-static'" -mod vendor -v cmd/soteria/soteria.go - -test: - go test --race -gcflags=-l -v -coverprofile .coverage.out.tmp ./... - cat .coverage.out.tmp | grep -v "mock.go" > .coverage.out - rm -rf .coverage.out.tmp - go tool cover -func .coverage.out - -html-report: test - go tool cover -html=.coverage.out -o coverage.html - -build-image: compile - docker build -t ${IMAGE_TAG} . - -push-image: build-image - docker push ${IMAGE_TAG} - -build-image-dev: compile - docker build -t soteria:latest . - -up: build-image-dev - docker-compose up - -down: - docker-compose down - docker-compose stop - docker rmi soteria - -.PHONY: all test clean diff --git a/README.md b/README.md index 012bf1b5..3e598785 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,271 @@ -# Soteria - -# What is Soteria? - -Soteria is responsible for Authentication and Authorization of every request sent to EMQ and Herald. - -# How to compile? - -- [Install Golang](https://golang.org/doc/install) - -- Set `snapp goproxy` - -`go env -w GOPROXY="https://repo.snapp.tech/repository/goproxy/"` - -- Run the following command to compile the application +

Soteria

+ +

+ +

+ +

+ GitHub Workflow Status + Codecov + GitHub repo size + GitHub tag (with filter) + GitHub go.mod Go version (subdirectory of monorepo) +

+ +## Introduction + +Soteria is responsible for Authentication and Authorization of every request sent to [EMQ](https://github.com/emqx/emqx/). +The following configuration in [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md) +format, configure EMQ to use HTTP Service for Authentication and Authorization. + +```hocon +{ + mechanism = password_based + backend = http + enable = true + + method = post + url = "http://127.0.0.1:8080/v2/auth" + body { + username = "${username}" + password = "${password}" + token = "${username}" + clientid = "${clientid}" + } + headers { + "Content-Type" = "application/json" + "X-Request-Source" = "EMQX" + } +} +``` -`make compile` +```hocon +{ + type = http + enable = true + + method = post + url = "http://127.0.0.1:32333/v2/acl" + body { + username = "${username}" + topic = "${topic}" + action = "${action}" + } + headers { + "Content-Type" = "application/json" + "X-Request-Source" = "EMQX" + } +} -# How to run it locally? +``` -By executing the following command Herald will be up with EMQX and RabbitMQ brokers. +We are using the [Authentication HTTP Service](https://www.emqx.io/docs/en/v5.2/access-control/authn/http.html) +and [Authorization HTTP Service](https://www.emqx.io/docs/en/v5.2/access-control/authn/http.html) +plugins of EMQ for forwarding these requests to Soteria and doing Authentication and Authorization. +EMQ has caching mechanism, but it sends requests almost for each Publish message to Soteria. +PS: On Subscribe we have only one message from client that need authorization and other messages are coming from server. + +## Architecture + +![arch](docs/arch.png) + +## Support Vendors + +Soteria supports having multiple vendors at the same time. +Means you can use single cluster for multiple companies at the same time and validate their tokens +and control accesses. + +### Vendor Configuration + +```yaml +company: "<>" +driver_salt: "" +passenger_salt: "" +passenger_hash_length: 15 +driver_hash_length: 15 +allowed_access_types: ["pub", "sub"] +keys: + iss-0: "key-value" + iss-1: "key-value" +iss_entity_map: + 0: "entity-0" + 1: "entity-1" + default: "default-entity" +iss_peer_map: + 0: "peer-0" + 1: "peer-1" + default: "default-peer" +jwt: + iss_name: "iss" + sub_name: "sub" + signing_method: "RS512" +topics: + - topic1 + - topic2 + - ... +``` -`make up` +### HashID Manager -# How to test? +`driver_salt`,`passenger_salt`, `passenger_hash_length`, `driver_hash_length` are used for HashIDManager. +This component only works for passenger and driver issuers. -## Unit testing +### Keys -`make test` +The following is a mapping that associates vendors (companies) with the keys used for opening JWT tokens. +If symmetrical keys are utilized, it is important to use their base64 representation. +It should also be noted that Soteria only requires public keys in cases where asymmetrical keys are employed. -# Deployment +### IssEntityMap & IssPeerMap -## Staging +These two configuration map iss to entity and peer respectively. -You can deploy `soteria` to the staging environments using -helm charts. +**Note**: default case is `required` +```yaml +iss_entity_map: + 0: "driver" + 1: "passenger" + default: "none" +iss_peer_map: + 0: "passenger" + 1: "driver" + default: "none" ``` -cd deployments -helm install soteria --generate-name -``` - -## Production - -We deploy `soteria` on two different infrastructures. - -- VM -- Cloud (okd) -### VM - -For VM deployments following 2 steps are required. - -- Preparing VMs which is done by `ansible playbooks`. You can find `herald`'s - `ansible playbooks` in the following link - [bravo/new-ansible-playbook](https://gitlab.snapp.ir/bravo/new-ansible-playbook) +In the example above, we have two maps for entity & peer maps. As it's clear for **entity** structure **0** and **1** is mapped to **driver** and **passenger**, respectively. Vice Versa, for peer structure it can be seen that **1** and **0** is mapped to **driver** and **passenger**. We have also the **default** key for both two cases. + +In the topic example, we have an accesses section in which **0** is mapped to **2** and **1** is mapped to **-1** which can be interpreted as a map from **IssEntity's Keys** to **Access Types**. In the other words this structure means: + +- **Driver** has a **Pub** access on topic +- **Passenger** has a **None** access on topic (No Access) + +### JWT + +This is the JWT configuration. `iss_name` and `sub_name` are the name of issuer +and subject in the JWT token's payload respectively. + +`signing_method` is the method that is used to sign the JWT token. +Here are list of different signing methods + +- ES384 +- RS512 \* +- PS512 +- RS384 \* +- HS256 \* +- HS384 \* +- RS256 \* +- PS384 +- ES256 +- ES512 +- EdDSA +- HS512 \* +- PS256 + **Note**: only the methods with `*` are supported for now. + +### Topic Configuration + +```yaml +type: "<>" +template: "<>" +hash_type: 0|1 +accesses: + iss-0: "<>" + iss-1: "<>" +``` -- Deploying `soteria` with CI/CD pipelines. +### Template -### Cloud (okd) +Topic template is a string consist of [Variables](##Available_Variables) and [Functions](##Available_Functions) +and regular expressions. -[dispatching/ignite](https://gitlab.snapp.ir/dispatching/ignite) is responsible -for production deployments on Cloud (okd). +Variables and Function are replaced first, and then the whole template will compile as a regular expression. +The end result will be compared against the requested topic. -# Folder Structure +#### Example -- `.api`: API documentation like swagger files -- `.gitlab`: Gitlab CI templates -- `.okd`: OpenShift deployment configs (no longer is use. please use Helm charts) -- `configs`: Config files -- `deployments`: Helm Charts -- `internal`: Main application directory for codes -- `pkg`: Go packages that their logic is independent of this project and can become handy in other projects as well. -- `web`: web interface of application including rest and grpc -- `test`: test data like jwt keys +This is template topic given in `vendor:topics[#]:template`. -# Accounting with Soteria-CLI +```yaml +- type: driver_location + template: ^{{.company}}/driver/{{.sub}}/location$ + accesses: + 0: "2" + 1: "-1" +``` -## Installation +```regex +^{{.company}}/driver/{{HashID .hashType .sub (IssToSnappID .iss)}}/location/[a-zA-Z0-9-_]+$ +``` -Install [pipenv](https://pipenv.pypa.io/en/latest/). +After parsing the template we get something like this -```sh -poetry install -poetry shell +```regex +// company=snapp +// hashType=0 +// sub=D96ZbvJakLp4PYd +// iss=0 +^snapp/driver/D96ZbvJakLp4PYd/location/[a-zA-Z0-9-_]+$ ``` -## EMQ User (http-auth) - -1. First create an `emq` account +Now if the requested topic match the created topic it is considered as a valid topic for that particular user. -```sh -python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" new -u gossiper -p password -t emq +```text +requested_topic: snapp/driver/D96ZbvJakLp4PYd/location/23fw49vxd +created_topic_regex: ^snapp/driver/D96ZbvJakLp4PYd/location/[a-zA-Z0-9-_]+$ ``` -2. Add rule for accessing the requested topic +#### Available Variables -```sh -python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" rules-add --username gossiper --password password --topic shared_location --access-type pubsub -``` +These are the variables available to use in the topic templates. -3. Set account secret +- `iss` + issuer obtained from JWT token +- `sub` + subject obtained from JWT token +- `hashType` + Hash type field defined in topic template configuration -```sh -python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" set-secret --username gossiper --password password --secret secret -``` + | HashType | Value | + | -------- | ----- | + | HashID | 0 | + | MD5 | 1 | -4. Set token timeout in nanoseconds +- `company` + company field defined in vendor configuration -```sh -python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" set-expire --username gossiper --password password -e $(( 356 * 24 * 60 * 60 * 1000 * 1000 * 1000)) -``` +#### Available Functions -5. Checkout the created user +These are the function available to use in the topic templates. -```sh -python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" show --username gossiper --password password -``` +- `IssToEntity(iss string) string` + convert `iss` obtained from JWT token to defined entity in `issEntityMap` +- `IssToPeer(iss string) string` + convert `iss` obtained from JWT token to define peer in `issPeerMap` +- `IssToSnappID(iss string) string` + convert `iss` obtained from JWT token to `snappid.audience` +- `HashID(hashType int, sub string, snappID snappid.audience)` + generated `hashID` for the given `subject` base on the `hashType` and `snappid.audience` -6. Generate token and have fun with `emq` +**Note**: `snappid.audience` only is available for issuer 0 and 1 which are for driver and passenger respectively. -```sh -python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" token --username gossiper --secret secret --grant-type pub -``` +#### Accesses -## EMQ Superuser (redis-auth) +List of all types of access on a topic. -The main job of superuser authentication is on the emq and the redis. soteria only makes the creation process easier. +| Access | Value | +| ------------------- | ----- | +| Subscribe | 1 | +| Publish | 2 | +| Subscribe & Publish | 3 | +| None | -1 | -1. Create EMQ superuser on redis. the command returns the authentication information for your service. +#### Suggested Issuers -```sh -python3 main.py -b "https://soteria-snapp-ode-012.apps.private.teh-1.snappcloud.io/" superuser --username parham --password password --duration $((1000 * 1000 * 1000)) -``` +Use any value for issuer but if you have an entity called `Driver` or `Passenger`, +we recommend use the following issuers for them. + +| Issuer | Value | +| --------- | ----- | +| Driver | 0 | +| Passenger | 1 | diff --git a/api/swagger.yml b/api/swagger.yml index 5cae6513..221ecc01 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1,65 +1,46 @@ --- swagger: '2.0' info: - version: 3.5.0 + version: 5.0.0 title: Soteria - paths: /auth: post: summary: Authenticates EMQ user's connection request produces: - - text + - text consumes: - - application/json + - application/json parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/auth' + - in: body + name: request + description: Request body + schema: + $ref: '#/definitions/auth' responses: 401: description: Request is not authorized 400: description: Bad input parameter 200: - description: if body is empty means OK, if contains "ignore" it means request is ignored + description: > + if body is empty means OK, + if contains "ignore" it means request is ignored /acl: post: summary: Authorizes EMQ user's subscription request produces: - - text + - text consumes: - - application/json - parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/acl' - responses: - 401: - description: Request is not authorized - 400: - description: Bad input parameter - 200: - description: OK - /token: - post: - summary: Generates token for EMQ users - produces: - - text - consumes: - - application/json + - application/json parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/token' + - in: body + name: request + description: Request body + schema: + $ref: '#/definitions/acl' responses: 401: description: Request is not authorized @@ -68,187 +49,6 @@ paths: 200: description: OK - /accounts: - post: - summary: Creates new account - produces: - - application/json - consumes: - - application/json - parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/createaccount' - responses: - 400: - description: Bad input parameter - 200: - description: OK - - /accounts/:username: - get: - summary: gets account - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/user' - put: - summary: updates the account - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/updateaccount' - - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 500: - description: Internal Server Error - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/response' - delete: - summary: deletes the account - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 500: - description: Internal Server Error - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/response' - - - /accounts/:username/rules: - post: - summary: creates new rule - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/rule' - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/response' - - - /accounts/:username/rules/:uuid: - get: - summary: get the rule with uuid - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/response' - - put: - summary: updates a rule - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - parameters: - - in: body - name: request - description: Request body - schema: - $ref: '#/definitions/rule' - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/response' - - delete: - summary: deletes the rule with uuid - produces: - - application/json - consumes: - - application/json - security: - - basicAuth: [] - responses: - 400: - description: Bad input parameter - schema: - $ref: '#/definitions/response' - 200: - description: OK - schema: - $ref: '#/definitions/response' - -components: - securitySchemes: - basicAuth: # <-- arbitrary name for the security scheme - type: http - scheme: basic - definitions: auth: type: object @@ -274,94 +74,3 @@ definitions: type: string enum: ["1", "2", "3"] example: "1" - - token: - type: object - required: - - grant_type - - client_id - - client_secret - properties: - grant_type: - type: string - client_id: - type: string - example: "snapp-box" - client_secret: - type: string - - createaccount: - type: object - required: - - username - - password - - user_type - properties: - username: - type: string - password: - type: string - user_type: - type: string - enum: ["HeraldUser", "EMQUser", "Staff"] - - updateaccount: - type: object - properties: - new_password: - type: string - IPs: - type: array - items: - type: string - token_expiration: - type: string - secret: - type: string - user_type: - type: string - enum: ["HeraldUser", "EMQUser", "Staff"] - - user: - type: object - properties: - username: - type: string - password: - type: string - IPs: - type: array - items: - type: string - token_expiration_duration: - type: string - rules: - type: array - items: - schema: - $ref: '#/definitions/rule' - example: - uuid: "1" - endpoint: "/event" - access_type: "2" - - response: - type: object - properties: - code: - type: integer - message: - type: string - data: - type: object - - rule: - type: object - properties: - endpoint: - type: string - topic: - type: string - access_type: - type: string - enum: ["2", "1", "3"] diff --git a/build/package/Dockerfile b/build/package/Dockerfile new file mode 100644 index 00000000..1f0cfa16 --- /dev/null +++ b/build/package/Dockerfile @@ -0,0 +1,27 @@ +FROM golang:1.23-alpine3.20 as builder + +# hadolint ignore=DL3018 +RUN apk --no-cache add git + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +WORKDIR /app/cmd/soteria +RUN go build -o /soteria + +FROM alpine:3.20 + +# hadolint ignore=DL3018 +RUN apk --no-cache add ca-certificates tzdata && \ + mkdir /app + +COPY --from=builder /soteria /app +WORKDIR /app + +ENTRYPOINT ["/app/soteria" ] +CMD ["serve"] diff --git a/build/package/docker-bake.json b/build/package/docker-bake.json new file mode 100644 index 00000000..4e9a7c7b --- /dev/null +++ b/build/package/docker-bake.json @@ -0,0 +1,22 @@ +{ + "group": { + "default": { + "targets": [ + "soteria" + ] + } + }, + "variable": { + "TAG": { + "default": "latest" + } + }, + "target": { + "soteria": { + "dockerfile": "build/package/Dockerfile", + "tags": [ + "ghcr.io/snapp-incubator/soteria:${TAG}" + ] + } + } +} diff --git a/deployments/soteria-init/.helmignore b/charts/soteria/.helmignore similarity index 100% rename from deployments/soteria-init/.helmignore rename to charts/soteria/.helmignore diff --git a/charts/soteria/Chart.yaml b/charts/soteria/Chart.yaml new file mode 100644 index 00000000..40c0e4c5 --- /dev/null +++ b/charts/soteria/Chart.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v2 +name: soteria +icon: https://github.com/snapp-incubator/soteria/blob/main/.github/assets/logo.jpg +description: Soteria provides EMQX authentication using HTTP + +maintainers: + - name: 1995parham + url: https://github.com/1995parham + email: parham.alvani@gmail.com + +type: application + +version: 0.0.0 +appVersion: latest diff --git a/charts/soteria/templates/NOTES.txt b/charts/soteria/templates/NOTES.txt new file mode 100644 index 00000000..f7bdfe37 --- /dev/null +++ b/charts/soteria/templates/NOTES.txt @@ -0,0 +1 @@ +Soteria does the emq authentication and its serivce must be set in emqx http_auth configuration. diff --git a/deployments/soteria/templates/_helpers.tpl b/charts/soteria/templates/_helpers.tpl similarity index 88% rename from deployments/soteria/templates/_helpers.tpl rename to charts/soteria/templates/_helpers.tpl index 183046af..5f6e7f44 100644 --- a/deployments/soteria/templates/_helpers.tpl +++ b/charts/soteria/templates/_helpers.tpl @@ -35,11 +35,18 @@ Common labels */}} {{- define "soteria.labels" -}} helm.sh/chart: {{ include "soteria.chart" . }} +app: soteria {{ include "soteria.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} +app.snappcloud.io/managed-by: {{ .Values.labels.managedby }} +app.snappcloud.io/created-by: {{ .Values.labels.createdby }} +{{- end }} + +{{- define "soteria.podLabels" -}} +service: soteria +owned-by: dispatching {{- end }} {{/* diff --git a/charts/soteria/templates/configmap.yaml b/charts/soteria/templates/configmap.yaml new file mode 100644 index 00000000..b430d367 --- /dev/null +++ b/charts/soteria/templates/configmap.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "soteria.fullname" . }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "soteria.labels" . | nindent 4 }} +data: + config.yaml: | + {{- toYaml .Values.config | nindent 4}} + vendors: + {{- range $key, $value := .Values.vendors}} + {{- if $value}} + - {{- toYaml $value | nindent 8}} + {{- end}} + {{- end}} diff --git a/deployments/soteria/templates/deployment.yaml b/charts/soteria/templates/deployment.yaml similarity index 71% rename from deployments/soteria/templates/deployment.yaml rename to charts/soteria/templates/deployment.yaml index 4d70dd49..f3ddfb9c 100644 --- a/deployments/soteria/templates/deployment.yaml +++ b/charts/soteria/templates/deployment.yaml @@ -2,9 +2,12 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "soteria.fullname" . }} + namespace: {{ $.Release.Namespace }} labels: {{- include "soteria.labels" . | nindent 4 }} spec: + {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} + {{- end }} selector: matchLabels: {{- include "soteria.selectorLabels" . | nindent 6 }} @@ -15,16 +18,9 @@ spec: type: RollingUpdate template: metadata: - annotations: - prometheus.io/scrape: "true" - prometheus.io/path: "/metrics" - {{ range .Values.service.ports }} - {{ if eq .name "http"}} - prometheus.io/port: {{ .port | quote }} - {{ end }} - {{ end }} labels: {{- include "soteria.selectorLabels" . | nindent 8 }} + {{- include "soteria.podLabels" . | nindent 8 }} spec: containers: - name: app @@ -68,23 +64,19 @@ spec: periodSeconds: 23 successThreshold: 1 timeoutSeconds: 2 - envFrom: - - configMapRef: - name: {{ include "soteria.fullname" . }} - - secretRef: - name: {{ include "soteria.fullname" . }}-auth-envs + env: + - name: TZ + value: {{ .Values.timezone }} volumeMounts: - {{- $root := . -}} - {{- range $key, $value := .Values.jwtKeys }} - - name: {{ include "soteria.fullname" $ }}-jwt-keys - mountPath: "{{ $root.Values.authEnvs.jwt_keys_path }}{{ $key }}" - subPath: {{ $key }} + - name: configuration + mountPath: "/app/config.yml" + subPath: "config.yaml" readOnly: true - {{ end }} volumes: - - name: {{ include "soteria.fullname" . }}-jwt-keys - secret: - secretName: {{ include "soteria.fullname" . }}-jwt-keys + - name: configuration + configMap: + defaultMode: 0440 + name: {{ include "soteria.fullname" . }} dnsPolicy: ClusterFirst restartPolicy: Always terminationGracePeriodSeconds: 30 diff --git a/deployments/soteria/templates/hpa.yaml b/charts/soteria/templates/hpa.yaml similarity index 56% rename from deployments/soteria/templates/hpa.yaml rename to charts/soteria/templates/hpa.yaml index 691e8fb4..198acd6e 100644 --- a/deployments/soteria/templates/hpa.yaml +++ b/charts/soteria/templates/hpa.yaml @@ -1,5 +1,5 @@ {{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: {{ include "soteria.fullname" . }} @@ -13,10 +13,20 @@ spec: minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} metrics: + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - type: Resource resource: name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} {{- end }} {{- end }} diff --git a/charts/soteria/templates/secret.yaml b/charts/soteria/templates/secret.yaml new file mode 100644 index 00000000..2ab382f4 --- /dev/null +++ b/charts/soteria/templates/secret.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "soteria.fullname" . }}-jwt-keys + namespace: {{ $.Release.Namespace }} + labels: + {{- include "soteria.labels" . | nindent 4 }} + +stringData: + {{- range $key, $value := .Values.jwtKeys }} + {{- if $value }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} diff --git a/deployments/soteria/templates/service.yaml b/charts/soteria/templates/service.yaml similarity index 91% rename from deployments/soteria/templates/service.yaml rename to charts/soteria/templates/service.yaml index 21da2a01..01a8a229 100644 --- a/deployments/soteria/templates/service.yaml +++ b/charts/soteria/templates/service.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: {{ include "soteria.fullname" . }} + namespace: {{ $.Release.Namespace }} labels: {{- include "soteria.labels" . | nindent 4 }} spec: diff --git a/deployments/soteria/templates/servicemonitor.yaml b/charts/soteria/templates/servicemonitor.yaml similarity index 92% rename from deployments/soteria/templates/servicemonitor.yaml rename to charts/soteria/templates/servicemonitor.yaml index e759e7f5..8b494c7f 100644 --- a/deployments/soteria/templates/servicemonitor.yaml +++ b/charts/soteria/templates/servicemonitor.yaml @@ -5,6 +5,8 @@ metadata: name: {{ template "soteria.fullname" . }} {{- if .Values.serviceMonitor.namespace }} namespace: {{ .Values.serviceMonitor.namespace }} + {{ else }} + namespace: {{ $.Release.Namespace }} {{- end }} labels: {{- include "soteria.labels" . | nindent 4 }} @@ -29,7 +31,7 @@ spec: {{- end }} namespaceSelector: matchNames: - - {{ .Release.Namespace }} + - {{ $.Release.Namespace }} selector: matchLabels: {{- include "soteria.selectorLabels" . | nindent 6 }} diff --git a/deployments/soteria/templates/tests/test-connection.yaml b/charts/soteria/templates/tests/test-connection.yaml similarity index 79% rename from deployments/soteria/templates/tests/test-connection.yaml rename to charts/soteria/templates/tests/test-connection.yaml index 7ecafaf8..fb2635ef 100644 --- a/deployments/soteria/templates/tests/test-connection.yaml +++ b/charts/soteria/templates/tests/test-connection.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Pod metadata: name: {{ include "soteria.fullname" . }}-test-connection + namespace: {{ $.Release.Namespace }} labels: {{- include "soteria.labels" . | nindent 4 }} annotations: @@ -14,7 +15,7 @@ spec: {{ range .Values.service.ports }} {{ if eq .name "http"}} args: - - {{ include "soteria.fullname" $ }}:{{ .port }} + - {{ include "soteria.fullname" $ }}:{{ .port }}/metrics {{ end }} {{ end }} restartPolicy: Never diff --git a/charts/soteria/values.yaml b/charts/soteria/values.yaml new file mode 100644 index 00000000..8e783d57 --- /dev/null +++ b/charts/soteria/values.yaml @@ -0,0 +1,111 @@ +--- +replicaCount: 1 + +labels: + managedby: cloud-platform-team + createdby: cloud-platform-team + +image: + registry: ghcr.io + repository: snapp-incubator/soteria + pullPolicy: Always + +timezone: Asia/Tehran + +service: + type: ClusterIP + ports: + - name: http + port: 9999 + protocol: tcp + +resources: + limits: + memory: 128Mi + cpu: 1 + requests: + memory: 128Mi + cpu: 500m + +autoscaling: + enabled: false + minReplicas: 3 + maxReplicas: 20 + targetCPUUtilizationPercentage: 65 + targetMemoryUtilizationPercentage: 80 + +rollingParams: + maxSurge: 5 + maxUnavailable: 0 + +serviceMonitor: + enabled: false + +config: + default_vendor: "snapp" + http_port: 9999 + logger: + level: "debug" + stacktrace: true + tracer: + enabled: false + +black_list_user_logging: + iss: 0 + user_ids: [] + +vendors: + snapp: + company: "snapp" + type: "manual" + hashid_map: + 0: + salt: "secret" + length: 15 + 1: + salt: "secret" + length: 15 + allowed_access_types: ["pub", "sub"] + topics: + - type: cab_event + template: ^{{IssToEntity .iss}}-event-{{ EncodeMD5 (DecodeHashID .sub .iss) }}$ + hash_type: 1 + # describe the way issuers (from the jwt token) can interact with topic. + # here issuer 1 (mapped to passenger in the iss_entity_map) can subscribe (second element in the allowed_access_types). + accesses: + 0: '1' + 1: '1' + keys: + 0: |- + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB + -----END PUBLIC KEY----- + 1: |- + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB + -----END PUBLIC KEY----- + iss_entity_map: + 0: "driver" + 1: "passenger" + default: "none" + iss_peer_map: + 0: "passenger" + 1: "driver" + default: "none" + jwt: + # provide keys and algorithm to parse JWT token. + iss_name: "iss" + sub_name: "sub" + signing_method: "RS512" diff --git a/cmd/soteria/soteria.go b/cmd/soteria/soteria.go index 2d2c3bf6..2608c189 100644 --- a/cmd/soteria/soteria.go +++ b/cmd/soteria/soteria.go @@ -1,24 +1,10 @@ package main import ( - "log" - - "gitlab.snapp.ir/dispatching/soteria/v3/internal/commands" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/commands/accounts" + "github.com/snapp-incubator/soteria/internal/cmd" + _ "go.uber.org/automaxprocs" ) -var cli = commands.Root - -func init() { - cli.AddCommand(commands.Serve) - - accounts.Accounts.AddCommand(accounts.Init) - cli.AddCommand(accounts.Accounts) - cli.AddCommand(commands.Token) -} - func main() { - if err := cli.Execute(); err != nil { - log.Fatal(err) - } + cmd.Execute() } diff --git a/config.example.yml b/config.example.yml new file mode 100644 index 00000000..0529b832 --- /dev/null +++ b/config.example.yml @@ -0,0 +1,130 @@ +# Company name of the vendor to use if the incoming ACL request vendor is not found withing the registered vendors. +default_vendor: snapp +# Port of the HTTP server: +http_port: 9999 +# Application logger config: +logger: + level: debug + stacktrace: true +black_list_user_logging: + iss: 0 + user_ids: [] + user_hashed_ids: [] + +# Validator is the upstream backend service that can validate the tokens: +validator: + url: http://validator-lb + timeout: "5s" +# The list of different vendors or companies that Soteria should work with: +vendors: + - allowed_access_types: + - pub + - sub + company: snapp + hash_id_map: + "0": + alphabet: "" + length: 15 + salt: secret + "1": + alphabet: "" + length: 15 + salt: secret + iss_entity_map: + "0": driver + "1": passenger + default: "" + iss_peer_map: + "0": passenger + "1": driver + default: "" + jwt: + iss_name: iss + signing_method: RS512 + sub_name: sub + keys: + "0": |- + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB + -----END PUBLIC KEY----- + "1": |- + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB + -----END PUBLIC KEY----- + # Examples of different use cases of template functions: + # Topics are dynamics and their patterns can be defined using some GoTemplate functions. + # + # + # IssToEntity: converts iss claim token to company name. + # + # EncodeMD5: encode MD5 of the input + # + # DecodeHashID: runs hashid algorithm on the input. The first argument is the input of hashid and the second argument + # is the issuer of id of hash_id_map. + topics: + - accesses: + "0": "1" + "1": "1" + template: ^{{IssToEntity .iss}}-event-{{ EncodeMD5 (DecodeHashID .sub .iss) }}$ + type: cab_event + - accesses: + "0": "2" + "1": "-1" + template: ^{{.company}}/driver/{{.sub}}/location$ + type: driver_location + - accesses: + "0": "2" + "1": "2" + template: ^{{.company}}/passenger/{{.sub}}/location$ + type: passenger_location + - accesses: + "0": "1" + "1": "1" + template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/superapp$ + type: superapp_event + - accesses: + "0": "-1" + "1": "-1" + template: ^bucks$ + type: box_event + - accesses: + "0": "1" + "1": "1" + template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/{{IssToPeer .iss}}-location$ + type: shared_location + - accesses: + "0": "1" + "1": "1" + template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/chat$ + type: chat + - accesses: + "0": "2" + "1": "2" + template: ^shared/{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/send$ + type: general_call_entry + - accesses: + "0": "2" + "1": "2" + template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/[a-zA-Z0-9-_]+/send$ + type: node_call_entry + - accesses: + "0": "1" + "1": "1" + template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/receive$ + type: call_outgoing +tracer: + enabled: false + endpoint: 127.0.0.1:4317 + ratio: 0.1 diff --git a/deployments/soteria-init/Chart.yaml b/deployments/soteria-init/Chart.yaml deleted file mode 100644 index 5c21d685..00000000 --- a/deployments/soteria-init/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: soteria-init -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/deployments/soteria-init/templates/NOTES.txt b/deployments/soteria-init/templates/NOTES.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/deployments/soteria-init/templates/_helpers.tpl b/deployments/soteria-init/templates/_helpers.tpl deleted file mode 100644 index 8b61a174..00000000 --- a/deployments/soteria-init/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "soteria-init.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "soteria-init.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "soteria-init.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "soteria-init.labels" -}} -helm.sh/chart: {{ include "soteria-init.chart" . }} -{{ include "soteria-init.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "soteria-init.selectorLabels" -}} -app.kubernetes.io/name: {{ include "soteria-init.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "soteria-init.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "soteria-init.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/deployments/soteria-init/templates/configmap.yaml b/deployments/soteria-init/templates/configmap.yaml deleted file mode 100644 index aa44d96c..00000000 --- a/deployments/soteria-init/templates/configmap.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "soteria-init.fullname" . }} - labels: - {{- include "soteria-init.labels" . | nindent 4 }} -data: - {{ range $key, $value := .Values.envs }} - {{ if $value }} - SOTERIA_{{ $key | upper }}: {{ $value | quote }} - {{ end }} - {{ end }} diff --git a/deployments/soteria-init/templates/pod.yaml b/deployments/soteria-init/templates/pod.yaml deleted file mode 100644 index 78aa6607..00000000 --- a/deployments/soteria-init/templates/pod.yaml +++ /dev/null @@ -1,31 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: {{ include "soteria-init.fullname" . }} - labels: - {{- include "soteria-init.labels" . | nindent 4 }} -spec: - containers: - - name: {{ .Chart.Name }} - image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: ["/app/soteria"] - args: - - "accounts" - - "init" - - "db.json" - envFrom: - - configMapRef: - name: {{ include "soteria-init.fullname" . }} - volumeMounts: - - name: {{ include "soteria-init.fullname" . }}-db - mountPath: "/app/db.json" - subPath: db.json - readOnly: true - resources: - {{- toYaml .Values.resources | nindent 8 }} - restartPolicy: Never - volumes: - - name: {{ include "soteria-init.fullname" . }}-db - secret: - secretName: {{ include "soteria-init.fullname" . }}-db diff --git a/deployments/soteria-init/templates/secret.yaml b/deployments/soteria-init/templates/secret.yaml deleted file mode 100644 index d0a0e3fa..00000000 --- a/deployments/soteria-init/templates/secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "soteria-init.fullname" . }}-db - labels: - {{- include "soteria-init.labels" . | nindent 4 }} -stringData: - db.json: | - {{ toRawJson .Values.db | nindent 4 }} diff --git a/deployments/soteria-init/values.yaml b/deployments/soteria-init/values.yaml deleted file mode 100644 index 7f8b1f35..00000000 --- a/deployments/soteria-init/values.yaml +++ /dev/null @@ -1,170 +0,0 @@ -# Default values for Soteria. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - - -image: - registry: docker-registry.default.svc:5000 - repository: realtime-staging/soteria - tag: dev - pullPolicy: Always - -resources: - limits: - memory: 128Mi - cpu: 2 - requests: - memory: 128Mi - cpu: 1 - -envs: - http_port: "9999" - grpc_port: "50051" - mode: "debug" - redis_address: "main-redis-master.dispatching-staging.svc:6379" - redis_pass: "" - redis_pool_size: "10" - redis_max_retries: "0" - redis_read_timeout: "3s" - redis_pool_timeout: "4s" - redis_min_retry_backoff: "8ms" - redis_max_retry_backoff: "512ms" - redis_idle_timeout: "300s" - redis_idle_check_frequency: "60s" - redis_set_member_exp_time: "300s" - redis_min_idle_connections: "5" - logger_level: "debug" - logger_sentry_enabled: "false" - logger_sentry_dsn: "" - logger_sentry_timeout: "500ms" - allowed_access_types: "sub,pub" - cache_enabled: "true" - cache_expiration: "600s" - -db: - - meta_data: - model_name: user - date_created: '2020-09-29T10:44:15.106544359+03:30' - date_modified: '2020-09-29T10:44:15.106544423+03:30' - username: driver - password: password - type: EMQUser - ips: - secret: secret - token_expiration_duration: 2592000000000000 - rules: - - uuid: 84df6017-a850-42a3-a06f-22306a006a8d - endpoint: '' - topic: cab_event - access_type: '1' - - uuid: df0b884e-41dc-444e-81d4-67f62ae2174c - endpoint: '' - topic: driver_location - access_type: '2' - - uuid: 1375192a-6fc5-4409-abd8-0d21ef106bee - endpoint: '' - topic: shared_location - access_type: '1' - - uuid: d7aeb294-6271-470a-987a-01ea4f035e5d - endpoint: '' - topic: superapp_event - access_type: '1' - - meta_data: - model_name: user - date_created: '2020-09-29T10:44:15.106560757+03:30' - date_modified: '2020-09-29T10:44:15.106560814+03:30' - username: passenger - password: password - type: EMQUser - ips: - secret: secret - token_expiration_duration: 2592000000000000 - rules: - - uuid: a51f1898-3381-456f-a5fe-0e9d80b5bee0 - endpoint: '' - topic: cab_event - access_type: '1' - - uuid: 8de97e3f-e772-4da4-8f15-4dc048f5c06e - endpoint: '' - topic: superapp_event - access_type: '1' - - uuid: c41260ed-70eb-4554-bc15-5714fc1bf3f8 - endpoint: '' - topic: passenger_location - access_type: '2' - - uuid: 1375192a-6fc5-4409-abd8-0d21ef106bee - endpoint: '' - topic: shared_location - access_type: '1' - - meta_data: - model_name: user - date_created: '2020-09-29T10:44:15.106567252+03:30' - date_modified: '2020-09-29T10:44:15.106567311+03:30' - username: box - password: password - type: EMQUser - ips: - secret: secret - token_expiration_duration: 2592000000000000 - rules: - - uuid: e591af90-8191-4118-941a-7ef4434414ee - endpoint: '' - topic: box_event - access_type: '1' - - meta_data: - model_name: user - date_created: '2020-09-29T10:44:15.106569957+03:30' - date_modified: '2020-09-29T10:44:15.106570022+03:30' - username: colony-subscriber - password: password - type: EMQUser - ips: - secret: secret - token_expiration_duration: 2592000000000000 - rules: - - uuid: bcbe0a57-1706-4de7-a948-71c56c9700c4 - endpoint: '' - topic: driver_location - access_type: '1' - - meta_data: - model_name: user - date_created: '2020-09-29T10:44:15.106573104+03:30' - date_modified: '2020-09-29T10:44:15.106573162+03:30' - username: gabriel - password: password - type: HeraldUser - ips: - secret: secret - token_expiration_duration: 2592000000000000 - rules: - - uuid: 305dc844-5df0-42dc-849b-deccfe2dab96 - endpoint: "/notification" - topic: '' - access_type: '2' - - uuid: 5795227f-10a8-4822-ba5e-a1fc057a0a95 - endpoint: "/event" - topic: '' - access_type: '2' - - meta_data: - model_name: user - date_created: '2021-06-23T08:42:24.722521722Z' - date_modified: '2021-07-06T18:40:10.074710788Z' - username: gossiper - password: password - type: EMQUser - ips: - secret: secret - token_expiration_duration: 30758400000000000 - rules: - - uuid: ec6f659e-fe73-41cd-805e-c5285bd92a8d - endpoint: '' - topic: shared_location - access_type: '2' - - uuid: a8dc06c4-bb11-4cb9-8da7-84ae34d2d14a - endpoint: '' - topic: driver_location - access_type: '1' - - uuid: cda59530-d412-4c6d-bb3b-7a89c6bf58c5 - endpoint: '' - topic: passenger_location - access_type: '1' diff --git a/deployments/soteria/.helmignore b/deployments/soteria/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/deployments/soteria/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/deployments/soteria/Chart.yaml b/deployments/soteria/Chart.yaml deleted file mode 100644 index aed4a1bf..00000000 --- a/deployments/soteria/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: soteria -description: Soteria Helm Chart - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 3.8.4 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "v3-8-4" diff --git a/deployments/soteria/templates/NOTES.txt b/deployments/soteria/templates/NOTES.txt deleted file mode 100644 index 786ac34a..00000000 --- a/deployments/soteria/templates/NOTES.txt +++ /dev/null @@ -1,22 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} -{{- range $host := .Values.ingress.hosts }} - {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "soteria.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "soteria.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "soteria.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "soteria.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT -{{- end }} diff --git a/deployments/soteria/templates/configmap.yaml b/deployments/soteria/templates/configmap.yaml deleted file mode 100644 index 3d3a971a..00000000 --- a/deployments/soteria/templates/configmap.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "soteria.fullname" . }} - labels: - {{- include "soteria.labels" . | nindent 4 }} -data: - {{ range $key, $value := .Values.envs }} - {{ if $value }} - SOTERIA_{{ $key | upper }}: {{ $value | quote }} - {{ end }} - {{ end }} diff --git a/deployments/soteria/templates/ingress.yaml b/deployments/soteria/templates/ingress.yaml deleted file mode 100644 index b259cd16..00000000 --- a/deployments/soteria/templates/ingress.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "soteria.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "soteria.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} -{{- end }} diff --git a/deployments/soteria/templates/secret.yaml b/deployments/soteria/templates/secret.yaml deleted file mode 100644 index 3bf55f59..00000000 --- a/deployments/soteria/templates/secret.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "soteria.fullname" . }}-auth-envs - labels: - {{- include "soteria.labels" . | nindent 4 }} -stringData: - {{- range $key, $value := .Values.authEnvs }} - {{- if $value }} - SOTERIA_{{ $key | upper }}: {{ $value | quote }} - {{- end }} - {{- end }} - ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "soteria.fullname" . }}-jwt-keys - labels: - {{- include "soteria.labels" . | nindent 4 }} -stringData: - {{- range $key, $value := .Values.jwtKeys }} - {{- if $value }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- end }} diff --git a/deployments/soteria/values.yaml b/deployments/soteria/values.yaml deleted file mode 100644 index ff79e2c9..00000000 --- a/deployments/soteria/values.yaml +++ /dev/null @@ -1,131 +0,0 @@ -# Default values for Soteria. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -namespace: dispatching-staging -region: teh-1 - -image: - registry: docker-registry.default.svc:5000 - repository: realtime-staging/soteria - tag: dev - pullPolicy: Always - -service: - type: ClusterIP - ports: - - name: http - port: 9999 - protocol: tcp - - name: grpc - port: 50051 - protocol: tcp - -resources: - limits: - memory: 128Mi - cpu: 2 - requests: - memory: 128Mi - cpu: 1 - -autoscaling: - enabled: false - minReplicas: 3 - maxReplicas: 20 - targetCPUUtilizationPercentage: 65 - -rollingParams: - maxSurge: 5 - maxUnavailable: 0 - -ingress: - enabled: false - annotations: { } - # kubernetes.io/ingress.class: nginDeploymentConfigx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: [ ] - tls: [ ] - -serviceMonitor: - enabled: false - -jwtKeys: - 0.pem: |- - -----BEGIN PUBLIC KEY----- - MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBk7O6M5p4eYNAwtVU2beGa - W4mhFG94OtYUWDl1E7UUrhUNGf97Eb/45NjQszu0YPERnApJc2RUm2TrS7iq0mHz - Xbwf+CbNF54Q5mjuHcpBKgvFwUUSCCYBftmRc4xbFIH4Oh3nHC2GeukUS9TmJwjM - tJKyU0Ve8BK5BgjhagM7XSs+scE2mxemoWtcs6mJLtBuEgRGMgHW00mSdOcLp/+l - oHpSzRYN92/DomwmmjGVy8Ji0faeHx+r79ZzE0E8Rcc29Yhrg1ymrjfkXg98WjAb - TSv4UAN20lsBDejpnGEZKJrxHZ56gHgaJn6PKKCD6ItJA7y7iraCdBhCfAIUIz/z - AgMBAAE= - -----END PUBLIC KEY----- - 1.pem: |- - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lNRwyNsDieWs6LvHOJ+ - GyehhRC4Pn5yL5edKP3565F3LtRDMrkzwDRsQbqnUtTea9HCdTdBv+lI8vE17qRi - RQn10IMaIH6e4Aa3OWNClFhuqNOag7VmffsjTOgxHgHpfGAKVF/4BwqOHrdHFbAD - VOiWB1hv9Uc0C5laffGAub7fj+EAI02zlrsNDxYW8vyF2H47N7VWcvgd3RhZpxlG - 8bq9phl7Ja55YmQiT2Ic3/K5tsazg5z9lz6OTrx+JvWbefHFlJpjCLz5yefEaRmX - 9L/zyDMi4jgFTZEWNXC2vIrxwZMFwFhBXEp0PcCbuHJgJIucbRrbwukQC16uHJwP - zQIDAQAB - -----END PUBLIC KEY----- - 100.pem: |- - -----BEGIN PUBLIC KEY----- - MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8 - OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu - GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o - Wsupn2HVhMSjFbE6dwIDAQAB - -----END PUBLIC KEY----- - 100.private.pem: |- - -----BEGIN RSA PRIVATE KEY----- - MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n - gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs - AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB - AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1 - j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP - hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs - hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh - 1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E - pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg - FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT - 6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r - m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ - l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g== - -----END RSA PRIVATE KEY----- - -authEnvs: - jwt_keys_path: "/jwt_pems/" - driver_salt: "12345678901234567890123456789012" - passenger_salt: "12345678901234567890123456789012" - driver_hash_length: "15" - passenger_hash_length: "15" - -envs: - http_port: "9999" - grpc_port: "50051" - mode: "debug" - redis_address: "main-redis-master.dispatching-staging.svc:6379" - redis_pass: "" - redis_pool_size: "10" - redis_max_retries: "0" - redis_read_timeout: "3s" - redis_pool_timeout: "4s" - redis_min_retry_backoff: "8ms" - redis_max_retry_backoff: "512ms" - redis_idle_timeout: "300s" - redis_idle_check_frequency: "60s" - redis_set_member_exp_time: "300s" - redis_min_idle_connections: "5" - logger_level: "debug" - logger_sentry_enabled: "false" - logger_sentry_dsn: "" - logger_sentry_timeout: "500ms" - allowed_access_types: "sub,pub" - cache_enabled: "true" - cache_expiration: "600s" diff --git a/docker-compose.yml b/docker-compose.yml index 09463618..98454bea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,30 +1,30 @@ -version: '3.7' services: soteria: - container_name: soteria - image: soteria:latest - working_dir: /go/src/app - ports: - - 9999:9999 - - 50051:50051 - volumes: - - .:/go/src/app - depends_on: - - redis + build: + dockerfile: ./build/package/Dockerfile + context: . + command: serve + emqx: + image: emqx/emqx environment: - SOTERIA_LOGGER_LEVEL: 'DEBUG' - SOTERIA_JWT_KEYS_PATH: '/go/src/app/test/' - SOTERIA_HTTP_PORT: '9999' - SOTERIA_GRPC_PORT: '50051' - SOTERIA_REDIS_ADDRESS: 'redis:6379' - redis: - image: redis - container_name: soteria-redis - ports: - - 6379:6379 - jaeger: - image: jaegertracing/all-in-one:latest + EMQX_DASHBOARD__DEFAULT_PASSWORD: public + EMQX_DASHBOARD__DEFAULT_USERNAME: admin + EMQX_AUTHENTICATION__1__MECHANISM: "password_based" + EMQX_AUTHENTICATION__1__BACKEND: "http" + EMQX_AUTHENTICATION__1__ENABLE: "true" + EMQX_AUTHENTICATION__1__METHOD: "post" + EMQX_AUTHENTICATION__1__URL: "http://soteria:9999/v2/auth" + EMQX_AUTHENTICATION__1__BODY: '{"username" = "$${username}", "password" = "$${password}", "token" = "$${username}"}' + EMQX_AUTHORIZATION__NO_MATCH: deny + EMQX_AUTHORIZATION__DENY_ACTION: disconnect + EMQX_AUTHORIZATION__SOURCES__1__TYPE: http + EMQX_AUTHORIZATION__SOURCES__1__METHOD: post + EMQX_AUTHORIZATION__SOURCES__1__URL: "http://soteria:9999/v2/acl" + EMQX_AUTHORIZATION__SOURCES__1__BODY: '{"username" = "$${username}", "password" = "$${password}", "token" = "$${username}"}' + EMQX_LISTENERS__TCP__DEFAULT__ENABLE_AUTHN: "quick_deny_anonymous" + EMQX_LISTENERS__TCP__INTERNAL__ENABLE: "true" + EMQX_LISTENERS__TCP__INTERNAL__BIND: 11883 + EMQX_LISTENERS__TCP__INTERNAL__ENABLE_AUTHN: "false" ports: - - 16686:16686 - - "6831:6831/udp" - - 14250:14250 + - 1883:1883 + - 18083:18083 diff --git a/docs/arch.png b/docs/arch.png new file mode 100644 index 00000000..1a5250ef Binary files /dev/null and b/docs/arch.png differ diff --git a/go.mod b/go.mod index 5c0559a3..794a1df8 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,67 @@ -module gitlab.snapp.ir/dispatching/soteria/v3 +module github.com/snapp-incubator/soteria -go 1.15 +go 1.23 require ( - github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect - github.com/alicebob/miniredis/v2 v2.13.1 - github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/gin-contrib/zap v0.0.1 - github.com/gin-gonic/gin v1.6.3 - github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect - github.com/go-playground/validator/v10 v10.3.0 // indirect - github.com/go-redis/redis/v8 v8.3.2 - github.com/golang/protobuf v1.4.2 - github.com/google/uuid v1.1.2 - github.com/kelseyhightower/envconfig v1.4.0 - github.com/opentracing/opentracing-go v1.2.0 - github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/prometheus/client_golang v1.7.1 - github.com/speps/go-hashids v2.0.0+incompatible // indirect - github.com/spf13/cobra v1.0.0 - github.com/stretchr/testify v1.7.0 - github.com/uber/jaeger-client-go v2.25.0+incompatible - github.com/uber/jaeger-lib v2.4.0+incompatible // indirect - gitlab.snapp.ir/dispatching/snappids/v2 v2.6.1 - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/automaxprocs v1.3.0 - go.uber.org/zap v1.16.0 - golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 - google.golang.org/grpc v1.27.0 - google.golang.org/protobuf v1.25.0 // indirect + github.com/ansrivas/fiberprometheus/v2 v2.7.0 + github.com/gofiber/contrib/fiberzap v1.0.2 + github.com/gofiber/fiber/v2 v2.52.5 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/knadh/koanf v1.5.0 + github.com/knadh/koanf/v2 v2.1.1 + github.com/prometheus/client_golang v1.20.3 + github.com/speps/go-hashids/v2 v2.0.1 + github.com/spf13/cobra v1.8.1 + github.com/stretchr/testify v1.9.0 + github.com/tidwall/pretty v1.2.1 + go.opentelemetry.io/otel v1.29.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 + go.opentelemetry.io/otel/sdk v1.29.0 + go.opentelemetry.io/otel/trace v1.29.0 + go.uber.org/automaxprocs v1.5.3 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6a496d2c..955908f7 100644 --- a/go.sum +++ b/go.sum @@ -1,126 +1,92 @@ -bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= -bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= -github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.13.1 h1:twArGFW9kCdxe0K1Ga+iWrZc9RdWalf7V5bQerNoPK0= -github.com/alicebob/miniredis/v2 v2.13.1/go.mod h1:0UIBNuf97uxrWhdVBpJvPtafKyGpL2NS2pYe0tYM97k= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/ansrivas/fiberprometheus/v2 v2.7.0 h1:09XiSzG0J7aZp7RviklngdWdDbSybKjhuWAstp003Gg= +github.com/ansrivas/fiberprometheus/v2 v2.7.0/go.mod h1:hSJdO65lfnWW70Qn9uGdXXsUUSkckbhuw5r/KesygpU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= +github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= +github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= +github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCroCIB1foO/yQ36swABL8aOVeDpgg= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/zap v0.0.1 h1:wsX/ahRftxPiXpiUw0YqyHj+TQTKtv+DAFWH84G1Uvg= -github.com/gin-contrib/zap v0.0.1/go.mod h1:vJJndZ8f44gsTHQrDPIB4YOZzwOwiEIdE0mMrZLOogk= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= -github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= -github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-redis/redis/v8 v8.3.2 h1:1bJscgN2yGtKLW6MsTRosa2LHyeq94j0hnNAgRZzj/M= -github.com/go-redis/redis/v8 v8.3.2/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/contrib/fiberzap v1.0.2 h1:EQwhggtszVfIdBeXxN9Xrmld71es34Ufs+ef8VMqZxc= +github.com/gofiber/contrib/fiberzap v1.0.2/go.mod h1:jGO8BHU4gRI9U0JtM6zj2CIhYfgVmW5JxziN8NTgVwE= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -132,340 +98,429 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8= -github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= -github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= +github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= +github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= -github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= +github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY= +github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw= -github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/speps/go-hashids/v2 v2.0.1 h1:ViWOEqWES/pdOSq+C1SLVa8/Tnsd52XC34RY7lt7m4g= github.com/speps/go-hashids/v2 v2.0.1/go.mod h1:47LKunwvDZki/uRVD6NImtyk712yFzIs3UF3KlHohGw= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= -github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= -github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0= -github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= -gitlab.snapp.ir/dispatching/snappids/v2 v2.4.0 h1:8D/t3baH3OCNXoAtWFb23p9jh3kyJM+wdRkmkc5/1RE= -gitlab.snapp.ir/dispatching/snappids/v2 v2.4.0/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc= -gitlab.snapp.ir/dispatching/snappids/v2 v2.5.0 h1:z3s9L1kcNBGpmTI//BTElJ6EAaq1lOH8tOxU2urgpdI= -gitlab.snapp.ir/dispatching/snappids/v2 v2.5.0/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc= -gitlab.snapp.ir/dispatching/snappids/v2 v2.5.1 h1:jK9/w2o7+5qlQWwss7MqZfH5XGL9mJDfE/q+GXlPluM= -gitlab.snapp.ir/dispatching/snappids/v2 v2.5.1/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc= -gitlab.snapp.ir/dispatching/snappids/v2 v2.5.2 h1:P+PNVwppAee0XzYP9jXPevCyMJO9ESxH+XdoA2IztJY= -gitlab.snapp.ir/dispatching/snappids/v2 v2.5.2/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc= -gitlab.snapp.ir/dispatching/snappids/v2 v2.6.0-beta h1:ztJ35NxA0desQqH8ZRPqQSL/iPuvqqO2HV6XWjepKGA= -gitlab.snapp.ir/dispatching/snappids/v2 v2.6.0-beta/go.mod h1:U7lxAuqPifdXkUNcCXhtk9+6HFAyw5qy8LrF1DQDMuQ= -gitlab.snapp.ir/dispatching/snappids/v2 v2.6.1 h1:G782ZLXcCTI5Kby/s3YPur9S6cJxtLds1NVemWrgzi8= -gitlab.snapp.ir/dispatching/snappids/v2 v2.6.1/go.mod h1:U7lxAuqPifdXkUNcCXhtk9+6HFAyw5qy8LrF1DQDMuQ= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA= -go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= +github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0= -go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0 h1:sfUMP1Gu8qASkorDVjnMuvgJzwFbTZSeXFiGBYAVdl4= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b h1:Qh4dB5D/WpoUUp3lSod7qgoyEHbDGPUWjIbnqdqqe1k= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c h1:e0zB268kOca6FbuJkYUGxfwG4DKFZG/8DLyv9Zv66cE= +google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c h1:Kqjm4WpoWvwhMPcrAczoTyMySQmYa9Wy2iL6Con4zn8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -474,40 +529,30 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/accounts/accounts.go b/internal/accounts/accounts.go deleted file mode 100644 index cb65e0dc..00000000 --- a/internal/accounts/accounts.go +++ /dev/null @@ -1,8 +0,0 @@ -package accounts - -import "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - -// Service is responsible for all things related to account handling. -type Service struct { - Handler db.ModelHandler -} diff --git a/internal/accounts/delete.go b/internal/accounts/delete.go deleted file mode 100644 index dca61387..00000000 --- a/internal/accounts/delete.go +++ /dev/null @@ -1,16 +0,0 @@ -package accounts - -import ( - "context" - - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" -) - -// Delete removes a user by given username and password from database -func (s Service) Delete(ctx context.Context, username string) *errors.Error { - if err := s.Handler.Delete(ctx, "user", username); err != nil { - return errors.CreateError(errors.DatabaseDeleteFailure, err.Error()) - } - - return nil -} diff --git a/internal/accounts/info.go b/internal/accounts/info.go deleted file mode 100644 index 5cbdf3b1..00000000 --- a/internal/accounts/info.go +++ /dev/null @@ -1,23 +0,0 @@ -package accounts - -import ( - "context" - - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "golang.org/x/crypto/bcrypt" -) - -// Info returns a user based on given username and password. -func (s Service) Info(ctx context.Context, username, password string) (*user.User, *errors.Error) { - var u user.User - if err := s.Handler.Get(ctx, "user", username, &u); err != nil { - return nil, errors.CreateError(errors.DatabaseGetFailure, err.Error()) - } - - if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)); err != nil { - return nil, errors.CreateError(errors.WrongUsernameOrPassword, "") - } - - return &u, nil -} diff --git a/internal/accounts/rules.go b/internal/accounts/rules.go deleted file mode 100644 index 2bfbffad..00000000 --- a/internal/accounts/rules.go +++ /dev/null @@ -1,136 +0,0 @@ -package accounts - -import ( - "context" - "time" - - "github.com/google/uuid" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" -) - -// CreateRule will add a new rule with given information to a user -func (s Service) CreateRule(ctx context.Context, username, endpoint string, topicPattern topics.Type, accessType acl.AccessType) (*user.Rule, *errors.Error) { - if err := validateRuleInfo(endpoint, topicPattern, accessType); err != nil { - return nil, err - } - - var u user.User - if err := s.Handler.Get(ctx, "user", username, &u); err != nil { - return nil, errors.CreateError(errors.DatabaseGetFailure, err.Error()) - } - - rule := &user.Rule{ - UUID: uuid.New(), - Endpoint: endpoint, - Topic: topicPattern, - AccessType: accessType, - } - - u.Rules = append(u.Rules, *rule) - - u.MetaData.DateModified = time.Now() - - if err := s.Handler.Update(ctx, u); err != nil { - return nil, errors.CreateError(errors.DatabaseUpdateFailure, err.Error()) - } - - return rule, nil -} - -// GetRule returns a user rule based on given username, password and rule UUID -func (s Service) GetRule(ctx context.Context, username string, id uuid.UUID) (*user.Rule, *errors.Error) { - var u user.User - if err := s.Handler.Get(ctx, "user", username, &u); err != nil { - return nil, errors.CreateError(errors.DatabaseGetFailure, err.Error()) - } - - for _, r := range u.Rules { - if r.UUID == id { - return &r, nil - } - } - - return nil, errors.CreateError(errors.RuleNotFound, "") -} - -// UpdateRule updates an account's rule based on given username, UUID and information -func (s Service) UpdateRule(ctx context.Context, username string, id uuid.UUID, endpoint string, topicPattern topics.Type, accessType acl.AccessType) *errors.Error { - if err := validateRuleInfo(endpoint, topicPattern, accessType); err != nil { - return err - } - - var u user.User - if err := s.Handler.Get(ctx, "user", username, &u); err != nil { - return errors.CreateError(errors.DatabaseGetFailure, err.Error()) - } - - var ruleIndex *int - for i, r := range u.Rules { - if r.UUID == id { - ruleIndex = &i - } - } - if ruleIndex == nil { - return errors.CreateError(errors.RuleNotFound, "") - } - - u.Rules[*ruleIndex].Endpoint = endpoint - u.Rules[*ruleIndex].Topic = topicPattern - u.Rules[*ruleIndex].AccessType = accessType - - u.MetaData.DateModified = time.Now() - - if err := s.Handler.Update(ctx, u); err != nil { - return errors.CreateError(errors.DatabaseUpdateFailure, err.Error()) - } - - return nil -} - -// DeleteRule deletes an account's rule based on given username and UUID -func (s Service) DeleteRule(ctx context.Context, username string, id uuid.UUID) *errors.Error { - var u user.User - if err := s.Handler.Get(ctx, "user", username, &u); err != nil { - return errors.CreateError(errors.DatabaseGetFailure, err.Error()) - } - - var ruleIndex *int - for i, r := range u.Rules { - if r.UUID == id { - ruleIndex = &i - } - } - if ruleIndex == nil { - return errors.CreateError(errors.RuleNotFound, "") - } - - u.Rules = append(u.Rules[:*ruleIndex], u.Rules[*ruleIndex+1:]...) - - u.MetaData.DateModified = time.Now() - - if err := s.Handler.Update(ctx, u); err != nil { - return errors.CreateError(errors.DatabaseUpdateFailure, err.Error()) - } - - return nil -} - -// validateRuleInfo will validate a rule info -func validateRuleInfo(endpoint string, topicPattern topics.Type, accessType acl.AccessType) *errors.Error { - if endpoint == "" && topicPattern == "" && accessType == "" { - return errors.CreateError(errors.InvalidRule, "all rule information is empty") - } - - if accessType == "" { - return errors.CreateError(errors.InvalidRule, "access type is necessary") - } - - if (endpoint != "" && topicPattern != "") || (endpoint == "" && topicPattern == "") { - return errors.CreateError(errors.InvalidRule, "either endpoint or topic pattern should be present") - } - - return nil -} diff --git a/internal/accounts/rules_test.go b/internal/accounts/rules_test.go deleted file mode 100644 index a9c4c72b..00000000 --- a/internal/accounts/rules_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package accounts - -import ( - "github.com/stretchr/testify/assert" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "testing" -) - -func TestValidateRuleInfo(t *testing.T) { - t.Run("test with with invalid rule info", func(t *testing.T) { - endpoint := "" - topicPattern := topics.Type("") - accessType := acl.AccessType("") - - err := validateRuleInfo(endpoint, topicPattern, accessType) - assert.NotNil(t, err) - assert.Equal(t, errors.InvalidRule, err.Code) - - endpoint = "/notification" - topicPattern = topics.DriverLocation - accessType = "" - - err = validateRuleInfo(endpoint, topicPattern, accessType) - assert.NotNil(t, err) - assert.Equal(t, errors.InvalidRule, err.Code) - - endpoint = "/notification" - topicPattern = topics.DriverLocation - accessType = "" - - err = validateRuleInfo(endpoint, topicPattern, accessType) - assert.NotNil(t, err) - assert.Equal(t, errors.InvalidRule, err.Code) - - endpoint = "/notification" - topicPattern = topics.CabEvent - accessType = acl.Pub - - err = validateRuleInfo(endpoint, topicPattern, accessType) - assert.NotNil(t, err) - assert.Equal(t, errors.InvalidRule, err.Code) - - endpoint = "" - topicPattern = topics.DriverLocation - accessType = "" - - err = validateRuleInfo(endpoint, topicPattern, accessType) - assert.NotNil(t, err) - assert.Equal(t, errors.InvalidRule, err.Code) - - endpoint = "" - topicPattern = "" - accessType = acl.Sub - - err = validateRuleInfo(endpoint, topicPattern, accessType) - assert.NotNil(t, err) - assert.Equal(t, errors.InvalidRule, err.Code) - }) - - t.Run("test with with valid rule info", func(t *testing.T) { - endpoint := "/notification" - topicPattern := topics.Type("") - accessType := acl.Pub - - err := validateRuleInfo(endpoint, topicPattern, accessType) - assert.Nil(t, err) - - endpoint = "" - topicPattern = topics.DriverLocation - accessType = acl.Pub - - err = validateRuleInfo(endpoint, topicPattern, accessType) - assert.Nil(t, err) - }) -} diff --git a/internal/accounts/signup.go b/internal/accounts/signup.go deleted file mode 100644 index f6109dbd..00000000 --- a/internal/accounts/signup.go +++ /dev/null @@ -1,35 +0,0 @@ -package accounts - -import ( - "context" - "time" - - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "golang.org/x/crypto/bcrypt" -) - -// SignUp creates a user with the given information in database -func (s Service) SignUp(ctx context.Context, username, password string, userType user.Type) *errors.Error { - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return errors.CreateError(errors.PasswordHashGenerationFailure, err.Error()) - } - - u := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: username, - Password: string(hash), - Type: userType, - } - if err := s.Handler.Save(context.Background(), u); err != nil { - return errors.CreateError(errors.DatabaseSaveFailure, err.Error()) - } - - return nil -} diff --git a/internal/accounts/update.go b/internal/accounts/update.go deleted file mode 100644 index 99b82668..00000000 --- a/internal/accounts/update.go +++ /dev/null @@ -1,57 +0,0 @@ -package accounts - -import ( - "context" - "time" - - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "golang.org/x/crypto/bcrypt" -) - -// Update updates given username with given new data in `newInfo` in database -func (s Service) Update( - ctx context.Context, - username string, - newPassword string, - newType user.Type, - newIPs []string, - newSecret string, - newTokenExpiration time.Duration) *errors.Error { - var u user.User - if err := s.Handler.Get(context.Background(), "user", username, &u); err != nil { - return errors.CreateError(errors.DatabaseGetFailure, err.Error()) - } - - if newPassword != "" { - hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) - if err != nil { - return errors.CreateError(errors.PasswordHashGenerationFailure, err.Error()) - } - u.Password = string(hash) - } - - if newType != "" { - u.Type = newType - } - - if newSecret != "" { - u.Secret = newSecret - } - - if newIPs != nil && len(newIPs) != 0 { - u.IPs = newIPs - } - - if newTokenExpiration != 0 { - u.TokenExpirationDuration = newTokenExpiration - } - - u.MetaData.DateModified = time.Now() - - if err := s.Handler.Update(context.Background(), u); err != nil { - return errors.CreateError(errors.DatabaseUpdateFailure, err.Error()) - } - - return nil -} diff --git a/internal/api/acl.go b/internal/api/acl.go new file mode 100644 index 00000000..222bc177 --- /dev/null +++ b/internal/api/acl.go @@ -0,0 +1,221 @@ +package api + +import ( + "errors" + "net/http" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/pkg/acl" + "go.opentelemetry.io/otel/attribute" + "go.uber.org/zap" +) + +type ACLResponse struct { + Result string `json:"result,omitempty"` +} + +// AclRequest is the body payload structure of the ACL endpoint. +type ACLRequest struct { + Access acl.AccessType `form:"access"` + Token string `form:"token"` + Username string `from:"username"` + Password string `form:"password"` + Topic string `form:"topic"` +} + +// ACL is the handler responsible for ACL requests. +// nolint: wrapcheck, funlen +func (a API) ACLv1(c *fiber.Ctx) error { + _, span := a.Tracer.Start(c.Context(), "api.v1.acl") + defer span.End() + + request := new(ACLRequest) + if err := c.BodyParser(request); err != nil { + a.Logger. + Warn("acl bad request", + zap.Error(err), + zap.String("access", request.Access.String()), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + ) + authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err) + + return c.Status(http.StatusBadRequest).SendString("bad request") + } + + vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password) + + topic := request.Topic + auth := a.Authenticator(vendor) + + span.SetAttributes( + attribute.String("access", request.Access.String()), + attribute.String("topic", request.Topic), + attribute.String("token", request.Token), + attribute.String("username", request.Username), + attribute.String("password", request.Password), + attribute.String("authenticator", auth.GetCompany()), + ) + + ok, err := auth.ACL(request.Access, token, topic) + if err != nil || !ok { + if err != nil { + span.RecordError(err) + } + + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + var tnaErr authenticator.TopicNotAllowedError + + if errors.As(err, &tnaErr) { + a.Logger. + Warn("acl request is not authorized", + zap.Error(tnaErr), + zap.String("access", request.Access.String()), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany())) + } else if !errors.Is(err, jwt.ErrTokenExpired) { + a.Logger. + Error("acl request is not authorized", + zap.Error(err), + zap.String("access", request.Access.String()), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany())) + } + + return c.Status(http.StatusUnauthorized).SendString("request is not authorized") + } + + a.Logger. + Info("acl ok", + zap.String("access", request.Access.String()), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany()), + ) + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + return c.Status(http.StatusOK).SendString("ok") +} + +// ACLv2Request is the body payload structure of the ACL endpoint. +type ACLv2Request struct { + Token string `json:"token"` + Username string `json:"username"` + Password string `json:"password"` + Topic string `json:"topic"` + Action string `json:"action"` +} + +// ACLv2 is the handler responsible for ACL requests coming from EMQv5. +// https://www.emqx.io/docs/en/latest/access-control/authz/http.html +// nolint: wrapcheck, funlen +func (a API) ACLv2(c *fiber.Ctx) error { + _, span := a.Tracer.Start(c.Context(), "api.v2.acl") + defer span.End() + + request := new(ACLv2Request) + if err := c.BodyParser(request); err != nil { + a.Logger. + Warn("acl bad request", + zap.Error(err), + zap.String("access", request.Action), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + ) + authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err) + + return c.Status(http.StatusOK).JSON(ACLResponse{ + Result: "deny", + }) + } + + vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password) + + topic := request.Topic + auth := a.Authenticator(vendor) + + span.SetAttributes( + attribute.String("access", request.Action), + attribute.String("topic", request.Topic), + attribute.String("token", request.Token), + attribute.String("username", request.Username), + attribute.String("password", request.Password), + attribute.String("authenticator", auth.GetCompany()), + ) + + var access acl.AccessType + + switch request.Action { + case "publish": + access = acl.Pub + case "subscribe": + access = acl.Sub + } + + ok, err := auth.ACL(access, token, topic) + if err != nil || !ok { + if err != nil { + span.RecordError(err) + } + + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + var tnaErr authenticator.TopicNotAllowedError + + if errors.As(err, &tnaErr) { + a.Logger. + Warn("acl request is not authorized", + zap.Error(tnaErr), + zap.String("access", request.Action), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany())) + } else if !errors.Is(err, jwt.ErrTokenExpired) { + a.Logger. + Error("acl request is not authorized", + zap.Error(err), + zap.String("access", request.Action), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany())) + } + + return c.Status(http.StatusOK).JSON(ACLResponse{ + Result: "deny", + }) + } + + a.Logger. + Info("acl ok", + zap.String("access", request.Action), + zap.String("topic", request.Topic), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany()), + ) + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + return c.Status(http.StatusOK).JSON(ACLResponse{ + Result: "allow", + }) +} diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 00000000..0cce488e --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,87 @@ +package api + +import ( + "strings" + + "github.com/ansrivas/fiberprometheus/v2" + "github.com/gofiber/contrib/fiberzap" + "github.com/gofiber/fiber/v2" + "github.com/snapp-incubator/soteria/internal/authenticator" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" +) + +const VendorTokenSeparator = ":" + +type API struct { + Authenticators map[string]authenticator.Authenticator + DefaultVendor string + Tracer trace.Tracer + Logger *zap.Logger +} + +// MetricLogSkipper check if route is equal "metric" disable log. +func MetricLogSkipper(ctx *fiber.Ctx) bool { + route := string(ctx.Request().URI().Path()) + + return route == "/metrics" +} + +// ReSTServer will return fiber app. +func (a API) ReSTServer() *fiber.App { + app := fiber.New() + + //nolint: exhaustruct + app.Use(fiberzap.New(fiberzap.Config{ + Next: MetricLogSkipper, + Logger: a.Logger.Named("fiber"), + })) + + prometheus := fiberprometheus.NewWith("http", "dispatching", "soteria") + prometheus.RegisterAt(app, "/metrics") + app.Use(prometheus.Middleware) + + app.Post("/auth", a.Authv1) + app.Post("/acl", a.ACLv1) + + app.Post("/v2/auth", a.Authv2) + app.Post("/v2/acl", a.ACLv2) + + return app +} + +func (a API) Authenticator(vendor string) authenticator.Authenticator { + auth, ok := a.Authenticators[vendor] + + if ok { + return auth + } + + return a.Authenticators[a.DefaultVendor] +} + +func ExtractVendorToken(rawToken, username, password string) (string, string) { + tokenString := rawToken + + if len(tokenString) == 0 { + tokenString = username + } + + if len(tokenString) == 0 { + tokenString = password + } + + split := strings.Split(tokenString, VendorTokenSeparator) + + var vendor, token string + + if len(split) == 2 { //nolint:mnd + vendor = split[0] + token = split[1] + } else { + vendor = "" + token = split[0] + } + + return vendor, token +} diff --git a/internal/api/api_test.go b/internal/api/api_test.go new file mode 100644 index 00000000..f5d895c6 --- /dev/null +++ b/internal/api/api_test.go @@ -0,0 +1,205 @@ +package api_test + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/api" + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/stretchr/testify/suite" + "go.opentelemetry.io/otel/trace/noop" + "go.uber.org/zap" +) + +func getSampleToken(key string) (string, error) { + exp := time.Now().Add(time.Hour * 24 * 365 * 10) + sub := "DXKgaNQa7N5Y7bo" + + // nolint: exhaustruct + claims := jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(exp), + Issuer: "Colony", + Subject: sub, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims) + + tokenString, err := token.SignedString([]byte(key)) + if err != nil { + return "", fmt.Errorf("cannot generate a signed string %w", err) + } + + return tokenString, nil +} + +// nolint: funlen +func TestExtractVendorToken(t *testing.T) { + t.Parallel() + + type fields struct { + Token string + Username string + Password string + } + + tests := []struct { + name string + fields fields + vendor string + token string + }{ + { + name: "token field as token", + fields: fields{ + Token: "vendor:token", + Username: "vendor:username", + Password: "password", + }, + vendor: "vendor", + token: "token", + }, + { + name: "username as token without vendor", + fields: fields{ + Token: "", + Username: "username", + Password: "password", + }, + vendor: "", + token: "username", + }, + { + name: "username as token with vendor", + fields: fields{ + Token: "", + Username: "vendor:username", + Password: "", + }, + vendor: "vendor", + token: "username", + }, + { + name: "password as token", + fields: fields{ + Token: "", + Username: "", + Password: "vendor:password", + }, + vendor: "vendor", + token: "password", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + vendor, token := api.ExtractVendorToken(tt.fields.Token, tt.fields.Username, tt.fields.Password) + if vendor != tt.vendor || token != tt.token { + t.Errorf("ExtractVendorToken() vendor = %v, vendor %v", vendor, tt.vendor) + } + }) + } +} + +type APITestSuite struct { + suite.Suite + + app *fiber.App + key string +} + +func (suite *APITestSuite) SetupSuite() { + suite.key = "secret" + + app := fiber.New() + + a := api.API{ + Authenticators: map[string]authenticator.Authenticator{ + "snapp-admin": authenticator.AdminAuthenticator{ + Key: []byte(suite.key), + Company: "snapp-admin", + JwtConfig: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "HS512", + }, + Parser: jwt.NewParser(), + }, + }, + DefaultVendor: "snapp", + Tracer: noop.NewTracerProvider().Tracer(""), + Logger: zap.NewExample(), + } + + app.Post("/v2/auth", a.Authv2) + + suite.app = app +} + +func (suite *APITestSuite) BadRequest() { + require := suite.Require() + + body, err := json.Marshal(api.AuthRequest{ + Token: "", + Username: "not-found:token", + Password: "", + }) + require.NoError(err) + + req := httptest.NewRequest(http.MethodPost, "/v2/auth", bytes.NewReader(body)) + + resp, err := suite.app.Test(req) + require.NoError(err) + + defer resp.Body.Close() + + require.Equal(http.StatusBadRequest, resp.StatusCode) +} + +func (suite *APITestSuite) ValidToken() { + require := suite.Require() + + token, err := getSampleToken(suite.key) + require.NoError(err) + + body, err := json.Marshal(api.AuthRequest{ + Token: "", + Username: "snapp-admin:" + token, + Password: "", + }) + require.NoError(err) + + req := httptest.NewRequest(http.MethodPost, "/v2/auth", bytes.NewReader(body)) + req.Header.Add("Content-Type", "application/json") + + resp, err := suite.app.Test(req) + require.NoError(err) + + defer resp.Body.Close() + + require.Equal(http.StatusOK, resp.StatusCode) + + data, err := io.ReadAll(resp.Body) + require.NoError(err) + + var authResp api.AuthResponse + + require.NoError(json.Unmarshal(data, &authResp)) + + require.Equal("allow", authResp.Result) + require.True(authResp.IsSuperuser) +} + +func TestAPITestSuite(t *testing.T) { + t.Parallel() + + suite.Run(t, new(APITestSuite)) +} diff --git a/internal/api/auth.go b/internal/api/auth.go new file mode 100644 index 00000000..d85f88d6 --- /dev/null +++ b/internal/api/auth.go @@ -0,0 +1,159 @@ +package api + +import ( + "errors" + "net/http" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/authenticator" + "go.opentelemetry.io/otel/attribute" + "go.uber.org/zap" +) + +// AuthRequest is the body payload structure of the auth endpoint. +type AuthRequest struct { + Token string `form:"token" json:"token,omitempty"` + Username string `from:"username" json:"username,omitempty"` + Password string `form:"password" json:"password,omitempty"` +} + +type AuthResponse struct { + Result string `json:"result,omitempty"` + IsSuperuser bool `json:"is_superuser,omitempty"` +} + +// Auth is the handler responsible for authentication. +// nolint: wrapcheck +func (a API) Authv1(c *fiber.Ctx) error { + _, span := a.Tracer.Start(c.Context(), "api.v1.auth") + defer span.End() + + request := new(AuthRequest) + + if err := c.BodyParser(request); err != nil { + span.RecordError(err) + + a.Logger. + Warn("bad request", + zap.Error(err), + ) + authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err) + + return c.Status(http.StatusBadRequest).SendString("bad request") + } + + vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password) + + auth := a.Authenticator(vendor) + + span.SetAttributes( + attribute.String("token", request.Token), + attribute.String("username", request.Username), + attribute.String("password", request.Password), + attribute.String("authenticator", auth.GetCompany()), + ) + + err := auth.Auth(token) + if err != nil { + authenticator.IncrementWithErrorAuthCounter(vendor, err) + span.RecordError(err) + + if !errors.Is(err, jwt.ErrTokenExpired) { + a.Logger. + Debug("auth request is not authorized", + zap.Error(err), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany()), + ) + } + + return c.Status(http.StatusUnauthorized).SendString("request is not authorized") + } + + a.Logger. + Info("auth ok", + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany()), + ) + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + return c.Status(http.StatusOK).SendString("ok") +} + +// Auth is the handler responsible for authentication. +// Endpoint will be used by EMQ version 5 which supports JSON on both request and response. +// https://www.emqx.io/docs/en/latest/access-control/authn/http.html +// nolint: wrapcheck, funlen +func (a API) Authv2(c *fiber.Ctx) error { + _, span := a.Tracer.Start(c.Context(), "api.v2.auth") + defer span.End() + + request := new(AuthRequest) + + if err := c.BodyParser(request); err != nil { + span.RecordError(err) + + a.Logger. + Warn("bad request", + zap.Error(err), + ) + authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err) + + return c.Status(http.StatusOK).JSON(AuthResponse{ + Result: "deny", + IsSuperuser: false, + }) + } + + vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password) + + auth := a.Authenticator(vendor) + + span.SetAttributes( + attribute.String("token", request.Token), + attribute.String("username", request.Username), + attribute.String("password", request.Password), + attribute.String("authenticator", auth.GetCompany()), + ) + + err := auth.Auth(token) + if err != nil { + span.RecordError(err) + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + if !errors.Is(err, jwt.ErrTokenExpired) { + a.Logger. + Debug("auth request is not authorized", + zap.Error(err), + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany()), + ) + } + + return c.Status(http.StatusOK).JSON(AuthResponse{ + Result: "deny", + IsSuperuser: false, + }) + } + + a.Logger. + Info("auth ok", + zap.String("token", request.Token), + zap.String("username", request.Username), + zap.String("password", request.Password), + zap.String("authenticator", auth.GetCompany()), + ) + authenticator.IncrementWithErrorAuthCounter(vendor, err) + + return c.Status(http.StatusOK).JSON(AuthResponse{ + Result: "allow", + IsSuperuser: auth.IsSuperuser(), + }) +} diff --git a/internal/app/a.go b/internal/app/a.go deleted file mode 100644 index 761ecb9a..00000000 --- a/internal/app/a.go +++ /dev/null @@ -1,55 +0,0 @@ -package app - -import ( - "io" - "sync" - - "github.com/opentracing/opentracing-go" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/metrics" -) - -type app struct { - Metrics metrics.Metrics - Authenticator *authenticator.Authenticator - AccountsService *accounts.Service - EMQStore emq.Store - Tracer opentracing.Tracer - TracerCloser io.Closer -} - -var ( - singleton *app - once sync.Once -) - -func GetInstance() *app { - once.Do(func() { - singleton = &app{} - }) - - return singleton -} - -func (a *app) SetEMQStore(store emq.Store) { - a.EMQStore = store -} - -func (a *app) SetAuthenticator(authenticator *authenticator.Authenticator) { - a.Authenticator = authenticator -} - -func (a *app) SetMetrics(m metrics.Metrics) { - a.Metrics = m -} - -func (a *app) SetAccountsService(service *accounts.Service) { - a.AccountsService = service -} - -func (a *app) SetTracer(tracer opentracing.Tracer, closer io.Closer) { - a.Tracer = tracer - a.TracerCloser = closer -} diff --git a/internal/authenticator/admin_authenticator.go b/internal/authenticator/admin_authenticator.go new file mode 100644 index 00000000..8b606c26 --- /dev/null +++ b/internal/authenticator/admin_authenticator.go @@ -0,0 +1,65 @@ +package authenticator + +import ( + "fmt" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/pkg/acl" +) + +// AdminAuthenticator is responsible for Acl/Auth/Token of the internal system userIDs, +// these userIDs have admin access. +type AdminAuthenticator struct { + Key any + Company string + JwtConfig config.JWT + Parser *jwt.Parser +} + +// Auth check user authentication by checking the user's token +// isSuperuser is a flag that authenticator set it true when credentials is related to a superuser. +func (a AdminAuthenticator) Auth(tokenString string) error { + _, err := a.Parser.Parse(tokenString, func( + token *jwt.Token, + ) (interface{}, error) { + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return nil, ErrInvalidClaims + } + + if claims[a.JwtConfig.IssName] == nil { + return nil, ErrIssNotFound + } + + return a.Key, nil + }) + if err != nil { + return fmt.Errorf("token is invalid: %w", err) + } + + return nil +} + +// ACL check a system user access to a topic. +// because we returns is-admin: true, this endpoint shouldn't +// be called. +func (a AdminAuthenticator) ACL( + _ acl.AccessType, + _ string, + _ string, +) (bool, error) { + return true, nil +} + +func (a AdminAuthenticator) ValidateAccessType(_ acl.AccessType) bool { + return true +} + +func (a AdminAuthenticator) GetCompany() string { + return a.Company +} + +func (a AdminAuthenticator) IsSuperuser() bool { + return true +} diff --git a/internal/authenticator/admin_authenticator_test.go b/internal/authenticator/admin_authenticator_test.go new file mode 100644 index 00000000..8b1992e0 --- /dev/null +++ b/internal/authenticator/admin_authenticator_test.go @@ -0,0 +1,65 @@ +package authenticator_test + +import ( + "testing" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type AdminAuthenticatorTestSuite struct { + suite.Suite + + AdminToken string + + Authenticator authenticator.Authenticator +} + +func TestAdminAuthenticator_suite(t *testing.T) { + t.Parallel() + + st := new(AdminAuthenticatorTestSuite) + + pkey0, err := getPublicKey("admin") + require.NoError(t, err) + + st.Authenticator = authenticator.AdminAuthenticator{ + Key: pkey0, + Company: "snapp-admin", + Parser: jwt.NewParser(), + JwtConfig: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "rsa256", + }, + } + + suite.Run(t, st) +} + +func (suite *AdminAuthenticatorTestSuite) SetupSuite() { + require := suite.Require() + + key, err := getPrivateKey("admin") + require.NoError(err) + + adminToken, err := getSampleToken("admin", key) + require.NoError(err) + + suite.AdminToken = adminToken +} + +func (suite *AdminAuthenticatorTestSuite) TestAuth() { + require := suite.Require() + + suite.Run("testing admin token auth", func() { + require.NoError(suite.Authenticator.Auth(suite.AdminToken)) + }) + + suite.Run("testing invalid token auth", func() { + require.Error(suite.Authenticator.Auth(invalidToken)) + }) +} diff --git a/internal/authenticator/authenticator.go b/internal/authenticator/authenticator.go index b7a5505a..21805220 100644 --- a/internal/authenticator/authenticator.go +++ b/internal/authenticator/authenticator.go @@ -1,343 +1,28 @@ package authenticator import ( - "context" - "crypto/rsa" - "errors" - "fmt" - "time" - - "github.com/dgrijalva/jwt-go" - "gitlab.snapp.ir/dispatching/snappids/v2" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" -) - -var ( - ErrInvalidSigningMethod = errors.New("token is not valid, signing method is not RSA") - ErrIssNotFound = errors.New("could not found iss in token claims") - ErrSubNotFound = errors.New("could not found sub in token claims") - ErrInvalidClaims = errors.New("invalid claims") - ErrInvalidIP = errors.New("IP is not valid") - ErrInvalidAccessType = errors.New("requested access type is invalid") - ErrDecodeHashID = errors.New("could not decode hash id") - ErrInvalidSecret = errors.New("invalid secret") - ErrIncorrectPassword = errors.New("username or password is worng") + "github.com/snapp-incubator/soteria/pkg/acl" ) -type ErrTopicNotAllowed struct { - Issuer user.Issuer - Sub string - AccessType acl.AccessType - Topic topics.Topic -} - -func (err ErrTopicNotAllowed) Error() string { - return fmt.Sprintf("issuer %s with sub %s is not allowed to %s on topic %s (%s)", - err.Issuer, err.Sub, err.AccessType, err.Topic, err.Topic.GetType(), - ) -} - -type ErrPublicKeyNotFound struct { - Issuer user.Issuer -} - -func (err ErrPublicKeyNotFound) Error() string { - return fmt.Sprintf("cannot find issuer %s public key", err.Issuer) -} - -type ErrInvalidTopic struct { - Topic topics.Topic -} - -func (err ErrInvalidTopic) Error() string { - return fmt.Sprintf("provided topic %s is not valid", err.Topic) -} - -// Authenticator is responsible for Acl/Auth/Token of users. -type Authenticator struct { - PrivateKeys map[user.Issuer]*rsa.PrivateKey - PublicKeys map[user.Issuer]*rsa.PublicKey - AllowedAccessTypes []acl.AccessType - ModelHandler db.ModelHandler - EMQTopicManager *snappids.EMQTopicManager - HashIDSManager *snappids.HashIDSManager - CompareHashAndPassword func([]byte, []byte) error - Company string -} - -// Auth check user authentication by checking the user's token -// isSuperuser is a flag that authenticator set it true when credentials is related to a superuser. -func (a Authenticator) Auth(ctx context.Context, tokenString string) (isSuperuser bool, err error) { - _, err = jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) { - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, ErrInvalidSigningMethod - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return nil, ErrInvalidClaims - } - if claims["iss"] == nil { - return nil, ErrIssNotFound - } - - _, isSuperuser = claims["is_superuser"] - - issuer := user.Issuer(fmt.Sprintf("%v", claims["iss"])) - - key := a.PublicKeys[issuer] - if key == nil { - return nil, ErrPublicKeyNotFound{Issuer: issuer} - } - - return key, nil - }) - - if err != nil { - return false, fmt.Errorf("token is invalid: %w", err) - } - - return isSuperuser, nil -} - -// ACL check a user access to a topic. -func (a Authenticator) ACL(ctx context.Context, accessType acl.AccessType, - tokenString string, topic topics.Topic) (bool, error) { - if !a.validateAccessType(accessType) { - return false, ErrInvalidAccessType - } - - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, ErrInvalidSigningMethod - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return nil, ErrInvalidClaims - } - if claims["iss"] == nil { - return nil, ErrIssNotFound - } - if claims["sub"] == nil { - return nil, ErrSubNotFound - } - - issuer := user.Issuer(fmt.Sprintf("%v", claims["iss"])) - key := a.PublicKeys[issuer] - if key == nil { - return nil, ErrPublicKeyNotFound{Issuer: issuer} - } - - return key, nil - }) - if err != nil { - return false, fmt.Errorf("token is invalid %w", err) - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return false, ErrInvalidClaims - } - - if claims["iss"] == nil { - return false, ErrIssNotFound - } - - issuer := user.Issuer(fmt.Sprintf("%v", claims["iss"])) - - if claims["sub"] == nil { - return false, ErrSubNotFound - } - - sub := fmt.Sprintf("%v", claims["sub"]) - - pk := primaryKey(issuer, sub) - - var u user.User - if err := a.ModelHandler.Get(ctx, "user", pk, &u); err != nil { - return false, fmt.Errorf("error getting user %s from db err: %w", pk, err) - } +type Authenticator interface { + // Auth check user authentication by checking the user's token. + // it retruns error in case of any issue with the user token. + Auth(tokenString string) error - // validate passenger and driver topics. - if issuer != user.ThirdParty { - id, err := a.HashIDSManager.DecodeHashID(sub, issuerToAudience(issuer)) - if err != nil { - return false, ErrDecodeHashID - } + // ACL check a user access to a topic. + ACL( + accessType acl.AccessType, + tokenString string, + topic string, + ) (bool, error) - ok := a.ValidateTopicBySender(topic, issuerToAudience(issuer), id) - if !ok { - return false, ErrInvalidTopic{Topic: topic} - } - } + // ValidateAccessType checks access type for specific topic + ValidateAccessType(accessType acl.AccessType) bool - if ok := u.CheckTopicAllowance(topic.GetTypeWithCompany(a.Company), accessType); !ok { - return false, - ErrTopicNotAllowed{issuer, sub, accessType, topic} - } - - return true, nil -} - -// Token function issues JWT token by taking client credentials. -func (a Authenticator) Token(ctx context.Context, _ acl.AccessType, - username, secret string) (string, error) { - var u user.User - if err := a.ModelHandler.Get(ctx, "user", username, &u); err != nil { - return "", fmt.Errorf("could not get user %s. %w", username, err) - } - - if u.Secret != secret { - return "", ErrInvalidSecret - } - - // nolint: exhaustivestruct - claims := jwt.StandardClaims{ - ExpiresAt: time.Now().Add(u.TokenExpirationDuration).Unix(), - Issuer: string(user.ThirdParty), - Subject: username, - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - - tokenString, err := token.SignedString(a.PrivateKeys[user.ThirdParty]) - if err != nil { - return "", fmt.Errorf("could not sign the token %w", err) - } - - return tokenString, nil -} - -func (a Authenticator) HeraldToken( - username string, - endpoints []acl.Endpoint, - topics []acl.Topic, - duration time.Duration, -) (string, error) { - // nolint: exhaustivestruct - claims := acl.Claims{ - StandardClaims: jwt.StandardClaims{ - ExpiresAt: time.Now().Add(duration).Unix(), - Issuer: string(user.ThirdParty), - Subject: username, - }, - Topics: topics, - Endpoints: endpoints, - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - - tokenString, err := token.SignedString(a.PrivateKeys[user.ThirdParty]) - if err != nil { - return "", fmt.Errorf("could not sign the token. %w", err) - } - - return tokenString, nil -} - -func (a Authenticator) SuperuserToken(username string, duration time.Duration) (string, error) { - // nolint: exhaustivestruct - claims := acl.SuperuserClaims{ - StandardClaims: jwt.StandardClaims{ - ExpiresAt: time.Now().Add(duration).Unix(), - Issuer: string(user.ThirdParty), - Subject: username, - }, - IsSuperuser: true, - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - - tokenString, err := token.SignedString(a.PrivateKeys[user.ThirdParty]) - if err != nil { - return "", fmt.Errorf("could not sign the token. %w", err) - } - - return tokenString, nil -} - -func (a Authenticator) EndPointBasicAuth(ctx context.Context, username, password, endpoint string) (bool, error) { - var u user.User - if err := a.ModelHandler.Get(ctx, "user", username, &u); err != nil { - return false, fmt.Errorf("could not get user from db: %w", err) - } - - if err := a.CompareHashAndPassword([]byte(u.Password), []byte(password)); err != nil { - return false, ErrIncorrectPassword - } - - return u.CheckEndpointAllowance(endpoint), nil -} - -func (a Authenticator) EndpointIPAuth(ctx context.Context, username string, ip string, endpoint string) (bool, error) { - var u user.User - if err := a.ModelHandler.Get(ctx, "user", username, &u); err != nil { - return false, fmt.Errorf("could not get user from db: %w", err) - } - - if ok := acl.ValidateIP(ip, u.IPs, []string{}); !ok { - return false, ErrInvalidIP - } - - return u.CheckEndpointAllowance(endpoint), nil -} - -func (a Authenticator) validateAccessType(accessType acl.AccessType) bool { - for _, allowedAccessType := range a.AllowedAccessTypes { - if allowedAccessType == accessType { - return true - } - } - - return false -} - -func primaryKey(issuer user.Issuer, sub string) string { - if issuer == user.Passenger { - return "passenger" - } else if issuer == user.Driver { - return "driver" - } - - return sub -} - -func (a Authenticator) ValidateTopicBySender(topic topics.Topic, audience snappids.Audience, id int) bool { - var ch snappids.Topic - - switch topic.GetType() { - case topics.CabEvent: - ch, _ = a.EMQTopicManager.CreateCabEventTopic(id, audience) - case topics.DriverLocation, topics.PassengerLocation: - ch, _ = a.EMQTopicManager.CreateLocationTopic(id, audience) - case topics.SuperappEvent: - ch, _ = a.EMQTopicManager.CreateSuperAppEventTopic(id, audience) - case topics.SharedLocation: - ch, _ = a.EMQTopicManager.CreateSharedLocationTopic(id, audience) - case topics.Chat: - ch, _ = a.EMQTopicManager.CreateChatTopic(id, audience) - case topics.DaghighSys: - return true - case topics.BoxEvent: - return true - } - - return string(ch) == string(topic) -} + // GetCompany Return the Company Field of The Inherited Objects + GetCompany() string -func issuerToAudience(issuer user.Issuer) snappids.Audience { - switch issuer { - case user.Passenger: - return snappids.PassengerAudience - case user.Driver: - return snappids.DriverAudience - case user.ThirdParty: - return snappids.ThirdPartyAudience - default: - return -1 - } + // IsSuperuser changes the Auth response in case of successful authentication + // and shows user as superuser which disables the ACL. + IsSuperuser() bool } diff --git a/internal/authenticator/authenticator_test.go b/internal/authenticator/authenticator_test.go index 5cc52db8..91faa21a 100644 --- a/internal/authenticator/authenticator_test.go +++ b/internal/authenticator/authenticator_test.go @@ -1,27 +1,19 @@ -package authenticator +package authenticator_test import ( - "context" "crypto/rsa" + "errors" "fmt" - "io/ioutil" - "testing" + "os" "time" - "github.com/dgrijalva/jwt-go" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - snappids "gitlab.snapp.ir/dispatching/snappids/v2" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/memoize" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "golang.org/x/crypto/bcrypt" + "github.com/golang-jwt/jwt/v5" ) const ( - invalidToken = "ey1JhbGciOiJSUzI1NiIsInR5cCI56kpXVCJ9.eyJzdWIiOiJCRzdScDFkcnpWRE5RcjYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaXNzIjowLCJpYXQiOjE1MTYyMzkwMjJ9.1cYXFEhcewOYFjGJYhB8dsaFO9uKEXwlM8954rkt4Tsu0lWMITbRf_hHh1l9QD4MFqD-0LwRPUYaiaemy0OClMu00G2sujLCWaquYDEP37iIt8RoOQAh8Jb5vT8LX5C3PEKvbW_i98u8HHJoFUR9CXJmzrKi48sAcOYvXVYamN0S9KoY38H-Ze37Mdu3o6B58i73krk7QHecsc2_PkCJisvUVAzb0tiInIalBc8-zI3QZSxwNLr_hjlBg1sUxTUvH5SCcRR7hxI8TxJzkOHqAHWDRO84NC_DSAoO2p04vrHpqglN9XPJ8RC2YWpfefvD2ttH554RJWu_0RlR2kAYvQ" + // nolint: gosec, lll + invalidToken = "ey1JhbGciOiJSUzI1NiIsInR5cCI56kpXVCJ9.eyJzdWIiOiJCRzdScDFkcnpWRE5RcjYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaXNzIjowLCJpYXQiOjE1MTYyMzkwMjJ9.1cYXFEhcewOYFjGJYhB8dsaFO9uKEXwlM8954rkt4Tsu0lWMITbRf_hHh1l9QD4MFqD-0LwRPUYaiaemy0OClMu00G2sujLCWaquYDEP37iIt8RoOQAh8Jb5vT8LX5C3PEKvbW_i98u8HHJoFUR9CXJmzrKi48sAcOYvXVYamN0S9KoY38H-Ze37Mdu3o6B58i73krk7QHecsc2_PkCJisvUVAzb0tiInIalBc8-zI3QZSxwNLr_hjlBg1sUxTUvH5SCcRR7hxI8TxJzkOHqAHWDRO84NC_DSAoO2p04vrHpqglN9XPJ8RC2YWpfefvD2ttH554RJWu_0RlR2kAYvQ" + validPassengerCabEventTopic = "passenger-event-152384980615c2bd16143cff29038b67" invalidPassengerCabEventTopic = "passenger-event-152384980615c2bd16156cff29038b67" @@ -31,11 +23,11 @@ const ( validDriverLocationTopic = "snapp/driver/DXKgaNQa7N5Y7bo/location" invalidDriverLocationTopic = "snapp/driver/DXKgaNQa9Q5Y7bo/location" - validPassengerSuperappEventTopic = "snapp/passenger/0956923be632d673560af9adadd2f78a/superapp" - invalidPassengerSuperappEventTopic = "snapp/passenger/0959623be632d673560af9adadd2f78a/superapp" + validPassengerSuperappEventTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/superapp" + invalidPassengerSuperappEventTopic = "snapp/passenger/DXKgaNQa9Q5Y7bo/superapp" - validDriverSuperappEventTopic = "snapp/driver/0956923be632d673560af9adadd2f78a/superapp" - invalidDriverSuperappEventTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/superapp" + validDriverSuperappEventTopic = "snapp/driver/DXKgaNQa7N5Y7bo/superapp" + invalidDriverSuperappEventTopic = "snapp/driver/DXKgaNQa9Q5Y7bo/superapp" validDriverSharedTopic = "snapp/driver/DXKgaNQa7N5Y7bo/passenger-location" validPassengerSharedTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/driver-location" @@ -46,724 +38,94 @@ const ( validPassengerChatTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/chat" invalidDriverChatTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/chat" invalidPassengerChatTopic = "snapp/passenger/0596923be632d673560af9adadd2f78a/chat" -) - -func TestAuthenticator_Auth(t *testing.T) { - driverToken, err := getSampleToken(user.Driver, false) - if err != nil { - t.Fatal(err) - } - passengerToken, err := getSampleToken(user.Passenger, false) - if err != nil { - t.Fatal(err) - } - thirdPartyToken, err := getSampleToken(user.ThirdParty, false) - if err != nil { - t.Fatal(err) - } - superuserToken, err := getSampleToken(user.ThirdParty, true) - if err != nil { - t.Fatal(err) - } - pkey0, err := getPublicKey(user.Driver) - if err != nil { - t.Fatal(err) - } - pkey1, err := getPublicKey(user.Passenger) - if err != nil { - t.Fatal(err) - } - pkey100, err := getPublicKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - key100, err := getPrivateKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - passwordChecker := memoize.MemoizedCompareHashAndPassword() - authenticator := Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: key100, - }, - PublicKeys: map[user.Issuer]*rsa.PublicKey{ - user.Driver: pkey0, - user.Passenger: pkey1, - user.ThirdParty: pkey100, - }, - ModelHandler: MockModelHandler{}, - CompareHashAndPassword: passwordChecker, - } - t.Run("testing driver token auth", func(t *testing.T) { - ok, err := authenticator.Auth(context.Background(), driverToken) - assert.NoError(t, err) - assert.False(t, ok) - }) - - t.Run("testing passenger token auth", func(t *testing.T) { - ok, err := authenticator.Auth(context.Background(), passengerToken) - assert.NoError(t, err) - assert.False(t, ok) - }) - - t.Run("testing third party token auth", func(t *testing.T) { - ok, err := authenticator.Auth(context.Background(), thirdPartyToken) - assert.NoError(t, err) - assert.False(t, ok) - }) - - t.Run("testing superuser token auth", func(t *testing.T) { - ok, err := authenticator.Auth(context.Background(), superuserToken) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing invalid token auth", func(t *testing.T) { - ok, err := authenticator.Auth(context.Background(), invalidToken) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestAuthenticator_Token(t *testing.T) { - key, err := getPrivateKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - pk, err := getPublicKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - passwordChecker := memoize.MemoizedCompareHashAndPassword() - - authenticator := Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: key, - }, - ModelHandler: MockModelHandler{}, - CompareHashAndPassword: passwordChecker, - } - t.Run("testing getting token with valid inputs", func(t *testing.T) { - tokenString, err := authenticator.Token(context.Background(), acl.ClientCredentials, "snappbox", "KJIikjIKbIYVGj)YihYUGIB&") - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - return pk, nil - }) - assert.NoError(t, err) - claims := token.Claims.(jwt.MapClaims) - assert.Equal(t, "snappbox", claims["sub"].(string)) - assert.Equal(t, "100", claims["iss"].(string)) - - }) - t.Run("testing getting token with valid inputs", func(t *testing.T) { - tokenString, err := authenticator.Token(context.Background(), acl.ClientCredentials, "snappbox", "invalid secret") - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - return pk, nil - }) - assert.Error(t, err) - assert.Nil(t, token) - }) -} -func TestAuthenticator_HeraldToken(t *testing.T) { - key, err := getPrivateKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - pk, err := getPublicKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - - authenticator := Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: key, - }, - } - - t.Run("testing issuing valid herald token", func(t *testing.T) { - allowedTopics := []acl.Topic{ - { - Type: topics.BoxEvent, - }, - } - - allowedEndpoints := []acl.Endpoint{ - { - Name: "event", - }, - } - - tokenString, err := authenticator.HeraldToken( - "snappbox", allowedEndpoints, allowedTopics, time.Hour*24) - assert.NoError(t, err) - token, err := jwt.ParseWithClaims(tokenString, &acl.Claims{}, func(token *jwt.Token) (interface{}, error) { - return pk, nil - }) - assert.NoError(t, err) - actual := token.Claims.(*acl.Claims) - - expected := acl.Claims{ - StandardClaims: jwt.StandardClaims{ - Issuer: "100", - Subject: "snappbox", - }, - Topics: []acl.Topic{ - { - Type: topics.BoxEvent, - }, - }, - Endpoints: []acl.Endpoint{ - { - Name: "event", - }, - }, - } - assert.Equal(t, expected.StandardClaims.Issuer, actual.StandardClaims.Issuer) - assert.Equal(t, expected.StandardClaims.Subject, actual.StandardClaims.Subject) - assert.EqualValues(t, expected.Endpoints, actual.Endpoints) - assert.EqualValues(t, expected.Topics, actual.Topics) - }) -} - -func TestAuthenticator_SuperuserToken(t *testing.T) { - key, err := getPrivateKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - pk, err := getPublicKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - authenticator := Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: key, - }, - } - - tokenString, err := authenticator.SuperuserToken("herald", time.Hour*24) - assert.NoError(t, err) - - token, err := jwt.ParseWithClaims(tokenString, &acl.SuperuserClaims{}, func(token *jwt.Token) (interface{}, error) { - return pk, nil - }) - assert.NoError(t, err) - - actual := token.Claims.(*acl.SuperuserClaims) - expected := acl.SuperuserClaims{ - StandardClaims: jwt.StandardClaims{ - Issuer: "100", - Subject: "herald", - }, - IsSuperuser: true, - } - - assert.Equal(t, expected.StandardClaims.Issuer, actual.StandardClaims.Issuer) - assert.Equal(t, expected.StandardClaims.Subject, actual.StandardClaims.Subject) - assert.Equal(t, expected.IsSuperuser, actual.IsSuperuser) -} - -func TestAuthenticator_Acl(t *testing.T) { - pkey0, err := getPublicKey(user.Driver) - if err != nil { - t.Fatal(err) - } - pkey1, err := getPublicKey(user.Passenger) - if err != nil { - t.Fatal(err) - } - pkey100, err := getPublicKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - key100, err := getPrivateKey(user.ThirdParty) - if err != nil { - t.Fatal(err) - } - passengerToken, err := getSampleToken(user.Passenger, false) - if err != nil { - t.Fatal(err) - } - driverToken, err := getSampleToken(user.Driver, false) - if err != nil { - t.Fatal(err) - } - thirdPartyToken, err := getSampleToken(user.ThirdParty, false) - if err != nil { - t.Fatal(err) - } - - hid := &snappids.HashIDSManager{ - Salts: map[snappids.Audience]string{ - snappids.PassengerAudience: "secret", - snappids.DriverAudience: "secret", - snappids.ThirdPartyAudience: "secret", - }, - Lengths: map[snappids.Audience]int{ - snappids.PassengerAudience: 15, - snappids.DriverAudience: 15, - snappids.ThirdPartyAudience: 15, - }, - } - - passwordChecker := memoize.MemoizedCompareHashAndPassword() - authenticator := Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: key100, - }, - PublicKeys: map[user.Issuer]*rsa.PublicKey{ - user.Driver: pkey0, - user.Passenger: pkey1, - user.ThirdParty: pkey100, - }, - AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}, - ModelHandler: MockModelHandler{}, - EMQTopicManager: snappids.NewEMQManager(hid), - HashIDSManager: hid, - CompareHashAndPassword: passwordChecker, - } - t.Run("testing acl with invalid access type", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.PubSub, passengerToken, "test") - assert.Error(t, err) - assert.False(t, ok) - assert.Equal(t, ErrInvalidAccessType.Error(), err.Error()) - }) - t.Run("testing acl with invalid token", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Pub, invalidToken, validDriverCabEventTopic) - assert.False(t, ok) - assert.Error(t, err) - assert.Equal(t, "token is invalid illegal base64 data at input byte 37", err.Error()) - }) - t.Run("testing acl with valid inputs", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerCabEventTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - t.Run("testing acl with invalid topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerCabEventTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - t.Run("testing acl with invalid access type", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Pub, passengerToken, validPassengerCabEventTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing acl with third party token", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, thirdPartyToken, validDriverLocationTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing driver publish on its location topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Pub, driverToken, validDriverLocationTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing driver publish on invalid location topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Pub, driverToken, invalidDriverLocationTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing driver subscribe on invalid cab event topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverCabEventTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing passenger subscribe on valid superapp event topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerSuperappEventTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing passenger subscribe on invalid superapp event topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerSuperappEventTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing driver subscribe on valid superapp event topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, validDriverSuperappEventTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing driver subscribe on invalid superapp event topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverSuperappEventTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing driver subscribe on valid shared location topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, validDriverSharedTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing passenger subscribe on valid shared location topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerSharedTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing driver subscribe on invalid shared location topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverSharedTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing passenger subscribe on invalid shared location topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerSharedTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing driver subscribe on valid chat topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, validDriverChatTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing passenger subscribe on valid chat topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerChatTopic) - assert.NoError(t, err) - assert.True(t, ok) - }) - - t.Run("testing driver subscribe on invalid chat topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverChatTopic) - assert.Error(t, err) - assert.False(t, ok) - }) - - t.Run("testing passenger subscribe on invalid chat topic", func(t *testing.T) { - ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerChatTopic) - assert.Error(t, err) - assert.False(t, ok) - }) -} - -func TestAuthenticator_ValidateTopicBySender(t *testing.T) { - hid := &snappids.HashIDSManager{ - Salts: map[snappids.Audience]string{ - snappids.DriverAudience: "secret", - }, - Lengths: map[snappids.Audience]int{ - snappids.DriverAudience: 15, - }, - } - - passwordChecker := memoize.MemoizedCompareHashAndPassword() - authenticator := Authenticator{ - AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}, - ModelHandler: MockModelHandler{}, - EMQTopicManager: snappids.NewEMQManager(hid), - HashIDSManager: hid, - CompareHashAndPassword: passwordChecker, - } - - t.Run("testing valid driver cab event", func(t *testing.T) { - ok := authenticator.ValidateTopicBySender(validDriverCabEventTopic, snappids.DriverAudience, 123) - assert.True(t, ok) - }) - -} - -func TestAuthenticator_validateAccessType(t *testing.T) { - type fields struct { - AllowedAccessTypes []acl.AccessType - } - type args struct { - accessType acl.AccessType - } - tests := []struct { - name string - fields fields - args args - want bool - }{ - { - name: "#1 testing with no allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{}}, - args: args{accessType: acl.Sub}, - want: false, - }, - { - name: "#2 testing with no allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{}}, - args: args{accessType: acl.Pub}, - want: false, - }, - { - name: "#3 testing with no allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{}}, - args: args{accessType: acl.PubSub}, - want: false, - }, - { - name: "#4 testing with one allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}}, - args: args{accessType: acl.Pub}, - want: true, - }, - { - name: "#5 testing with one allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}}, - args: args{accessType: acl.Sub}, - want: false, - }, - { - name: "#6 testing with two allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}}, - args: args{accessType: acl.Sub}, - want: true, - }, - { - name: "#7 testing with two allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}}, - args: args{accessType: acl.Pub}, - want: true, - }, - { - name: "#8 testing with two allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}}, - args: args{accessType: acl.PubSub}, - want: false, - }, - { - name: "#9 testing with three allowed access type", - fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub}}, - args: args{accessType: acl.PubSub}, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - a := Authenticator{ - AllowedAccessTypes: tt.fields.AllowedAccessTypes, - } - if got := a.validateAccessType(tt.args.accessType); got != tt.want { - t.Errorf("validateAccessType() = %v, want %v", got, tt.want) - } - }) - } -} - -type MockModelHandler struct{} - -func (rmh MockModelHandler) Save(ctx context.Context, model db.Model) error { - return nil -} - -func (rmh MockModelHandler) Delete(ctx context.Context, modelName, pk string) error { - return nil -} - -func (rmh MockModelHandler) Get(ctx context.Context, modelName, pk string, v db.Model) error { - switch pk { - case "passenger": - *v.(*user.User) = user.User{ - MetaData: db.MetaData{}, - Username: string(user.Passenger), - Type: user.EMQUser, - Rules: []user.Rule{ - { - UUID: uuid.New(), - Topic: topics.CabEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.SuperappEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.PassengerLocation, - AccessType: acl.Pub, - }, - { - UUID: uuid.New(), - Topic: topics.SharedLocation, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.Chat, - AccessType: acl.Sub, - }, - }, - } - case "driver": - *v.(*user.User) = user.User{ - MetaData: db.MetaData{}, - Username: string(user.Driver), - Type: user.EMQUser, - Rules: []user.Rule{ - { - UUID: uuid.Nil, - Endpoint: "", - Topic: topics.DriverLocation, - AccessType: acl.Pub, - }, - { - UUID: uuid.Nil, - Endpoint: "", - Topic: topics.CabEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.SuperappEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.PassengerLocation, - AccessType: acl.Pub, - }, - { - UUID: uuid.New(), - Topic: topics.SharedLocation, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.Chat, - AccessType: acl.Sub, - }, - }, - } - case "snappbox": - *v.(*user.User) = user.User{ - MetaData: db.MetaData{}, - Username: "snapp-box", - Password: getSamplePassword(), - Type: user.HeraldUser, - Secret: "KJIikjIKbIYVGj)YihYUGIB&", - TokenExpirationDuration: time.Hour * 72, - Rules: []user.Rule{ - { - UUID: uuid.New(), - Topic: topics.BoxEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - }, - } - case "colony-subscriber": - *v.(*user.User) = user.User{ - MetaData: db.MetaData{}, - Username: "colony-subscriber", - Password: "password", - Type: user.EMQUser, - Secret: "secret", - TokenExpirationDuration: 0, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Topic: topics.DriverLocation, - AccessType: acl.Sub, - }, - }, - } - } - return nil -} + validDriverCallEntryTopic = "shared/snapp/driver/DXKgaNQa7N5Y7bo/call/send" + validPassengerCallEntryTopic = "shared/snapp/passenger/DXKgaNQa7N5Y7bo/call/send" + validDriverNodeCallEntryTopic = "snapp/driver/DXKgaNQa7N5Y7bo/call/heliograph-0/send" + validPassengerNodeCallEntryTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/call/heliograph-0/send" + invalidDriverCallEntryTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/call/send" + invalidPassengerCallEntryTopic = "snapp/passenger/0596923be632d673560af9adadd2f78a/call/send" + validDriverCallOutgoingTopic = "snapp/driver/DXKgaNQa7N5Y7bo/call/receive" + validPassengerCallOutgoingTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/call/receive" + invalidDriverCallOutgoingTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/call/receive" + invalidPassengerCallOutgoingTopic = "snapp/passenger/0596923be632d673560af9adadd2f78a/call/receive" +) -func (rmh MockModelHandler) Update(ctx context.Context, model db.Model) error { - return nil -} +var ( + ErrPrivateKeyNotFound = errors.New("invalid user, private key not found") + ErrPublicKeyNotFound = errors.New("invalid user, public key not found") +) -func getPublicKey(u user.Issuer) (*rsa.PublicKey, error) { +func getPublicKey(u string) (*rsa.PublicKey, error) { var fileName string + switch u { - case user.Passenger: - fileName = "../../test/1.pem" - case user.Driver: - fileName = "../../test/0.pem" - case user.ThirdParty: - fileName = "../../test/100.pem" + case "1": + fileName = "../../test/snapp-1.pem" + case "0": + fileName = "../../test/snapp-0.pem" + case "admin": + fileName = "../../test/snapp-admin.pem" default: - return nil, fmt.Errorf("invalid user, public key not found") + return nil, ErrPublicKeyNotFound } - pem, err := ioutil.ReadFile(fileName) + + pem, err := os.ReadFile(fileName) if err != nil { - return nil, err + return nil, fmt.Errorf("reading public key failed %w", err) } + publicKey, err := jwt.ParseRSAPublicKeyFromPEM(pem) if err != nil { - return nil, err + return nil, fmt.Errorf("paring public key failed %w", err) } + return publicKey, nil } -func getPrivateKey(u user.Issuer) (*rsa.PrivateKey, error) { +func getPrivateKey(u string) (*rsa.PrivateKey, error) { var fileName string + switch u { - case user.Driver: - fileName = "../../test/0.private.pem" - case user.Passenger: - fileName = "../../test/1.private.pem" - case user.ThirdParty: - fileName = "../../test/100.private.pem" + case "0": + fileName = "../../test/snapp-0.private.pem" + case "1": + fileName = "../../test/snapp-1.private.pem" + case "admin": + fileName = "../../test/snapp-admin.private.pem" default: - return nil, fmt.Errorf("invalid user, private key not found") + return nil, ErrPrivateKeyNotFound } - pem, err := ioutil.ReadFile(fileName) + + pem, err := os.ReadFile(fileName) if err != nil { - return nil, err + return nil, fmt.Errorf("reading private key failed %w", err) } + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(pem) if err != nil { - return nil, err + return nil, fmt.Errorf("paring private key failed %w", err) } + return privateKey, nil } -func getSampleToken(issuer user.Issuer, isSuperuser bool) (string, error) { - key, err := getPrivateKey(issuer) - if err != nil { - panic(err) - } - - exp := time.Now().Add(time.Hour * 24 * 365 * 10).Unix() +func getSampleToken(issuer string, key *rsa.PrivateKey) (string, error) { + exp := time.Now().Add(time.Hour * 24 * 365 * 10) sub := "DXKgaNQa7N5Y7bo" - if issuer == user.ThirdParty { - sub = "colony-subscriber" - } - var claims jwt.Claims - if isSuperuser { - claims = jwt.MapClaims{ - "exp": exp, - "iss": string(issuer), - "sub": sub, - "is_superuser": true, - } - } else { - claims = jwt.StandardClaims{ - ExpiresAt: exp, - Issuer: string(issuer), - Subject: sub, - } + // nolint: exhaustruct + claims := jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(exp), + Issuer: issuer, + Subject: sub, } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + tokenString, err := token.SignedString(key) if err != nil { - panic(err) + return "", fmt.Errorf("cannot generate a signed string %w", err) } - return tokenString, nil -} -func getSamplePassword() string { - hash, _ := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost) - return string(hash) + return tokenString, nil } diff --git a/internal/authenticator/auto_authenticator.go b/internal/authenticator/auto_authenticator.go new file mode 100644 index 00000000..41a668c7 --- /dev/null +++ b/internal/authenticator/auto_authenticator.go @@ -0,0 +1,184 @@ +package authenticator + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/pkg/acl" + "github.com/snapp-incubator/soteria/pkg/validator" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" +) + +// AutoAuthenticator is responsible for Acl/Auth/Token of userIDs. +type AutoAuthenticator struct { + AllowedAccessTypes []acl.AccessType + TopicManager *topics.Manager + Company string + JWTConfig config.JWT + Validator validator.Client + Parser *jwt.Parser + Tracer trace.Tracer + Logger *zap.Logger + blackList autoBlackListChecker +} + +// Auth check user authentication by checking the user's token +// isSuperuser is a flag that authenticator set it true when credentials is related to a superuser. +func (a AutoAuthenticator) Auth(tokenString string) error { + ctx, span := a.Tracer.Start(context.Background(), "auto-authenticator.auth") + span.End() + + headers := http.Header{ + validator.ServiceNameHeader: []string{"soteria"}, + "user-agent": []string{}, + "X-APP-Version-Code": []string{""}, + "X-APP-Version": []string{""}, + "X-APP-Name": []string{"soteria"}, + "locale": []string{"en-US"}, + } + + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(headers)) + + payload, err := a.Validator.Validate(ctx, headers, "bearer "+tokenString) + if err != nil { + return fmt.Errorf("token is invalid: %w", err) + } + + if a.blackList.isBlackListByUserID(payload.UserID, payload.Iss) { + a.Logger.Warn("blacklisted user is requesting!", + zap.Int("iat", payload.IAT), + zap.String("aud", payload.Aud), + zap.Int("iss", payload.Iss), + zap.String("sub", payload.Sub), + zap.Int("user_id", payload.UserID), + zap.String("email", payload.Email), + zap.Int("exp", payload.Exp), + zap.String("locale", payload.Locale), + zap.String("sid", payload.Sid), + ) + } + + return nil +} + +// ACL check a user access to a topic. +// nolint: funlen, cyclop, dupl +func (a AutoAuthenticator) ACL( + accessType acl.AccessType, + tokenString string, + topic string, +) (bool, error) { + if !a.ValidateAccessType(accessType) { + return false, ErrInvalidAccessType + } + + var claims jwt.MapClaims + + if _, _, err := a.Parser.ParseUnverified(tokenString, &claims); err != nil { + return false, ErrInvalidClaims + } + + if claims[a.JWTConfig.IssName] == nil { + return false, ErrIssNotFound + } + + issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName]) + + if claims[a.JWTConfig.SubName] == nil { + return false, ErrSubNotFound + } + + issuerInt, _ := strconv.Atoi(issuer) + sub, _ := claims[a.JWTConfig.SubName].(string) + + if a.blackList.isBlackListByUserHashedID(sub, issuerInt) { + a.Logger.Warn("blacklisted user is requesting!", zap.Any("claims", claims)) + } + + topicTemplate := a.TopicManager.ParseTopic(topic, issuer, sub, map[string]any(claims)) + if topicTemplate == nil { + return false, InvalidTopicError{Topic: topic} + } + + if !topicTemplate.HasAccess(issuer, accessType) { + return false, TopicNotAllowedError{ + issuer, + sub, + accessType, + topic, + topicTemplate.Type, + } + } + + return true, nil +} + +func (a AutoAuthenticator) ValidateAccessType(accessType acl.AccessType) bool { + for _, allowedAccessType := range a.AllowedAccessTypes { + if allowedAccessType == accessType { + return true + } + } + + return false +} + +func (a AutoAuthenticator) GetCompany() string { + return a.Company +} + +func (a AutoAuthenticator) IsSuperuser() bool { + return false +} + +type autoBlackListChecker struct { + userIDs map[int]struct{} + userHashedIDs map[string]struct{} + iss int +} + +func newAutoBlackListChecker(cfg config.BlackListUserLogging) autoBlackListChecker { + users := make(map[int]struct{}) + for _, userID := range cfg.UserIDs { + users[userID] = struct{}{} + } + + userHashedIDs := make(map[string]struct{}) + for _, u := range cfg.UserHashedIDs { + userHashedIDs[u] = struct{}{} + } + + return autoBlackListChecker{ + userIDs: users, + userHashedIDs: userHashedIDs, + iss: cfg.Iss, + } +} + +func (a autoBlackListChecker) isBlackListByUserID(userID, iss int) bool { + if iss != a.iss { + return false + } + + _, ok := a.userIDs[userID] + + return ok +} + +func (a autoBlackListChecker) isBlackListByUserHashedID(userHashedID string, iss int) bool { + if iss != a.iss { + return false + } + + _, ok := a.userHashedIDs[userHashedID] + + return ok +} diff --git a/internal/authenticator/auto_authenticator_test.go b/internal/authenticator/auto_authenticator_test.go new file mode 100644 index 00000000..71c78801 --- /dev/null +++ b/internal/authenticator/auto_authenticator_test.go @@ -0,0 +1,150 @@ +package authenticator_test + +import ( + "crypto/rsa" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/pkg/acl" + "github.com/snapp-incubator/soteria/pkg/validator" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.opentelemetry.io/otel/trace/noop" + "go.uber.org/zap" +) + +type AutoAuthenticatorTestSuite struct { + suite.Suite + + Token string + PublicKey *rsa.PublicKey + + Server *httptest.Server + + Authenticator authenticator.Authenticator +} + +func TestAutoAuthenticator_suite(t *testing.T) { + t.Parallel() + + suite.Run(t, new(AutoAuthenticatorTestSuite)) +} + +// nolint: funlen +func (suite *AutoAuthenticatorTestSuite) SetupSuite() { + cfg := config.SnappVendor() + + require := suite.Require() + + pkey0, err := getPublicKey("0") + require.NoError(err) + + suite.PublicKey = pkey0 + + key0, err := getPrivateKey("0") + require.NoError(err) + + token, err := getSampleToken("0", key0) + require.NoError(err) + + suite.Token = token + + hid, err := topics.NewHashIDManager(cfg.HashIDMap) + require.NoError(err) + + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + authHeader := req.Header.Get("Authorization") + tokenString := strings.TrimPrefix(authHeader, "bearer ") + + _, err := jwt.Parse(tokenString, func( + _ *jwt.Token, + ) (interface{}, error) { + return pkey0, nil + }) + if err != nil { + res.WriteHeader(http.StatusUnauthorized) + + return + } + + userData, err := json.Marshal(map[string]any{}) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + + return + } + + res.Header().Add("X-User-Data", string(userData)) + + res.WriteHeader(http.StatusOK) + })) + suite.Server = testServer + + suite.Authenticator = authenticator.AutoAuthenticator{ + Validator: validator.New(testServer.URL, time.Second), + AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub}, + Tracer: noop.NewTracerProvider().Tracer(""), + Company: "snapp", + Parser: jwt.NewParser(), + TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()), + JWTConfig: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "rsa256", + }, + Logger: zap.NewNop(), + } +} + +func (suite *AutoAuthenticatorTestSuite) TestAuth() { + require := suite.Require() + + suite.Run("testing valid token auth", func() { + require.NoError(suite.Authenticator.Auth(suite.Token)) + }) + + suite.Run("testing invalid token auth", func() { + require.Error(suite.Authenticator.Auth(invalidToken)) + }) +} + +func (suite *AutoAuthenticatorTestSuite) TearDownSuite() { + suite.Server.Close() +} + +func TestAutoAuthenticator_ValidateTopicBySender(t *testing.T) { + t.Parallel() + + cfg := config.SnappVendor() + + hid, err := topics.NewHashIDManager(cfg.HashIDMap) + require.NoError(t, err) + + // nolint: exhaustruct + authenticator := authenticator.AutoAuthenticator{ + AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}, + Company: "snapp", + TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()), + Logger: zap.NewNop(), + } + + t.Run("testing valid driver cab event", func(t *testing.T) { + t.Parallel() + + topicTemplate := authenticator.TopicManager.ParseTopic( + validDriverCabEventTopic, + topics.DriverIss, + "DXKgaNQa7N5Y7bo", + nil, + ) + require.NotNil(t, topicTemplate) + }) +} diff --git a/internal/authenticator/builder.go b/internal/authenticator/builder.go new file mode 100644 index 00000000..dc1c2f8c --- /dev/null +++ b/internal/authenticator/builder.go @@ -0,0 +1,201 @@ +package authenticator + +import ( + "errors" + "fmt" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/pkg/acl" + "github.com/snapp-incubator/soteria/pkg/validator" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" +) + +var ( + ErrAdminAuthenticatorSystemKey = errors.New("admin authenticator supports only one key named system") + ErrNoAuthenticator = errors.New("at least one vendor should be enable to have soteria") + ErrNoDefaultCaseIssEntity = errors.New("default case for iss-entity map is required") + ErrNoDefaultCaseIssPeer = errors.New("default case for iss-peer map is required") + ErrInvalidAuthenticator = errors.New("there is no authenticator to support your request") +) + +type Builder struct { + Vendors []config.Vendor + Logger *zap.Logger + ValidatorConfig config.Validator + Tracer trace.Tracer + BlackListUserLoggingConfig config.BlackListUserLogging +} + +func (b Builder) Authenticators() (map[string]Authenticator, error) { + all := make(map[string]Authenticator) + + for _, vendor := range b.Vendors { + var ( + auth Authenticator + err error + ) + + switch vendor.Type { + case "auto", "validator", "validator-based", "using-validator": + auth, err = b.autoAuthenticator(vendor, b.BlackListUserLoggingConfig) + if err != nil { + return nil, fmt.Errorf("cannot build auto authenticator %w", err) + } + case "admin", "internal": + auth, err = b.adminAuthenticator(vendor) + if err != nil { + return nil, fmt.Errorf("cannot build admin authenticator %w", err) + } + case "manual": + auth, err = b.manualAuthenticator(vendor) + if err != nil { + return nil, fmt.Errorf("cannot build manual authenticator %w", err) + } + default: + return nil, ErrInvalidAuthenticator + } + + all[vendor.Company] = auth + } + + if len(all) == 0 { + return nil, ErrNoAuthenticator + } + + return all, nil +} + +func (b Builder) adminAuthenticator(vendor config.Vendor) (*AdminAuthenticator, error) { + if _, ok := vendor.Keys["system"]; !ok || len(vendor.Keys) != 1 { + return nil, ErrAdminAuthenticatorSystemKey + } + + keys, err := b.GenerateKeys(vendor.Jwt.SigningMethod, vendor.Keys) + if err != nil { + return nil, fmt.Errorf("loading keys failed %w", err) + } + + return &AdminAuthenticator{ + Key: keys["system"], + Company: vendor.Company, + JwtConfig: vendor.Jwt, + Parser: jwt.NewParser(), + }, nil +} + +func (b Builder) manualAuthenticator(vendor config.Vendor) (*ManualAuthenticator, error) { + if err := b.ValidateMappers(vendor.IssEntityMap, vendor.IssPeerMap); err != nil { + return nil, fmt.Errorf("failed to validate mappers %w", err) + } + + allowedAccessTypes, err := b.GetAllowedAccessTypes(vendor.AllowedAccessTypes) + if err != nil { + return nil, fmt.Errorf("cannot parse allowed access types %w", err) + } + + hid, err := topics.NewHashIDManager(vendor.HashIDMap) + if err != nil { + return nil, fmt.Errorf("cannot create hash-id manager %w", err) + } + + keys, err := b.GenerateKeys(vendor.Jwt.SigningMethod, vendor.Keys) + if err != nil { + return nil, fmt.Errorf("loading keys failed %w", err) + } + + return &ManualAuthenticator{ + Keys: keys, + AllowedAccessTypes: allowedAccessTypes, + Company: vendor.Company, + TopicManager: topics.NewTopicManager( + vendor.Topics, + hid, + vendor.Company, + vendor.IssEntityMap, + vendor.IssPeerMap, + b.Logger.Named("topic-manager"), + ), + JWTConfig: vendor.Jwt, + Parser: jwt.NewParser(jwt.WithValidMethods([]string{vendor.Jwt.SigningMethod})), + }, nil +} + +func (b Builder) autoAuthenticator(vendorCfg config.Vendor, blackListUserLoggingCfg config.BlackListUserLogging) ( + *AutoAuthenticator, error, +) { + allowedAccessTypes, err := b.GetAllowedAccessTypes(vendorCfg.AllowedAccessTypes) + if err != nil { + return nil, fmt.Errorf("cannot parse allowed access types %w", err) + } + + hid, err := topics.NewHashIDManager(vendorCfg.HashIDMap) + if err != nil { + return nil, fmt.Errorf("cannot create hash-id manager %w", err) + } + + client := validator.New(b.ValidatorConfig.URL, b.ValidatorConfig.Timeout) + + return &AutoAuthenticator{ + AllowedAccessTypes: allowedAccessTypes, + Company: vendorCfg.Company, + TopicManager: topics.NewTopicManager( + vendorCfg.Topics, + hid, + vendorCfg.Company, + vendorCfg.IssEntityMap, + vendorCfg.IssPeerMap, + b.Logger.Named("topic-manager"), + ), + Tracer: b.Tracer, + JWTConfig: vendorCfg.Jwt, + Validator: client, + Parser: jwt.NewParser(), + Logger: b.Logger.Named("auto-authenticator"), + blackList: newAutoBlackListChecker(blackListUserLoggingCfg), + }, nil +} + +// GetAllowedAccessTypes will return all allowed access types in Soteria. +func (b Builder) GetAllowedAccessTypes(accessTypes []string) ([]acl.AccessType, error) { + allowedAccessTypes := make([]acl.AccessType, 0, len(accessTypes)) + + for _, a := range accessTypes { + at, err := toUserAccessType(a) + if err != nil { + return nil, fmt.Errorf("could not convert %s: %w", at, err) + } + + allowedAccessTypes = append(allowedAccessTypes, at) + } + + return allowedAccessTypes, nil +} + +// toUserAccessType will convert string access type to it's own type. +func toUserAccessType(access string) (acl.AccessType, error) { + switch access { + case "pub", "publish": + return acl.Pub, nil + case "sub", "subscribe": + return acl.Sub, nil + case "pubsub", "subpub": + return acl.PubSub, nil + } + + return "", ErrInvalidAccessType +} + +func (b Builder) ValidateMappers(issEntityMap, issPeerMap map[string]string) error { + if _, ok := issEntityMap[topics.Default]; !ok { + return ErrNoDefaultCaseIssEntity + } + + if _, ok := issPeerMap[topics.Default]; !ok { + return ErrNoDefaultCaseIssPeer + } + + return nil +} diff --git a/internal/authenticator/builder_internal_test.go b/internal/authenticator/builder_internal_test.go new file mode 100644 index 00000000..35f7f66a --- /dev/null +++ b/internal/authenticator/builder_internal_test.go @@ -0,0 +1,75 @@ +package authenticator + +import ( + "testing" + + "github.com/snapp-incubator/soteria/pkg/acl" + "github.com/stretchr/testify/require" +) + +// nolint: funlen +func TestToUserAccessType(t *testing.T) { + t.Parallel() + + require := require.New(t) + + cases := []struct { + name string + input string + expected acl.AccessType + expectedErr error + }{ + { + name: "success", + input: "pub", + expected: acl.Pub, + expectedErr: nil, + }, + { + name: "success", + input: "publish", + expected: acl.Pub, + expectedErr: nil, + }, + { + name: "success", + input: "sub", + expected: acl.Sub, + expectedErr: nil, + }, + { + name: "success", + input: "subscribe", + expected: acl.Sub, + expectedErr: nil, + }, + { + name: "success", + input: "pubsub", + expected: acl.PubSub, + expectedErr: nil, + }, + { + name: "success", + input: "subpub", + expected: acl.PubSub, + expectedErr: nil, + }, + { + name: "failed", + input: "-", + expected: "", + expectedErr: ErrInvalidAccessType, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + v, err := toUserAccessType(c.input) + require.ErrorIs(c.expectedErr, err) + require.Equal(c.expected, v) + }) + } +} diff --git a/internal/authenticator/builder_test.go b/internal/authenticator/builder_test.go new file mode 100644 index 00000000..142fea1c --- /dev/null +++ b/internal/authenticator/builder_test.go @@ -0,0 +1,542 @@ +package authenticator_test + +import ( + "testing" + + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" + "go.uber.org/zap" +) + +func TestBuilderWithoutAuthenticator(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Vendors: []config.Vendor{}, + Logger: zap.NewNop(), + Tracer: noop.NewTracerProvider().Tracer(""), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrNoAuthenticator) +} + +func TestBuilderInvalidAuthenticator(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "internal", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "HS512", + }, + Type: "invalid", + AllowedAccessTypes: nil, + Topics: nil, + HashIDMap: nil, + IssEntityMap: nil, + IssPeerMap: nil, + Keys: map[string]string{ + "system": "c2VjcmV0", + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrInvalidAuthenticator) +} + +func TestBuilderInternalAuthenticator(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "internal", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "HS512", + }, + Type: "internal", + AllowedAccessTypes: nil, + Topics: nil, + HashIDMap: nil, + IssEntityMap: nil, + IssPeerMap: nil, + Keys: map[string]string{ + "system": "c2VjcmV0", + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + vendors, err := b.Authenticators() + require.NoError(err) + require.Len(vendors, 1) + require.Contains(vendors, "internal") +} + +func TestBuilderInternalAuthenticatorWithInvalidKey(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "admin", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "HS512", + }, + Type: "internal", + AllowedAccessTypes: nil, + Topics: nil, + HashIDMap: nil, + IssEntityMap: nil, + IssPeerMap: nil, + Keys: map[string]string{ + "not-system": "c2VjcmV0", + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrAdminAuthenticatorSystemKey) +} + +func TestBuilderManualAuthenticatorWithoutKey(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "snapp", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "HS512", + }, + Type: "manual", + AllowedAccessTypes: []string{"pub", "sub"}, + Topics: nil, + HashIDMap: map[string]topics.HashData{ + "0": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + "1": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + }, + IssEntityMap: map[string]string{ + "0": "driver", + "1": "passenger", + "default": "", + }, + IssPeerMap: map[string]string{ + "0": "passenger", + "1": "driver", + "default": "", + }, + Keys: nil, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrNoKeys) +} + +// nolint: funlen +func TestBuilderManualAuthenticator(t *testing.T) { + t.Parallel() + + require := require.New(t) + + // nolint: dupl + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "snapp", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "RSA512", + }, + Type: "manual", + AllowedAccessTypes: []string{"pub", "sub"}, + Topics: nil, + HashIDMap: map[string]topics.HashData{ + "0": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + "1": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + }, + IssEntityMap: map[string]string{ + "0": "driver", + "1": "passenger", + "default": "", + }, + IssPeerMap: map[string]string{ + "0": "passenger", + "1": "driver", + "default": "", + }, + Keys: map[string]string{ + "0": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB +-----END PUBLIC KEY-----`, + + "1": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB +-----END PUBLIC KEY-----`, + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + vendors, err := b.Authenticators() + require.NoError(err) + require.Len(vendors, 1) + require.Contains(vendors, "snapp") +} + +// nolint: funlen +func TestBuilderManualAuthenticatorInvalidMapping_1(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "snapp", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "RSA512", + }, + Type: "manual", + AllowedAccessTypes: []string{"pub", "sub"}, + Topics: nil, + HashIDMap: map[string]topics.HashData{ + "0": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + "1": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + }, + IssEntityMap: map[string]string{ + "0": "driver", + "1": "passenger", + "default": "", + }, + IssPeerMap: nil, + Keys: map[string]string{ + "0": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB +-----END PUBLIC KEY-----`, + + "1": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB +-----END PUBLIC KEY-----`, + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrNoDefaultCaseIssPeer) +} + +// nolint: funlen +func TestBuilderManualAuthenticatorInvalidMapping_2(t *testing.T) { + t.Parallel() + + require := require.New(t) + + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "snapp", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "RSA512", + }, + Type: "manual", + AllowedAccessTypes: []string{"pub", "sub"}, + Topics: nil, + HashIDMap: map[string]topics.HashData{ + "0": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + "1": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + }, + IssEntityMap: map[string]string{ + "0": "driver", + "1": "passenger", + }, + IssPeerMap: map[string]string{ + "0": "passenger", + "1": "driver", + "default": "", + }, + Keys: map[string]string{ + "0": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB +-----END PUBLIC KEY-----`, + + "1": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB +-----END PUBLIC KEY-----`, + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrNoDefaultCaseIssEntity) +} + +// nolint: funlen +func TestBuilderManualAuthenticatorInvalidAccess(t *testing.T) { + t.Parallel() + + require := require.New(t) + + // nolint: dupl + b := authenticator.Builder{ + Tracer: noop.NewTracerProvider().Tracer(""), + Vendors: []config.Vendor{ + { + Company: "snapp", + Jwt: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "RSA512", + }, + Type: "manual", + AllowedAccessTypes: []string{"pub", "superuser"}, + Topics: nil, + HashIDMap: map[string]topics.HashData{ + "0": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + "1": { + Alphabet: "", + Length: 15, + Salt: "secret", + }, + }, + IssEntityMap: map[string]string{ + "0": "driver", + "1": "passenger", + "default": "", + }, + IssPeerMap: map[string]string{ + "0": "passenger", + "1": "driver", + "default": "", + }, + Keys: map[string]string{ + "0": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB +-----END PUBLIC KEY-----`, + + "1": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB +-----END PUBLIC KEY-----`, + }, + }, + }, + Logger: zap.NewNop(), + ValidatorConfig: config.Validator{ + URL: "", + Timeout: 0, + }, + BlackListUserLoggingConfig: config.BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } + + _, err := b.Authenticators() + require.ErrorIs(err, authenticator.ErrInvalidAccessType) +} diff --git a/internal/authenticator/errors.go b/internal/authenticator/errors.go new file mode 100644 index 00000000..b5118266 --- /dev/null +++ b/internal/authenticator/errors.go @@ -0,0 +1,50 @@ +package authenticator + +import ( + "errors" + "fmt" + + "github.com/snapp-incubator/soteria/pkg/acl" +) + +var ( + ErrInvalidSigningMethod = errors.New("signing method does not match with authenticator signing method") + ErrIssNotFound = errors.New("could not found iss in token claims") + ErrSubNotFound = errors.New("could not found sub in token claims") + ErrInvalidClaims = errors.New("invalid claims") + ErrInvalidIP = errors.New("IP is not valid") + ErrInvalidAccessType = errors.New("requested access type is invalid") + ErrDecodeHashID = errors.New("could not decode hash id") + ErrInvalidSecret = errors.New("invalid secret") + ErrIncorrectPassword = errors.New("username or password is worng") +) + +type TopicNotAllowedError struct { + Issuer string + Sub string + AccessType acl.AccessType + Topic string + TopicType string +} + +func (err TopicNotAllowedError) Error() string { + return fmt.Sprintf("issuer %s with sub %s is not allowed to %s on topic %s (%s)", + err.Issuer, err.Sub, err.AccessType, err.Topic, err.TopicType, + ) +} + +type KeyNotFoundError struct { + Issuer string +} + +func (err KeyNotFoundError) Error() string { + return fmt.Sprintf("cannot find issuer %s key", err.Issuer) +} + +type InvalidTopicError struct { + Topic string +} + +func (err InvalidTopicError) Error() string { + return fmt.Sprintf("provided topic %s is not valid", err.Topic) +} diff --git a/internal/authenticator/key.go b/internal/authenticator/key.go new file mode 100644 index 00000000..b794f4d1 --- /dev/null +++ b/internal/authenticator/key.go @@ -0,0 +1,90 @@ +package authenticator + +import ( + "encoding/base64" + "errors" + "fmt" + "strings" + + "github.com/golang-jwt/jwt/v5" + "go.uber.org/zap" +) + +var ( + ErrInvalidKeyType = errors.New("cannot determine the key type") + ErrNoKeys = errors.New("at least one key required") +) + +func (b Builder) GenerateKeys(method string, keys map[string]string) (map[string]any, error) { + var ( + keyList map[string]any + err error + ) + + // https://jwt.io/ + switch { + case strings.HasPrefix(method, "RS"): + keyList, err = b.GenerateRSAKeys(keys) + case strings.HasPrefix(method, "HS"): + keyList, err = b.GenerateHMACKeys(keys) + case strings.HasPrefix(method, "ES"): + keyList, err = b.GenerateECDSAKeys(keys) + default: + return nil, ErrInvalidKeyType + } + + if err != nil { + return nil, fmt.Errorf("reading keys failed %w", err) + } + + if len(keyList) == 0 { + return nil, ErrNoKeys + } + + return keyList, nil +} + +func (b Builder) GenerateRSAKeys(raw map[string]string) (map[string]any, error) { + keys := make(map[string]any) + + for iss, publicKey := range raw { + bytes, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey)) + if err != nil { + b.Logger.Fatal("could not read public key", zap.String("issuer", iss), zap.Error(err)) + } + + keys[iss] = bytes + } + + return keys, nil +} + +func (b Builder) GenerateECDSAKeys(raw map[string]string) (map[string]any, error) { + keys := make(map[string]any) + + for iss, publicKey := range raw { + bytes, err := jwt.ParseECPublicKeyFromPEM([]byte(publicKey)) + if err != nil { + b.Logger.Fatal("could not read public key", zap.String("issuer", iss), zap.Error(err)) + } + + keys[iss] = bytes + } + + return keys, nil +} + +func (b Builder) GenerateHMACKeys(raw map[string]string) (map[string]any, error) { + keys := make(map[string]any) + + for iss, key := range raw { + bytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, fmt.Errorf("failed to generate hmac key from base64 %w", err) + } + + keys[iss] = bytes + } + + return keys, nil +} diff --git a/internal/authenticator/manual_authenticator.go b/internal/authenticator/manual_authenticator.go new file mode 100644 index 00000000..2babb003 --- /dev/null +++ b/internal/authenticator/manual_authenticator.go @@ -0,0 +1,142 @@ +package authenticator + +import ( + "fmt" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/pkg/acl" +) + +// ManualAuthenticator is responsible for Acl/Auth/Token of userIDs without calling +// any http client, etc. +type ManualAuthenticator struct { + Keys map[string]any + AllowedAccessTypes []acl.AccessType + TopicManager *topics.Manager + Company string + JWTConfig config.JWT + Parser *jwt.Parser +} + +// Auth check user authentication by checking the user's token. +func (a ManualAuthenticator) Auth(tokenString string) error { + _, err := a.Parser.Parse(tokenString, func( + token *jwt.Token, + ) (interface{}, error) { + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return nil, ErrInvalidClaims + } + + if claims[a.JWTConfig.IssName] == nil { + return nil, ErrIssNotFound + } + + issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName]) + + key := a.Keys[issuer] + if key == nil { + return nil, KeyNotFoundError{Issuer: issuer} + } + + return key, nil + }) + if err != nil { + return fmt.Errorf("token is invalid: %w", err) + } + + return nil +} + +// ACL check a user access to a topic. +// nolint: funlen, cyclop, dupl +func (a ManualAuthenticator) ACL( + accessType acl.AccessType, + tokenString string, + topic string, +) (bool, error) { + if !a.ValidateAccessType(accessType) { + return false, ErrInvalidAccessType + } + + token, err := a.Parser.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return nil, ErrInvalidClaims + } + + if claims[a.JWTConfig.IssName] == nil { + return nil, ErrIssNotFound + } + + if claims[a.JWTConfig.SubName] == nil { + return nil, ErrSubNotFound + } + + issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName]) + + key := a.Keys[issuer] + if key == nil { + return nil, KeyNotFoundError{Issuer: issuer} + } + + return key, nil + }) + if err != nil { + return false, fmt.Errorf("token is invalid: %w", err) + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return false, ErrInvalidClaims + } + + if claims[a.JWTConfig.IssName] == nil { + return false, ErrIssNotFound + } + + issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName]) + + if claims[a.JWTConfig.SubName] == nil { + return false, ErrSubNotFound + } + + sub, _ := claims[a.JWTConfig.SubName].(string) + + topicTemplate := a.TopicManager.ParseTopic(topic, issuer, sub, map[string]any(claims)) + if topicTemplate == nil { + return false, InvalidTopicError{Topic: topic} + } + + if !topicTemplate.HasAccess(issuer, accessType) { + return false, TopicNotAllowedError{ + issuer, + sub, + accessType, + topic, + topicTemplate.Type, + } + } + + return true, nil +} + +func (a ManualAuthenticator) ValidateAccessType(accessType acl.AccessType) bool { + for _, allowedAccessType := range a.AllowedAccessTypes { + if allowedAccessType == accessType { + return true + } + } + + return false +} + +func (a ManualAuthenticator) GetCompany() string { + return a.Company +} + +func (a ManualAuthenticator) IsSuperuser() bool { + return false +} diff --git a/internal/authenticator/manual_authenticator_test.go b/internal/authenticator/manual_authenticator_test.go new file mode 100644 index 00000000..c3795963 --- /dev/null +++ b/internal/authenticator/manual_authenticator_test.go @@ -0,0 +1,479 @@ +package authenticator_test + +import ( + "crypto/rsa" + "testing" + + "github.com/golang-jwt/jwt/v5" + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/pkg/acl" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" +) + +type ManualAuthenticatorSnappTestSuite struct { + suite.Suite + + Tokens struct { + Passenger string + Driver string + } + + PublicKeys struct { + Passenger *rsa.PublicKey + Driver *rsa.PublicKey + } + + PrivateKeys struct { + Passenger *rsa.PrivateKey + Driver *rsa.PrivateKey + } + + Authenticator authenticator.Authenticator +} + +func TestManualAuthenticator_suite(t *testing.T) { + t.Parallel() + + suite.Run(t, new(ManualAuthenticatorSnappTestSuite)) +} + +func (suite *ManualAuthenticatorSnappTestSuite) SetupSuite() { + cfg := config.SnappVendor() + + require := suite.Require() + + pkey0, err := getPublicKey("0") + require.NoError(err) + + suite.PublicKeys.Driver = pkey0 + + pkey1, err := getPublicKey("1") + require.NoError(err) + + suite.PublicKeys.Passenger = pkey1 + + key0, err := getPrivateKey("0") + require.NoError(err) + + suite.PrivateKeys.Driver = key0 + + key1, err := getPrivateKey("1") + require.NoError(err) + + suite.PrivateKeys.Passenger = key1 + + driverToken, err := getSampleToken("0", key0) + require.NoError(err) + + suite.Tokens.Driver = driverToken + + passengerToken, err := getSampleToken("1", key1) + require.NoError(err) + + suite.Tokens.Passenger = passengerToken + + hid, err := topics.NewHashIDManager(cfg.HashIDMap) + require.NoError(err) + + suite.Authenticator = authenticator.ManualAuthenticator{ + Keys: map[string]any{ + topics.DriverIss: pkey0, + topics.PassengerIss: pkey1, + }, + AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub}, + Company: "snapp", + Parser: jwt.NewParser(), + TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()), + JWTConfig: config.JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "rsa256", + }, + } +} + +func (suite *ManualAuthenticatorSnappTestSuite) TestAuth() { + require := suite.Require() + + suite.Run("testing driver token auth", func() { + require.NoError(suite.Authenticator.Auth(suite.Tokens.Driver)) + }) + + suite.Run("testing passenger token auth", func() { + require.NoError(suite.Authenticator.Auth(suite.Tokens.Passenger)) + }) + + suite.Run("testing invalid token auth", func() { + require.Error(suite.Authenticator.Auth(invalidToken)) + }) + + suite.Run("testing token with invalid iss", func() { + token, err := getSampleToken("-1", suite.PrivateKeys.Passenger) + require.NoError(err) + + require.ErrorIs(suite.Authenticator.Auth(token), authenticator.KeyNotFoundError{ + Issuer: "-1", + }) + }) +} + +func (suite *ManualAuthenticatorSnappTestSuite) TestACLBasics() { + require := suite.Require() + + suite.Run("testing acl with invalid access type", func() { + ok, err := suite.Authenticator.ACL("invalid-access", suite.Tokens.Passenger, "test") + require.False(ok) + require.ErrorIs(err, authenticator.ErrInvalidAccessType) + }) + + suite.Run("testing acl with invalid token", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, invalidToken, validDriverCabEventTopic) + require.False(ok) + require.ErrorIs(err, jwt.ErrTokenMalformed) + }) + + suite.Run("testing acl with valid inputs", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, suite.Tokens.Passenger, validPassengerCabEventTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing acl with invalid topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, suite.Tokens.Passenger, invalidPassengerCabEventTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing acl with invalid access type", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, suite.Tokens.Passenger, validPassengerCabEventTopic) + require.Error(err) + require.False(ok) + }) +} + +// nolint: funlen +func (suite *ManualAuthenticatorSnappTestSuite) TestACLPassenger() { + require := suite.Require() + token := suite.Tokens.Passenger + + suite.Run("testing passenger subscribe on valid superapp event topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerSuperappEventTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing passenger subscribe on invalid superapp event topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerSuperappEventTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing passenger subscribe on valid shared location topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerSharedTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing passenger subscribe on invalid shared location topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerSharedTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing passenger subscribe on valid chat topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerChatTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing passenger subscribe on invalid chat topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerChatTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing passenger subscribe on valid entry call topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, validPassengerCallEntryTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing passenger subscribe on invalid call entry topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, invalidPassengerCallEntryTopic) + require.ErrorIs(err, authenticator.InvalidTopicError{ + Topic: invalidPassengerCallEntryTopic, + }) + require.False(ok) + }) + + suite.Run("testing passenger subscribe on valid outgoing call topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerCallOutgoingTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing passenger subscribe on valid outgoing call node topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, validPassengerNodeCallEntryTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing passenger subscribe on invalid call outgoing topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerCallOutgoingTopic) + require.Error(err) + require.False(ok) + }) +} + +// nolint: funlen +func (suite *ManualAuthenticatorSnappTestSuite) TestACLDriver() { + require := suite.Require() + token := suite.Tokens.Driver + + suite.Run("testing driver publish on its location topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, validDriverLocationTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver publish on invalid location topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, invalidDriverLocationTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing driver subscribe on invalid cab event topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverCabEventTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing driver subscribe on valid superapp event topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverSuperappEventTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver subscribe on invalid superapp event topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverSuperappEventTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing driver subscribe on valid shared location topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverSharedTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver subscribe on invalid shared location topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverSharedTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing driver subscribe on valid chat topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverChatTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver subscribe on invalid chat topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverChatTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing driver subscribe on valid call entry topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, validDriverCallEntryTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver subscribe on invalid call entry topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, invalidDriverCallEntryTopic) + require.Error(err) + require.False(ok) + }) + + suite.Run("testing driver subscribe on valid call outgoing topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverCallOutgoingTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver subscribe on valid call outgoing node topic", func() { + ok, err := suite.Authenticator.ACL(acl.Pub, token, validDriverNodeCallEntryTopic) + require.NoError(err) + require.True(ok) + }) + + suite.Run("testing driver subscribe on invalid call outgoing topic", func() { + ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverCallOutgoingTopic) + require.Error(err) + require.False(ok) + }) +} + +func TestManualAuthenticator_ValidateTopicBySender(t *testing.T) { + t.Parallel() + + cfg := config.SnappVendor() + + hid, err := topics.NewHashIDManager(cfg.HashIDMap) + require.NoError(t, err) + + // nolint: exhaustruct + authenticator := authenticator.ManualAuthenticator{ + AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}, + Company: "snapp", + TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()), + } + + t.Run("testing valid driver cab event", func(t *testing.T) { + t.Parallel() + + topicTemplate := authenticator.TopicManager.ParseTopic( + validDriverCabEventTopic, + topics.DriverIss, + "DXKgaNQa7N5Y7bo", + nil, + ) + require.NotNil(t, topicTemplate) + }) +} + +// Provide a topic which we don't have at snapp to test functions and fields that +// are used during the templating. +func TestManualAuthenticator_ValidateTopicBySenderAndClaims(t *testing.T) { + t.Parallel() + + cfg := config.SnappVendor() + + cfg.Topics = append(cfg.Topics, topics.Topic{ + Type: topics.SharedLocation, + Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/{{.uid}}/{{DecodeHashID (EncodeHashID .sub .iss) .iss}}$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + }, + ) + + hid, err := topics.NewHashIDManager(cfg.HashIDMap) + require.NoError(t, err) + + // nolint: exhaustruct + authenticator := authenticator.ManualAuthenticator{ + AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}, + Company: "snapp", + TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()), + } + + t.Run("testing valid driver cab event", func(t *testing.T) { + t.Parallel() + + topicTemplate := authenticator.TopicManager.ParseTopic( + "snapp/driver/123/456/123", + topics.DriverIss, + "123", + map[string]any{ + "uid": "456", + }, + ) + require.NotNil(t, topicTemplate) + }) +} + +// nolint: funlen +func TestManualAuthenticator_validateAccessType(t *testing.T) { + t.Parallel() + + type fields struct { + AllowedAccessTypes []acl.AccessType + } + + type args struct { + accessType acl.AccessType + } + + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + name: "#1 testing with no allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{}}, + args: args{accessType: acl.Sub}, + want: false, + }, + { + name: "#2 testing with no allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{}}, + args: args{accessType: acl.Pub}, + want: false, + }, + { + name: "#3 testing with no allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{}}, + args: args{accessType: acl.PubSub}, + want: false, + }, + { + name: "#4 testing with one allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}}, + args: args{accessType: acl.Pub}, + want: true, + }, + { + name: "#5 testing with one allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}}, + args: args{accessType: acl.Sub}, + want: false, + }, + { + name: "#6 testing with two allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}}, + args: args{accessType: acl.Sub}, + want: true, + }, + { + name: "#7 testing with two allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}}, + args: args{accessType: acl.Pub}, + want: true, + }, + { + name: "#8 testing with two allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}}, + args: args{accessType: acl.PubSub}, + want: false, + }, + { + name: "#9 testing with three allowed access type", + fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub}}, + args: args{accessType: acl.PubSub}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // nolint: exhaustruct + a := authenticator.ManualAuthenticator{ + AllowedAccessTypes: tt.fields.AllowedAccessTypes, + } + if got := a.ValidateAccessType(tt.args.accessType); got != tt.want { + t.Errorf("validateAccessType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/authenticator/metrics.go b/internal/authenticator/metrics.go new file mode 100644 index 00000000..0ce11f3b --- /dev/null +++ b/internal/authenticator/metrics.go @@ -0,0 +1,56 @@ +package authenticator + +import ( + "errors" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +// nolint:exhaustruct,gochecknoglobals +var AuthenticateCounterMetric = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "dispatching", + Subsystem: "soteria", + Name: "auth_total", + Help: "Total number of authentication attempts", +}, []string{"company", "status"}) + +// nolint:cyclop +func IncrementWithErrorAuthCounter(company string, err error) { + var ( + status string + topicNotAllowedErrorTarget *TopicNotAllowedError + keyNotFoundErrorTarget *KeyNotFoundError + ) + + switch { + case err == nil: + status = "success" + case errors.Is(err, ErrInvalidSigningMethod): + status = "err_invalid_signing_method" + case errors.Is(err, ErrIssNotFound): + status = "err_iss_not_found" + case errors.Is(err, ErrSubNotFound): + status = "err_sub_not_found" + case errors.Is(err, ErrInvalidClaims): + status = "err_invalid_claims" + case errors.Is(err, ErrInvalidIP): + status = "err_invalid_ip" + case errors.Is(err, ErrInvalidAccessType): + status = "err_invalid_access_type" + case errors.Is(err, ErrDecodeHashID): + status = "err_decode_hash_id" + case errors.Is(err, ErrInvalidSecret): + status = "err_invalid_secret" + case errors.Is(err, ErrIncorrectPassword): + status = "err_incorrect_password" + case errors.As(err, &topicNotAllowedErrorTarget): + status = "topic_not_allowed_error" + case errors.As(err, &keyNotFoundErrorTarget): + status = "key_not_found_error" + default: + status = "unknown_error" + } + + AuthenticateCounterMetric.WithLabelValues(company, status).Inc() +} diff --git a/internal/authenticator/metrics_test.go b/internal/authenticator/metrics_test.go new file mode 100644 index 00000000..08687c95 --- /dev/null +++ b/internal/authenticator/metrics_test.go @@ -0,0 +1,78 @@ +//nolint:testpackage +package authenticator + +import ( + "errors" + "testing" +) + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_NoError(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", nil) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrInvalidSigningMethod(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrInvalidSigningMethod) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrIssNotFound(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrIssNotFound) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrSubNotFound(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrSubNotFound) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrInvalidClaims(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrInvalidClaims) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrInvalidIP(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrInvalidIP) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrInvalidAccessType(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrInvalidAccessType) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrDecodeHashID(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrDecodeHashID) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrInvalidSecret(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrInvalidSecret) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrIncorrectPassword(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", ErrIncorrectPassword) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrTopicNotAllowed(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", &TopicNotAllowedError{ + Issuer: "issuer", + Sub: "subject", + AccessType: "1", + Topic: "topic", + TopicType: "pub", + }) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_ErrKeyNotFound(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", &KeyNotFoundError{Issuer: "iss"}) +} + +//nolint:paralleltest +func TestIncrementWithErrorAuthCounter_UnknowError(_ *testing.T) { + IncrementWithErrorAuthCounter("snapp", errors.ErrUnsupported) +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go new file mode 100644 index 00000000..f7eb3564 --- /dev/null +++ b/internal/cmd/root.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "os" + + "github.com/snapp-incubator/soteria/internal/cmd/serve" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/logger" + "github.com/snapp-incubator/soteria/internal/tracing" + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +// ExitFailure status code. +const ExitFailure = 1 + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + cfg := config.New() + + logger := logger.New(cfg.Logger).Named("root") + + tracer := tracing.New(cfg.Tracer, logger.Named("tracer")) + + //nolint: exhaustruct + root := &cobra.Command{ + Use: "soteria", + Short: "Soteria is the authentication service.", + Long: `Soteria is responsible for Authentication and Authorization of every request witch send to EMQ Server.`, + Run: func(cmd *cobra.Command, _ []string) { + cmd.Println("Run `soteria serve` to start serving requests") + }, + } + + serve.Serve{ + Cfg: cfg, + Logger: logger.Named("serve"), + Tracer: tracer, + }.Register(root) + + if err := root.Execute(); err != nil { + logger.Error("failed to execute root command", zap.Error(err)) + + os.Exit(ExitFailure) + } +} diff --git a/internal/cmd/serve/main.go b/internal/cmd/serve/main.go new file mode 100644 index 00000000..747b0d9f --- /dev/null +++ b/internal/cmd/serve/main.go @@ -0,0 +1,77 @@ +package serve + +import ( + "errors" + "fmt" + "net/http" + "os" + "os/signal" + + "github.com/snapp-incubator/soteria/internal/api" + "github.com/snapp-incubator/soteria/internal/authenticator" + "github.com/snapp-incubator/soteria/internal/config" + "github.com/spf13/cobra" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" +) + +type Serve struct { + Cfg config.Config + Logger *zap.Logger + Tracer trace.Tracer +} + +func (s Serve) main() { + auth, err := authenticator.Builder{ + Vendors: s.Cfg.Vendors, + Logger: s.Logger, + ValidatorConfig: s.Cfg.Validator, + Tracer: s.Tracer, + BlackListUserLoggingConfig: s.Cfg.BlackListUserLogging, + }.Authenticators() + if err != nil { + s.Logger.Fatal("authenticator building failed", zap.Error(err)) + } + + api := api.API{ + DefaultVendor: s.Cfg.DefaultVendor, + Authenticators: auth, + Tracer: s.Tracer, + Logger: s.Logger.Named("api"), + } + + if _, ok := api.Authenticators[s.Cfg.DefaultVendor]; !ok { + s.Logger.Fatal("default vendor shouldn't be nil, please set it") + } + + rest := api.ReSTServer() + + go func() { + if err := rest.Listen(fmt.Sprintf(":%d", s.Cfg.HTTPPort)); err != nil && !errors.Is(err, http.ErrServerClosed) { + s.Logger.Fatal("failed to run REST HTTP server", zap.Error(err)) + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + + if err := rest.Shutdown(); err != nil { + s.Logger.Error("error happened during REST API shutdown", zap.Error(err)) + } +} + +// Register serve command. +func (s Serve) Register(root *cobra.Command) { + root.AddCommand( + //nolint: exhaustruct + &cobra.Command{ + Use: "serve", + Short: "serve runs the application", + Long: `serve will run Soteria ReST server and waits until user disrupts.`, + Run: func(_ *cobra.Command, _ []string) { + s.main() + }, + }, + ) +} diff --git a/internal/commands/accounts/accounts.go b/internal/commands/accounts/accounts.go deleted file mode 100644 index 7ae75234..00000000 --- a/internal/commands/accounts/accounts.go +++ /dev/null @@ -1,17 +0,0 @@ -package accounts - -import ( - "fmt" - "github.com/spf13/cobra" -) - -var Accounts = &cobra.Command{ - Use: "accounts", - Short: "Soteria account management", - Long: `accounts is used to manage all operations related to accounts`, - Run: accountsRun, -} - -func accountsRun(cmd *cobra.Command, args []string) { - fmt.Println("run `soteria accounts --help` to see available options") -} diff --git a/internal/commands/accounts/init.go b/internal/commands/accounts/init.go deleted file mode 100644 index bf4fd81f..00000000 --- a/internal/commands/accounts/init.go +++ /dev/null @@ -1,69 +0,0 @@ -package accounts - -import ( - "encoding/json" - "fmt" - "io/ioutil" - - "github.com/spf13/cobra" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/config" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "golang.org/x/crypto/bcrypt" -) - -var Init = &cobra.Command{ - Use: "init [json file]", - Short: "init accounts data from a json file", - Long: `init is used to set all accounts data from a json file `, - Args: cobra.ExactArgs(1), - PreRunE: initPreRun, - RunE: initRun, -} - -func initPreRun(cmd *cobra.Command, args []string) error { - cfg := config.InitConfig() - - rClient, err := redis.NewRedisClient(cfg.Redis) - if err != nil { - return fmt.Errorf("could not init redis: %w", err) - } - - app.GetInstance().SetAccountsService(&accounts.Service{ - Handler: redis.ModelHandler{ - Client: rClient, - }, - }) - - return nil -} - -func initRun(cmd *cobra.Command, args []string) error { - file := args[0] - - raw, err := ioutil.ReadFile(file) - if err != nil { - return fmt.Errorf("failed to read file: %w", err) - } - - var users []user.User - if err := json.Unmarshal(raw, &users); err != nil { - return fmt.Errorf("failed to unmarshal data: %w", err) - } - - for _, u := range users { - hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) - if err != nil { - return fmt.Errorf("failed to generate hash for %s, %w", u.Username, err) - } - u.Password = string(hash) - - if err := app.GetInstance().AccountsService.Handler.Save(cmd.Context(), u); err != nil { - return fmt.Errorf("failed to import user %s: %w", u.Username, err) - } - } - - return nil -} diff --git a/internal/commands/root.go b/internal/commands/root.go deleted file mode 100644 index 262f45d8..00000000 --- a/internal/commands/root.go +++ /dev/null @@ -1,16 +0,0 @@ -package commands - -import ( - "github.com/spf13/cobra" -) - -var Root = &cobra.Command{ - Use: "soteria", - Short: "Soteria is the authentication service.", - Long: `Soteria is responsible for Authentication and Authorization of every request witch send to EMQ Server.`, - Run: rootRun, -} - -func rootRun(cmd *cobra.Command, args []string) { - cmd.Println("Run `soteria serve` to start serving requests") -} diff --git a/internal/commands/serve.go b/internal/commands/serve.go deleted file mode 100644 index fd1ec8f2..00000000 --- a/internal/commands/serve.go +++ /dev/null @@ -1,182 +0,0 @@ -package commands - -import ( - "context" - "crypto/rsa" - "errors" - "fmt" - "net" - "net/http" - "os" - "os/signal" - - "github.com/patrickmn/go-cache" - "github.com/spf13/cobra" - "gitlab.snapp.ir/dispatching/snappids/v2" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/config" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/cachedredis" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/metrics" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/grpc" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/log" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/memoize" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/tracer" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - _ "go.uber.org/automaxprocs" - "go.uber.org/zap" - grpcLib "google.golang.org/grpc" -) - -var Serve = &cobra.Command{ - Use: "serve", - Short: "serve runs the application", - Long: `serve will run Soteria REST and gRPC server and waits until user disrupts.`, - PreRun: servePreRun, - Run: serveRun, -} - -var cfg config.AppConfig - -func servePreRun(cmd *cobra.Command, args []string) { - cfg = config.InitConfig() - log.InitLogger() - log.SetLevel(cfg.Logger.Level) - - zap.L().Debug("config init successfully", - zap.String("cache_config", pkg.PrettifyStruct(cfg.Cache)), - zap.String("redis_config", pkg.PrettifyStruct(cfg.Redis)), - zap.String("logger_config", pkg.PrettifyStruct(cfg.Logger)), - zap.String("jwt_keys_path", cfg.Jwt.KeysPath), - zap.String("allowed_access_types", fmt.Sprintf("%v", cfg.AllowedAccessTypes))) - - privateKey100, err := cfg.ReadPrivateKey(user.ThirdParty) - if err != nil { - zap.L().Fatal("could not read third party private key") - } - - publicKey100, err := cfg.ReadPublicKey(user.ThirdParty) - if err != nil { - zap.L().Fatal("could not read third party public key") - } - - publicKey0, err := cfg.ReadPublicKey(user.Driver) - if err != nil { - zap.L().Fatal("could not read driver public key") - } - - publicKey1, err := cfg.ReadPublicKey(user.Passenger) - if err != nil { - zap.L().Fatal("could not read passenger public key") - } - - hid := &snappids.HashIDSManager{ - Salts: map[snappids.Audience]string{ - snappids.DriverAudience: cfg.DriverSalt, - snappids.PassengerAudience: cfg.PassengerSalt, - }, - Lengths: map[snappids.Audience]int{ - snappids.DriverAudience: cfg.DriverHashLength, - snappids.PassengerAudience: cfg.PassengerHashLength, - }, - } - - trc, cl, err := tracer.New(cfg.Tracer) - if err != nil { - zap.L().Fatal("could not create tracer", zap.Error(err)) - } - - app.GetInstance().SetTracer(trc, cl) - - rClient, err := redis.NewRedisClient(cfg.Redis) - if err != nil { - zap.L().Fatal("could not create redis client", zap.Error(err)) - } - - redisModelHandler := &redis.ModelHandler{Client: rClient} - - var modelHandler db.ModelHandler - if cfg.Cache.Enabled { - modelHandler = cachedredis.NewCachedRedisModelHandler(redisModelHandler, - cache.New(cfg.Cache.Expiration, cache.NoExpiration), - ) - } else { - modelHandler = redisModelHandler - } - - app.GetInstance().SetAccountsService(&accounts.Service{ - Handler: modelHandler, - }) - - store := emq.Store{Client: rClient} - app.GetInstance().SetEMQStore(store) - - allowedAccessTypes, err := cfg.GetAllowedAccessTypes() - if err != nil { - zap.L().Fatal("error while getting allowed access types", zap.Error(err)) - } - memoizedCompareHashAndPassword := memoize.MemoizedCompareHashAndPassword() - - app.GetInstance().SetAuthenticator(&authenticator.Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: privateKey100, - }, - PublicKeys: map[user.Issuer]*rsa.PublicKey{ - user.Driver: publicKey0, - user.Passenger: publicKey1, - user.ThirdParty: publicKey100, - }, - AllowedAccessTypes: allowedAccessTypes, - ModelHandler: modelHandler, - HashIDSManager: hid, - EMQTopicManager: snappids.NewEMQManagerWithCompany(hid, cfg.Company), - CompareHashAndPassword: memoizedCompareHashAndPassword, - Company: cfg.Company, - }) - - m := metrics.NewMetrics() - app.GetInstance().SetMetrics(&m.Handler) -} - -func serveRun(cmd *cobra.Command, args []string) { - rest := api.RestServer(cfg.Mode, cfg.HttpPort) - - gListen, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.GrpcPort)) - if err != nil { - zap.L().Fatal("failed to listen", zap.Int("port", cfg.GrpcPort), zap.Error(err)) - } - - grpcServer := grpc.NewServer() - - go func() { - if err := rest.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - zap.L().Fatal("failed to run REST HTTP server", zap.Error(err)) - } - }() - - go func() { - if err := grpcServer.Serve(gListen); err != nil && !errors.Is(err, grpcLib.ErrServerStopped) { - zap.L().Fatal("failed to run GRPC server", zap.Error(err)) - } - }() - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - - if err := rest.Shutdown(context.Background()); err != nil { - zap.L().Error("error happened during REST API shutdown", zap.Error(err)) - } - - grpcServer.Stop() - - if err := app.GetInstance().TracerCloser.Close(); err != nil { - zap.L().Error("error happened while closing tracer", zap.Error(err)) - } -} diff --git a/internal/commands/token.go b/internal/commands/token.go deleted file mode 100644 index 559a355f..00000000 --- a/internal/commands/token.go +++ /dev/null @@ -1,229 +0,0 @@ -package commands - -import ( - "bufio" - "crypto/rsa" - "errors" - "fmt" - "os" - "time" - - "github.com/spf13/cobra" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/config" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" -) - -var ( - ErrInvalidToken = errors.New("invalid token type") - ErrNoToken = errors.New("must be at least one token type") -) - -type ErrInvalidInput struct { - Message string -} - -func (err ErrInvalidInput) Error() string { - return fmt.Sprintf("invalid input. %s", err.Message) -} - -var Token = &cobra.Command{ - Use: "token", - Short: "token issues token based on type of user", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return ErrNoToken - } - - t := args[0] - if t == "herald" || t == "superuser" { - return nil - } - - return ErrInvalidInput{Message: fmt.Sprintf("token with type %s is not valid", t)} - }, - RunE: func(cmd *cobra.Command, args []string) error { - config := config.InitConfig() - - pk100, err := config.ReadPrivateKey(user.ThirdParty) - if err != nil { - return fmt.Errorf("cannot load private keys %w", err) - } - - // nolint: exhaustivestruct - app.GetInstance().SetAuthenticator(&authenticator.Authenticator{ - PrivateKeys: map[user.Issuer]*rsa.PrivateKey{ - user.ThirdParty: pk100, - }, - }) - - switch args[0] { - case "herald": - return heraldToken(cmd, args) - case "superuser": - return superuserToken(config, cmd, args) - default: - return ErrInvalidToken - } - }, -} - -func heraldToken(cmd *cobra.Command, args []string) error { - r := bufio.NewScanner(os.Stdin) - - cmd.Print("username? >>> ") - r.Scan() - username := r.Text() - - cmd.Print("duration? (in hours) >>> ") - r.Scan() - duration, err := time.ParseDuration(fmt.Sprintf("%sh", r.Text())) - if err != nil { - return fmt.Errorf("cannot parse duration %w", err) - } - - var ( - allowedTopics []acl.Topic - allowedEndpoints []acl.Endpoint - ) - - heraldEndpoints := map[string]string{ - "1": "/event", - "2": "/events", - "3": "/notification", - } - topicTypes := map[string]topics.Type{ - "1": topics.CabEvent, - "2": topics.BoxEvent, - "3": topics.SuperappEvent, - "4": topics.DriverLocation, - "5": topics.PassengerLocation, - "6": topics.SharedLocation, - "7": topics.Chat, - } - accessTypes := map[string]acl.AccessType{ - "1": acl.Sub, - "2": acl.Pub, - "3": acl.PubSub, - } - - for { - cmd.Println("do you want to give permissions? [y/n]") - cmd.Print(">>> ") - r.Scan() - - switch r.Text() { - case "n": - token, err := app.GetInstance().Authenticator.HeraldToken(username, allowedEndpoints, allowedTopics, duration) - if err != nil { - return fmt.Errorf("token creation failed %w", err) - } - - cmd.Println(token) - - return nil - case "y": - cmd.Println("which one do you want to grant access to? \n\t1. topic\n\t2. endpoint") - cmd.Print(">>> ") - r.Scan() - - switch r.Text() { - case "1": - cmd.Println("which topic do you want to grant access to?\n" + - "\t1. Snapp Cab Events" + - "\t2. Snapp Box Events" + - "\t3. Snapp Super App Events" + - "\t4. Snapp Driver Locations") - cmd.Print(">>> ") - r.Scan() - topicType, ok := topicTypes[r.Text()] - if !ok { - return ErrInvalidInput{Message: "selected topic does not exist"} - } - - cmd.Println("which access type do you want to grant?\n" + - "\t1. Subscribe" + - "\t2. Publish" + - "\t3. Publish-Subscribe") - cmd.Print(">>> ") - r.Scan() - at, ok := accessTypes[r.Text()] - if !ok { - return ErrInvalidInput{Message: "invalid input. selected access type does not exist"} - } - topic := acl.Topic{ - Type: topicType, - AccessType: at, - } - allowedTopics = append(allowedTopics, topic) - case "2": - cmd.Println("which endpoint do you want to grant access to?\n" + - "\t1. event" + - "\t2. events" + - "\t3. notification") - cmd.Print(">>> ") - r.Scan() - endpoint, ok := heraldEndpoints[r.Text()] - if !ok { - return ErrInvalidInput{Message: "invalid input. selected endpoint does not exist"} - } - e := acl.Endpoint{Name: endpoint} - allowedEndpoints = append(allowedEndpoints, e) - default: - return ErrInvalidInput{Message: ""} - } - default: - return ErrInvalidInput{Message: "you should enter y or n"} - } - } -} - -func superuserToken(config config.AppConfig, cmd *cobra.Command, _ []string) error { - cli, err := redis.NewRedisClient(config.Redis) - if err != nil { - return fmt.Errorf("redis connection failed %w", err) - } - - app.GetInstance().SetEMQStore(emq.Store{ - Client: cli, - }) - - r := bufio.NewScanner(os.Stdin) - - cmd.Print("username? >>> ") - r.Scan() - username := r.Text() - - cmd.Print("duration? (in hours) >>> ") - r.Scan() - duration, err := time.ParseDuration(fmt.Sprintf("%sh", r.Text())) - if err != nil { - return fmt.Errorf("cannot parse duration %w", err) - } - - cmd.Print("password? >>> ") - r.Scan() - password := r.Text() - - token, err := app.GetInstance().Authenticator.SuperuserToken(username, duration) - if err != nil { - return fmt.Errorf("token creation failed %w", err) - } - - if err := app.GetInstance().EMQStore.Save(cmd.Context(), emq.User{ - Username: token, - Password: password, - IsSuperuser: true, - }); err != nil { - return fmt.Errorf("cannot save token to redis %w", err) - } - - cmd.Println(token) - - return nil -} diff --git a/internal/config/config.go b/internal/config/config.go index a0fb72b1..73eba7ff 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,165 +1,109 @@ package config import ( - "crypto/rsa" - "fmt" - "io/ioutil" + "encoding/json" + "log" + "strings" "time" - "github.com/dgrijalva/jwt-go" - "github.com/kelseyhightower/envconfig" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/env" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/providers/structs" + "github.com/knadh/koanf/v2" + "github.com/snapp-incubator/soteria/internal/logger" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/internal/tracing" + "github.com/tidwall/pretty" ) -// AppConfig is the main container of Soteria's config. -type AppConfig struct { - AllowedAccessTypes []string `default:"sub,pub" split_words:"true"` - PassengerHashLength int `split_words:"true"` - DriverHashLength int `split_words:"true"` - PassengerSalt string `split_words:"true"` - DriverSalt string `split_words:"true"` - Redis *RedisConfig - Jwt *JwtConfig - Logger *LoggerConfig - Cache *CacheConfig - Mode string `default:"debug"` - HttpPort int `default:"9999" split_words:"true"` - GrpcPort int `default:"50051" split_words:"true"` - Tracer *TracerConfig - Company string `default:"snapp"` -} +const ( + // Prefix indicates environment variables prefix. + Prefix = "soteria_" +) -// RedisConfig is all configs needed to connect to a Redis server. -type RedisConfig struct { - Address string `split_words:"true"` - Password string `default:"" split_words:"true"` - ExpirationTime int `default:"30" split_words:"true"` - PoolSize int `split_words:"true" default:"10"` - MaxRetries int `split_words:"true" default:"0"` - MinIdleConnections int `split_words:"true" default:"5"` - ReadTimeout time.Duration `split_words:"true" default:"3s"` - PoolTimeout time.Duration `split_words:"true" default:"4s"` - MinRetryBackoff time.Duration `split_words:"true" default:"8ms"` - MaxRetryBackoff time.Duration `split_words:"true" default:"512ms"` - IdleTimeout time.Duration `split_words:"true" default:"300s"` - IdleCheckFrequency time.Duration `split_words:"true" default:"60s"` -} +type ( + // Config is the main container of Soteria's config. + Config struct { + Vendors []Vendor `json:"vendors,omitempty" koanf:"vendors"` + Logger logger.Config `json:"logger,omitempty" koanf:"logger"` + HTTPPort int `json:"http_port,omitempty" koanf:"http_port"` + Tracer tracing.Config `json:"tracer,omitempty" koanf:"tracer"` + DefaultVendor string `json:"default_vendor,omitempty" koanf:"default_vendor"` + Validator Validator `json:"validator,omitempty" koanf:"validator"` + BlackListUserLogging BlackListUserLogging `json:"black_list_user_logging,omitempty" koanf:"black_list_user_logging"` + } -// CacheConfig contains configs of in memory cache. -type CacheConfig struct { - Enabled bool `split_words:"true" default:"true"` - Expiration time.Duration `split_words:"true" default:"600s"` -} + Vendor struct { + AllowedAccessTypes []string `json:"allowed_access_types,omitempty" koanf:"allowed_access_types"` + Company string `json:"company,omitempty" koanf:"company"` + Topics []topics.Topic `json:"topics,omitempty" koanf:"topics"` + Keys map[string]string `json:"keys,omitempty" koanf:"keys"` + IssEntityMap map[string]string `json:"iss_entity_map,omitempty" koanf:"iss_entity_map"` + IssPeerMap map[string]string `json:"iss_peer_map,omitempty" koanf:"iss_peer_map"` + Jwt JWT `json:"jwt,omitempty" koanf:"jwt"` + Type string `json:"type,omitempty" koanf:"type"` + HashIDMap map[string]topics.HashData `json:"hash_id_map,omitempty" koanf:"hashid_map"` + } -// JwtConfig contains path of the keys for JWT encryption. -type JwtConfig struct { - KeysPath string `split_words:"true" default:"test/"` -} + JWT struct { + IssName string `json:"iss_name,omitempty" koanf:"iss_name"` + SubName string `json:"sub_name,omitempty" koanf:"sub_name"` + SigningMethod string `json:"signing_method,omitempty" koanf:"signing_method"` + } -// LoggerConfig is the config for logging and this kind of stuff. -type LoggerConfig struct { - Level string `default:"warn" split_words:"true"` + Validator struct { + URL string `json:"url,omitempty" koanf:"url"` + Timeout time.Duration `json:"timeout,omitempty" koanf:"timeout"` + } - SentryEnabled bool `default:"false" split_words:"true"` - SentryDSN string `envconfig:"SENTRY_DSN"` - SentryTimeout time.Duration `split_words:"true" default:"100ms"` -} + BlackListUserLogging struct { + Iss int `json:"iss,omitempty" koanf:"iss"` + UserIDs []int `json:"user_ids,omitempty" koanf:"user_ids"` + UserHashedIDs []string `json:"user_hashed_ids,omitempty" koanf:"user_hashed_ids"` + } +) -// TracerConfig contains all configs needed to create a tracer. -type TracerConfig struct { - Enabled bool `split_words:"false" default:"true"` - ServiceName string `default:"soteria" split_words:"true"` - SamplerType string `default:"const" split_words:"true"` - SamplerParam float64 `default:"1" split_words:"true"` - Host string `default:"localhost" split_words:"true"` - Port int `default:"6831" split_words:"true"` -} +// New reads configuration with koanf. +func New() Config { + var instance Config -// InitConfig tries to initialize app config from env variables. -func InitConfig() AppConfig { - appConfig := &AppConfig{} - appConfig.Redis = &RedisConfig{} - appConfig.Cache = &CacheConfig{} - appConfig.Jwt = &JwtConfig{} - appConfig.Logger = &LoggerConfig{} - appConfig.Tracer = &TracerConfig{} - - envconfig.MustProcess("soteria", appConfig) - envconfig.MustProcess("soteria_redis", appConfig.Redis) - envconfig.MustProcess("soteria_cache", appConfig.Cache) - envconfig.MustProcess("soteria_jwt", appConfig.Jwt) - envconfig.MustProcess("soteria_logger", appConfig.Logger) - envconfig.MustProcess("soteria_tracer", appConfig.Tracer) - return *appConfig -} + k := koanf.New(".") -// ReadPrivateKey will read and return private key that is used for JWT encryption -func (a *AppConfig) ReadPrivateKey(u user.Issuer) (*rsa.PrivateKey, error) { - var fileName string - switch u { - case user.ThirdParty: - fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "100.private.pem") - default: - return nil, fmt.Errorf("invalid issuer, private key not found") - } - pem, err := ioutil.ReadFile(fileName) - if err != nil { - return nil, err + // load default configuration from file + if err := k.Load(structs.Provider(Default(), "koanf"), nil); err != nil { + log.Fatalf("error loading default: %s", err) } - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(pem) - if err != nil { - return nil, err + + // load configuration from file + if err := k.Load(file.Provider("config.yml"), yaml.Parser()); err != nil { + log.Printf("error loading config.yml: %s", err) } - return privateKey, nil -} -// ReadPrivateKey will read and return private key that is used for JWT encryption -func (a *AppConfig) ReadPublicKey(u user.Issuer) (*rsa.PublicKey, error) { - var fileName string - switch u { - case user.Driver: - fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "0.pem") - case user.Passenger: - fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "1.pem") - case user.ThirdParty: - fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "100.pem") - default: - return nil, fmt.Errorf("invalid issuer, public key not found") + // load environment variables + if err := k.Load(env.Provider(Prefix, ".", func(s string) string { + return strings.ReplaceAll(strings.ToLower( + strings.TrimPrefix(s, Prefix)), "_", ".") + }), nil); err != nil { + log.Printf("error loading environment variables: %s", err) } - pem, err := ioutil.ReadFile(fileName) - if err != nil { - return nil, err + + if err := k.Unmarshal("", &instance); err != nil { + log.Fatalf("error unmarshalling config: %s", err) } - privateKey, err := jwt.ParseRSAPublicKeyFromPEM(pem) + + indent, err := json.MarshalIndent(instance, "", "\t") if err != nil { - return nil, err + log.Fatalf("error marshaling configuration to json: %s", err) } - return privateKey, nil -} -// GetAllowedAccessTypes will return all allowed access types in Soteria -func (a *AppConfig) GetAllowedAccessTypes() ([]acl.AccessType, error) { - var allowedAccessTypes []acl.AccessType - for _, a := range a.AllowedAccessTypes { - at, err := toUserAccessType(a) - if err != nil { - return nil, fmt.Errorf("could not convert %s: %w", at, err) - } - allowedAccessTypes = append(allowedAccessTypes, at) - } - return allowedAccessTypes, nil -} + indent = pretty.Color(indent, nil) + tmpl := ` + ================ Loaded Configuration ================ + %s + ====================================================== + ` + log.Printf(tmpl, string(indent)) -// toUserAccessType will convert string access type to it's own type -func toUserAccessType(i string) (acl.AccessType, error) { - switch i { - case "pub": - return acl.Pub, nil - case "sub": - return acl.Sub, nil - case "pubsub": - return acl.PubSub, nil - } - return "", fmt.Errorf("%v is a invalid acces type", i) + return instance } diff --git a/internal/config/default.go b/internal/config/default.go new file mode 100644 index 00000000..6ab7774a --- /dev/null +++ b/internal/config/default.go @@ -0,0 +1,188 @@ +package config + +import ( + "time" + + "github.com/snapp-incubator/soteria/internal/logger" + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/internal/tracing" + "github.com/snapp-incubator/soteria/pkg/acl" +) + +const ( + DefaultHTTPPort = 9999 + DefaultDriverHashLength = 15 + DefaultPassengerHashLength = 15 +) + +// Default return default configuration. +// nolint: mnd +func Default() Config { + return Config{ + DefaultVendor: "snapp", + Vendors: []Vendor{ + SnappVendor(), + }, + Logger: logger.Config{ + Level: "debug", + Stacktrace: true, + }, + HTTPPort: DefaultHTTPPort, + Tracer: tracing.Config{ + Enabled: false, + Ratio: 0.1, + Endpoint: "127.0.0.1:4317", + }, + Validator: Validator{ + URL: "http://validator-lb", + Timeout: 5 * time.Second, + }, + BlackListUserLogging: BlackListUserLogging{ + Iss: 0, + UserIDs: []int{}, + UserHashedIDs: []string{}, + }, + } +} + +// nolint: funlen +func SnappVendor() Vendor { + return Vendor{ + Type: "manual", + AllowedAccessTypes: []string{ + "pub", + "sub", + }, + HashIDMap: map[string]topics.HashData{ + "0": { + Alphabet: "", + Length: DefaultDriverHashLength, + Salt: "secret", + }, + "1": { + Alphabet: "", + Length: DefaultPassengerHashLength, + Salt: "secret", + }, + }, + Company: "snapp", + Topics: []topics.Topic{ + { + Type: topics.CabEvent, + Template: "^{{IssToEntity .iss}}-event-{{ EncodeMD5 (DecodeHashID .sub .iss) }}$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + }, + { + Type: topics.DriverLocation, + Template: "^{{.company}}/driver/{{.sub}}/location$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Pub, + topics.PassengerIss: acl.None, + }, + }, + { + Type: topics.PassengerLocation, + Template: "^{{.company}}/passenger/{{.sub}}/location$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Pub, + topics.PassengerIss: acl.Pub, + }, + }, + { + Type: topics.SuperappEvent, + Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/superapp$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + }, + { + Type: topics.BoxEvent, + Template: "^bucks$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.None, + topics.PassengerIss: acl.None, + }, + }, + { + Type: topics.SharedLocation, + Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/{{IssToPeer .iss}}-location$", //nolint:lll + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + }, + { + Type: topics.Chat, + Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/chat$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + }, + { + Type: topics.GeneralCallEntry, + Template: "^shared/{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/send$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Pub, + topics.PassengerIss: acl.Pub, + }, + }, + { + Type: topics.NodeCallEntry, + Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/[a-zA-Z0-9-_]+/send$", //nolint: lll + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Pub, + topics.PassengerIss: acl.Pub, + }, + }, + { + Type: topics.CallOutgoing, + Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/receive$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + }, + }, + Keys: map[string]string{ + "0": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv + va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm + X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL + C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s + ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX + J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU + DwIDAQAB +-----END PUBLIC KEY-----`, + + "1": `-----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl + +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK + aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8 + 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI + Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN + YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS + pQIDAQAB +-----END PUBLIC KEY-----`, + }, + IssEntityMap: map[string]string{ + "0": "driver", + "1": "passenger", + "default": "", + }, + IssPeerMap: map[string]string{ + "0": "passenger", + "1": "driver", + "default": "", + }, + Jwt: JWT{ + IssName: "iss", + SubName: "sub", + SigningMethod: "RS512", + }, + } +} diff --git a/internal/constants.go b/internal/constants.go deleted file mode 100644 index 5515900e..00000000 --- a/internal/constants.go +++ /dev/null @@ -1,17 +0,0 @@ -package internal - -const ( - Soteria = "soteria" - Acl = "acl" - Auth = "auth" - Token = "token" - Success = "success" - Failure = "failure" - Ignore = "ignore" - CabEvent = "cab_event" - DriverLocation = "driver_location" - SuperAppEvent = "super_app_event" - - HttpApi = "http" - GrpcApi = "grpc" -) diff --git a/internal/db/cachedredis/c.go b/internal/db/cachedredis/c.go deleted file mode 100644 index 7affa532..00000000 --- a/internal/db/cachedredis/c.go +++ /dev/null @@ -1,68 +0,0 @@ -package cachedredis - -import ( - "context" - "encoding/json" - - "github.com/patrickmn/go-cache" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" -) - -type ModelHandler struct { - redisModelHandler *redis.ModelHandler - cache *cache.Cache -} - -func NewCachedRedisModelHandler(redisModelHandler *redis.ModelHandler, cache *cache.Cache) *ModelHandler { - return &ModelHandler{ - redisModelHandler: redisModelHandler, - cache: cache, - } -} - -// Save saves a model in redis. -func (mh *ModelHandler) Save(ctx context.Context, model db.Model) error { - return mh.redisModelHandler.Save(ctx, model) -} - -// Delete finds and deletes a model from redis and cache. -func (mh *ModelHandler) Delete(ctx context.Context, modelName, pk string) error { - key := redis.GenerateKey(modelName, pk) - mh.cache.Delete(key) - - return mh.redisModelHandler.Delete(ctx, modelName, pk) -} - -// Get searches cache at first then returns a model from redis or from cache, if exists. -func (mh *ModelHandler) Get(ctx context.Context, modelName, pk string, v db.Model) error { - key := redis.GenerateKey(modelName, pk) - - item, found := mh.cache.Get(key) - if found { - if err := json.Unmarshal([]byte(item.(string)), v); err != nil { - return err - } - - return nil - } - - err := mh.redisModelHandler.Get(ctx, modelName, pk, v) - if err != nil { - return err - } - - value, _ := json.Marshal(v) - mh.cache.SetDefault(key, string(value)) - - return nil -} - -// Update invalidates cache and then finds and updates a model in redis. -func (mh *ModelHandler) Update(ctx context.Context, model db.Model) error { - key := redis.GenerateKey(model.GetMetadata().ModelName, model.GetPrimaryKey()) - - mh.cache.Delete(key) - - return mh.redisModelHandler.Update(ctx, model) -} diff --git a/internal/db/cachedredis/c_test.go b/internal/db/cachedredis/c_test.go deleted file mode 100644 index 531ea0f3..00000000 --- a/internal/db/cachedredis/c_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package cachedredis_test - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis/v8" - "github.com/patrickmn/go-cache" - "github.com/stretchr/testify/suite" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/cachedredis" - rd "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" -) - -type CacheModelHandlerSuite struct { - suite.Suite - - Model db.ModelHandler - DB *redis.Client - Cache *cache.Cache -} - -func (suite *CacheModelHandlerSuite) SetupSuite() { - mr, err := miniredis.Run() - suite.NoError(err) - - // nolint: exhaustivestruct - client := redis.NewClient(&redis.Options{ - Addr: mr.Addr(), - }) - - cache := cache.New(time.Minute*60, time.Minute*60) - - suite.DB = client - suite.Cache = cache - - suite.Model = cachedredis.NewCachedRedisModelHandler(&rd.ModelHandler{Client: client}, cache) -} - -func (suite *CacheModelHandlerSuite) TestGet() { - require := suite.Require() - - expected := MockModel{Name: "test", Value: ""} - v, err := json.Marshal(expected) - require.NoError(err) - - require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err()) - - suite.Run("testing first successful get", func() { - var actual MockModel - require.NoError(suite.Model.Get(context.Background(), "mock", "test", &actual)) - require.Equal(expected, actual) - - suite.Cache.Get(rd.GenerateKey("mock", "test")) - }) - suite.Run("testing second successful get", func() { - var actual MockModel - err := suite.Model.Get(context.Background(), "mock", "test", &actual) - - require.Equal(expected, actual) - require.NoError(err) - }) - suite.Run("testing failed get", func() { - var actual MockModel - err := suite.Model.Get(context.Background(), "mock", "t", &actual) - - require.EqualError(err, "redis: nil") - }) - - suite.NoError(suite.DB.Del(context.Background(), "mock-test").Err()) -} - -func (suite *CacheModelHandlerSuite) TestSave() { - require := suite.Require() - - expected := MockModel{Name: "save-test", Value: ""} - - require.NoError(suite.Model.Save(context.Background(), expected)) - - v := suite.DB.Get(context.Background(), "mock-save-test").Val() - - var actual MockModel - - require.NoError(json.Unmarshal([]byte(v), &actual)) - require.Equal(expected, actual) -} - -func (suite *CacheModelHandlerSuite) TestDelete() { - require := suite.Require() - - expected := MockModel{Name: "test", Value: ""} - v, err := json.Marshal(expected) - require.NoError(err) - - require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err()) - - suite.Run("testing successful delete", func() { - require.NoError(suite.Model.Delete(context.Background(), "mock", "test")) - - err := suite.DB.Get(context.Background(), "mock-test").Err() - require.EqualError(err, "redis: nil") - - item, ok := suite.Cache.Get("mock-test") - require.False(ok) - require.Nil(item) - }) - suite.Run("testing failed delete", func() { - var actual MockModel - - err := suite.Model.Get(context.Background(), "mock", "t", &actual) - require.EqualError(err, "redis: nil") - }) -} - -func (suite *CacheModelHandlerSuite) TestUpdate() { - require := suite.Require() - - m := MockModel{Name: "test", Value: "test-1"} - v, err := json.Marshal(m) - require.NoError(err) - - require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err()) - - newModel := MockModel{Name: "test", Value: "test-2"} - require.NoError(suite.Model.Update(context.Background(), newModel)) - - var updatedModel MockModel - - require.NoError(suite.Model.Get(context.Background(), "mock", "test", &updatedModel)) - require.Equal(newModel, updatedModel) -} - -type MockModel struct { - Name string `json:"name"` - Value string `json:"value"` -} - -func (m MockModel) GetMetadata() db.MetaData { - return db.MetaData{ - ModelName: "mock", - DateCreated: time.Time{}, - DateModified: time.Time{}, - } -} - -func (m MockModel) GetPrimaryKey() string { - return m.Name -} - -func TestCacheModelHandlerSuite(t *testing.T) { - t.Parallel() - - suite.Run(t, new(CacheModelHandlerSuite)) -} diff --git a/internal/db/main/main.go b/internal/db/main/main.go deleted file mode 100644 index 74f2e44c..00000000 --- a/internal/db/main/main.go +++ /dev/null @@ -1,305 +0,0 @@ -package main - -import ( - "crypto/rsa" - "encoding/json" - "fmt" - "github.com/dgrijalva/jwt-go" - "github.com/google/uuid" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "io/ioutil" - "os" - "time" -) - -func main() { - password := "password" - secret := "secret" - - var users []user.User - driver := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "driver", - Password: string(password), - Type: user.EMQUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Topic: topics.CabEvent, - AccessType: acl.Sub, - }, - user.Rule{ - UUID: uuid.New(), - Topic: topics.DriverLocation, - AccessType: acl.Pub, - }, - user.Rule{ - UUID: uuid.New(), - Topic: topics.SuperappEvent, - AccessType: acl.Sub, - }, - user.Rule{ - UUID: uuid.New(), - Topic: topics.SharedLocation, - AccessType: acl.Sub, - }, - user.Rule{ - UUID: uuid.New(), - Topic: topics.Chat, - AccessType: acl.PubSub, - }, - }, - } - - passenger := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "passenger", - Password: string(password), - Type: user.EMQUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - { - UUID: uuid.New(), - Topic: topics.CabEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.SuperappEvent, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.PassengerLocation, - AccessType: acl.Pub, - }, - { - UUID: uuid.New(), - Topic: topics.SharedLocation, - AccessType: acl.Sub, - }, - { - UUID: uuid.New(), - Topic: topics.Chat, - AccessType: acl.PubSub, - }, - }, - } - box := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "box", - Password: string(password), - Type: user.EMQUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Topic: topics.BoxEvent, - AccessType: acl.Sub, - }, - }, - } - - colonySubscriber := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "colony-subscriber", - Password: string(password), - Type: user.EMQUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Topic: topics.DriverLocation, - AccessType: acl.Sub, - }, - }, - } - - snappbox := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "snapp-box", - Password: string(password), - Type: user.HeraldUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - }, - } - - snappfood := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "snapp-food", - Password: string(password), - Type: user.HeraldUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - }, - } - - snappmarket := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "snapp-market", - Password: string(password), - Type: user.HeraldUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - }, - } - - snappflight := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "snapp-flight", - Password: string(password), - Type: user.HeraldUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - }, - } - - snappdoctor := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "snapp-doctor", - Password: string(password), - Type: user.HeraldUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - }, - } - - gabriel := user.User{ - MetaData: db.MetaData{ - ModelName: "user", - DateCreated: time.Now(), - DateModified: time.Now(), - }, - Username: "gabriel", - Password: string(password), - Type: user.HeraldUser, - Secret: string(secret), - TokenExpirationDuration: time.Hour * 30 * 24, - Rules: []user.Rule{ - user.Rule{ - UUID: uuid.New(), - Endpoint: "/notification", - AccessType: acl.Pub, - }, - user.Rule{ - UUID: uuid.New(), - Endpoint: "/event", - AccessType: acl.Pub, - }, - }, - } - - users = append(users, driver) - users = append(users, passenger) - users = append(users, box) - users = append(users, colonySubscriber) - users = append(users, snappbox) - users = append(users, snappfood) - users = append(users, snappmarket) - users = append(users, snappflight) - users = append(users, snappdoctor) - users = append(users, gabriel) - - d, _ := json.Marshal(users) - ioutil.WriteFile("db.json", d, 0644) -} - -func getPublicKey(u user.Issuer) (*rsa.PublicKey, error) { - var fileName string - d, _ := os.Getwd() - fmt.Println(d) - switch u { - case user.Passenger: - fileName = "test/1.pem" - case user.Driver: - fileName = "test/0.pem" - case user.ThirdParty: - fileName = "test/100.pem" - default: - return nil, fmt.Errorf("invalid user, public key not found") - } - pem, err := ioutil.ReadFile(fileName) - if err != nil { - return nil, err - } - publicKey, err := jwt.ParseRSAPublicKeyFromPEM(pem) - if err != nil { - return nil, err - } - return publicKey, nil -} diff --git a/internal/db/models.go b/internal/db/models.go deleted file mode 100644 index d1633395..00000000 --- a/internal/db/models.go +++ /dev/null @@ -1,28 +0,0 @@ -package db - -import ( - "context" - "errors" - "time" -) - -// ErrDb is returned when there was an error in database operations -var ErrDb = errors.New("database error") - -type ModelHandler interface { - Save(ctx context.Context, model Model) error - Delete(ctx context.Context, modelName, pk string) error - Get(ctx context.Context, modelName, pk string, model Model) error - Update(ctx context.Context, model Model) error -} - -type Model interface { - GetMetadata() MetaData - GetPrimaryKey() string -} - -type MetaData struct { - ModelName string `json:"model_name"` - DateCreated time.Time `json:"date_created"` - DateModified time.Time `json:"date_modified"` -} diff --git a/internal/db/redis/client.go b/internal/db/redis/client.go deleted file mode 100644 index e4bab455..00000000 --- a/internal/db/redis/client.go +++ /dev/null @@ -1,33 +0,0 @@ -package redis - -import ( - "context" - "fmt" - - "github.com/go-redis/redis/v8" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/config" -) - -func NewRedisClient(cfg *config.RedisConfig) (*redis.Client, error) { - // nolint: exhaustivestruct - opts := &redis.Options{ - Addr: cfg.Address, - Password: cfg.Password, - PoolTimeout: cfg.PoolTimeout, - ReadTimeout: cfg.ReadTimeout, - MaxRetries: cfg.MaxRetries, - MaxRetryBackoff: cfg.MaxRetryBackoff, - MinRetryBackoff: cfg.MinRetryBackoff, - IdleTimeout: cfg.IdleTimeout, - IdleCheckFrequency: cfg.IdleCheckFrequency, - PoolSize: cfg.PoolSize, - MinIdleConns: cfg.MinIdleConnections, - } - - client := redis.NewClient(opts) - if err := client.Ping(context.Background()).Err(); err != nil { - return nil, fmt.Errorf("could not ping redis: %w", err) - } - - return client, nil -} diff --git a/internal/db/redis/redis.go b/internal/db/redis/redis.go deleted file mode 100644 index 635f8646..00000000 --- a/internal/db/redis/redis.go +++ /dev/null @@ -1,94 +0,0 @@ -package redis - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/go-redis/redis/v8" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" -) - -// RedisModelHandler implements ModelHandler interface. -type ModelHandler struct { - Client redis.Cmdable -} - -// Save saves a model in redis. -func (rmh ModelHandler) Save(ctx context.Context, model db.Model) error { - md := model.GetMetadata() - pk := model.GetPrimaryKey() - key := GenerateKey(md.ModelName, pk) - - value, err := json.Marshal(model) - if err != nil { - return err - } - - if err := rmh.Client.Set(ctx, key, string(value), 0).Err(); err != nil { - return fmt.Errorf("%w: %s", db.ErrDb, err) - } - - return nil -} - -// Delete finds and deletes a model from redis and cache. -func (rmh ModelHandler) Delete(ctx context.Context, modelName, pk string) error { - key := GenerateKey(modelName, pk) - - res, err := rmh.Client.Del(ctx, key).Result() - if err != nil { - return fmt.Errorf("%w: %s", db.ErrDb, err) - } - - if res < 1 { - return fmt.Errorf("key does not exist") - } - - return nil -} - -// Get returns a model from redis or from cache, if exists. -func (rmh ModelHandler) Get(ctx context.Context, modelName, pk string, v db.Model) error { - key := GenerateKey(modelName, pk) - - res, err := rmh.Client.Get(ctx, key).Result() - if err != nil && err == redis.Nil { - return fmt.Errorf("%s", err) - } else if err != nil { - return fmt.Errorf("%w: %s", db.ErrDb, err) - } - - if err := json.Unmarshal([]byte(res), v); err != nil { - return fmt.Errorf("cannot unmarshal model %w", err) - } - - return nil -} - -// Update finds and updates a model in redis. -func (rmh ModelHandler) Update(ctx context.Context, model db.Model) error { - md := model.GetMetadata() - pk := model.GetPrimaryKey() - - key := GenerateKey(md.ModelName, pk) - - value, err := json.Marshal(model) - if err != nil { - return err - } - - pipeline := rmh.Client.Pipeline() - pipeline.Del(ctx, key) - pipeline.Set(ctx, key, string(value), 0) - if _, err := pipeline.Exec(ctx); err != nil { - return fmt.Errorf("%w: %s", db.ErrDb, err) - } - - return nil -} - -// GenerateKey is used to generate redis keys. -func GenerateKey(modelName, pk string) string { - return fmt.Sprintf("%s-%s", modelName, pk) -} diff --git a/internal/db/redis/redis_test.go b/internal/db/redis/redis_test.go deleted file mode 100644 index 9b883915..00000000 --- a/internal/db/redis/redis_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package redis_test - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis/v8" - "github.com/stretchr/testify/suite" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - rd "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" -) - -type RedisModelHandlerSuite struct { - suite.Suite - - Model db.ModelHandler - DB *redis.Client -} - -func (suite *RedisModelHandlerSuite) SetupSuite() { - mr, err := miniredis.Run() - suite.Require().NoError(err) - - // nolint: exhaustivestruct - client := redis.NewClient(&redis.Options{ - Addr: mr.Addr(), - }) - - suite.DB = client - suite.Model = rd.ModelHandler{Client: client} -} - -func (suite *RedisModelHandlerSuite) TestGet() { - require := suite.Require() - - expected := MockModel{Name: "test", Value: ""} - v, err := json.Marshal(expected) - require.NoError(err) - - require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err()) - - suite.Run("testing successful get", func() { - var actual MockModel - err := suite.Model.Get(context.Background(), "mock", "test", &actual) - require.Equal(expected, actual) - require.NoError(err) - }) - - suite.Run("testing invalid get usage", func() { - var actual MockModel - err := suite.Model.Get(context.Background(), "mock", "test", actual) - require.Error(err) - }) - - suite.Run("testing failed get", func() { - var actual MockModel - err := suite.Model.Get(context.Background(), "mock", "t", &actual) - require.EqualError(err, "redis: nil") - }) - - require.NoError(suite.DB.Del(context.Background(), "mock-test").Err()) -} - -func (suite *RedisModelHandlerSuite) TestSave() { - require := suite.Require() - - expected := MockModel{Name: "save-test", Value: ""} - - require.NoError(suite.Model.Save(context.Background(), expected)) - - v := suite.DB.Get(context.Background(), "mock-save-test").Val() - - var actual MockModel - - require.NoError(json.Unmarshal([]byte(v), &actual)) - require.Equal(expected, actual) -} - -func (suite *RedisModelHandlerSuite) TestDelete() { - require := suite.Require() - - expected := MockModel{Name: "test", Value: ""} - v, _ := json.Marshal(expected) - - require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err()) - - suite.Run("testing successful delete", func() { - require.NoError(suite.Model.Delete(context.Background(), "mock", "test")) - - err := suite.DB.Get(context.Background(), "mock-test").Err() - require.EqualError(err, "redis: nil") - }) - - suite.Run("testing failed delete", func() { - var actual MockModel - - err := suite.Model.Get(context.Background(), "mock", "t", &actual) - require.EqualError(err, "redis: nil") - }) -} - -func (suite *RedisModelHandlerSuite) TestUpdate() { - require := suite.Require() - - m := MockModel{Name: "test", Value: "test-1"} - v, err := json.Marshal(m) - require.NoError(err) - - require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err()) - - newModel := MockModel{Name: "test", Value: "test-2"} - require.NoError(suite.Model.Update(context.Background(), newModel)) - - var updatedModel MockModel - - require.NoError(suite.Model.Get(context.Background(), "mock", "test", &updatedModel)) - require.Equal(newModel, updatedModel) -} - -type MockModel struct { - Name string `json:"name"` - Value string `json:"value"` -} - -func (m MockModel) GetMetadata() db.MetaData { - return db.MetaData{ - ModelName: "mock", - DateCreated: time.Time{}, - DateModified: time.Time{}, - } -} - -func (m MockModel) GetPrimaryKey() string { - return m.Name -} - -func TestRedisModelHandlerSuite(t *testing.T) { - t.Parallel() - - suite.Run(t, new(RedisModelHandlerSuite)) -} diff --git a/internal/emq/store.go b/internal/emq/store.go deleted file mode 100644 index a08adb25..00000000 --- a/internal/emq/store.go +++ /dev/null @@ -1,90 +0,0 @@ -package emq - -import ( - "context" - "errors" - "fmt" - "strconv" - "strings" - - "github.com/go-redis/redis/v8" -) - -type Store struct { - Client *redis.Client -} - -var ( - ErrInvalidHResult = errors.New("invalid hresult structure") - ErrUserNotExists = errors.New("user doesn't exist") -) - -const ( - PasswordHKey = "password" - IsSuperuserHKey = "is_superuser" -) - -func Key(username string) string { - return fmt.Sprintf("mqtt_user:%s", username) -} - -func (s *Store) Save(ctx context.Context, u User) error { - if err := s.Client.HSet(ctx, Key(u.Username), PasswordHKey, u.Password, - IsSuperuserHKey, strconv.FormatBool(u.IsSuperuser)).Err(); err != nil { - return fmt.Errorf("failed to save into redis %w", err) - } - - return nil -} - -func (s *Store) Load(ctx context.Context, username string) (User, error) { - val, err := s.Client.HGetAll(ctx, Key(username)).Result() - if err != nil { - return User{}, fmt.Errorf("failed to load from redis %w", err) - } - - if len(val) == 0 { - return User{}, ErrUserNotExists - } - - password, ok := val[PasswordHKey] - if !ok { - return User{}, ErrInvalidHResult - } - - siss, ok := val[IsSuperuserHKey] - if !ok { - return User{}, ErrInvalidHResult - } - - iss, err := strconv.ParseBool(siss) - if err != nil { - return User{}, fmt.Errorf("invalid is_superuser %w", err) - } - - return User{ - Password: password, - IsSuperuser: iss, - Username: username, - }, nil -} - -func (s *Store) LoadAll(ctx context.Context) ([]User, error) { - keys, err := s.Client.Keys(ctx, "mqtt_user:*").Result() - if err != nil { - return nil, fmt.Errorf("failed to fetch keys from redis %w", err) - } - - users := make([]User, 0) - - for _, key := range keys { - user, err := s.Load(ctx, strings.TrimPrefix(key, "mqtt_user:")) - if err != nil { - return nil, fmt.Errorf("failed to fetch user from redis %w", err) - } - - users = append(users, user) - } - - return users, nil -} diff --git a/internal/emq/store_test.go b/internal/emq/store_test.go deleted file mode 100644 index 63df608e..00000000 --- a/internal/emq/store_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package emq_test - -import ( - "context" - "fmt" - "testing" - - "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis/v8" - "github.com/stretchr/testify/suite" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq" -) - -type StoreSuite struct { - suite.Suite - - Client *redis.Client - Store emq.Store -} - -func (suite *StoreSuite) SetupSuite() { - mr, err := miniredis.Run() - suite.Require().NoError(err) - - // nolint: exhaustivestruct - client := redis.NewClient(&redis.Options{ - Addr: mr.Addr(), - }) - - suite.Client = client - suite.Store = emq.Store{Client: client} -} - -func (suite *StoreSuite) TearDownTest() { - suite.Require().NoError(suite.Client.FlushAll(context.Background()).Err()) -} - -func (suite *StoreSuite) TestSave() { - require := suite.Require() - - ctx := context.Background() - - require.NoError(suite.Store.Save(ctx, emq.User{ - Username: "secret", - Password: "password", - IsSuperuser: true, - })) - - ok, err := suite.Client.Exists(ctx, "mqtt_user:secret").Result() - require.NoError(err) - require.Equal(int64(1), ok) -} - -func (suite *StoreSuite) TestLoad() { - require := suite.Require() - - ctx := context.Background() - - user := emq.User{ - Username: "secret", - Password: "password", - IsSuperuser: true, - } - - require.NoError(suite.Store.Save(ctx, user)) - - u, err := suite.Store.Load(ctx, user.Username) - require.NoError(err) - require.Equal(u, user) -} - -func (suite *StoreSuite) TestLoadAll() { - require := suite.Require() - - total := 10 - - ctx := context.Background() - - users := make([]emq.User, 0) - - for index := 0; index < total; index++ { - user := emq.User{ - Username: fmt.Sprintf("secret-%d", index), - Password: "password", - IsSuperuser: true, - } - - users = append(users, user) - - require.NoError(suite.Store.Save(ctx, user)) - } - - us, err := suite.Store.LoadAll(ctx) - require.NoError(err) - require.Equal(us, users) -} - -func TestStoreSuite(t *testing.T) { - t.Parallel() - - suite.Run(t, new(StoreSuite)) -} diff --git a/internal/emq/user.go b/internal/emq/user.go deleted file mode 100644 index c03f6ab9..00000000 --- a/internal/emq/user.go +++ /dev/null @@ -1,9 +0,0 @@ -package emq - -// User contains the user information for storing in emq based on its redis-auth plugin. -// https://github.com/emqx/emqx-auth-redis -type User struct { - Username string - Password string - IsSuperuser bool -} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 00000000..e474b220 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,42 @@ +package logger + +import ( + "log" + "os" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Config struct { + Level string `json:"level,omitempty" koanf:"level"` + Stacktrace bool `json:"stacktrace,omitempty" koanf:"stacktrace"` +} + +// New creates a zap logger for console. +func New(cfg Config) *zap.Logger { + var lvl zapcore.Level + if err := lvl.Set(cfg.Level); err != nil { + log.Printf("cannot parse log level %s: %s", cfg.Level, err) + + lvl = zapcore.WarnLevel + } + + encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) + defaultCore := zapcore.NewCore(encoder, zapcore.Lock(zapcore.AddSync(os.Stderr)), lvl) + cores := []zapcore.Core{ + defaultCore, + } + + core := zapcore.NewTee(cores...) + zapOpts := make([]zap.Option, 0, 2) // nolint:mnd + zapOpts = append(zapOpts, zap.AddCaller()) + + if cfg.Stacktrace { + zapOpts = append(zapOpts, zap.AddStacktrace(zap.ErrorLevel)) + } + + logger := zap.New(core, zapOpts...) + + return logger +} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go deleted file mode 100644 index 099b1ef4..00000000 --- a/internal/metrics/metrics.go +++ /dev/null @@ -1,46 +0,0 @@ -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/metrics" -) - -type SoteriaMetrics struct { - Handler metrics.Handler -} - -// NewMetrics creates and returns all metrics needed in Soteria -func NewMetrics() *SoteriaMetrics { - statusCodesCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "dispatching", - Subsystem: "soteria", - Name: "status_codes", - Help: "status codes observed from soteria and its all external calls", - }, []string{"api", "service", "function", "code"}) - - statusesCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "dispatching", - Subsystem: "soteria", - Name: "statuses", - Help: "statuses observed from soteria and its all external calls", - }, []string{"api", "service", "function", "status", "info"}) - - responseTimesSummery := prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Namespace: "dispatching", - Subsystem: "soteria", - Name: "response_times", - Help: "response times observed from and its all external calls", - }, []string{"api", "service", "function"}) - - prometheus.MustRegister(statusCodesCounter) - prometheus.MustRegister(statusesCounter) - prometheus.MustRegister(responseTimesSummery) - - h := metrics.Handler{ - StatusCodeCounterVec: statusCodesCounter, - StatusCounterVec: statusesCounter, - ResponseTimeVec: responseTimesSummery, - } - - return &SoteriaMetrics{h} -} diff --git a/internal/topics/config.go b/internal/topics/config.go new file mode 100644 index 00000000..148422f4 --- /dev/null +++ b/internal/topics/config.go @@ -0,0 +1,7 @@ +package topics + +type HashData struct { + Length int `json:"length,omitempty" koanf:"length"` + Salt string `json:"salt,omitempty" koanf:"salt"` + Alphabet string `json:"alphabet,omitempty" koanf:"alphabet"` +} diff --git a/internal/topics/manager.go b/internal/topics/manager.go new file mode 100644 index 00000000..092dd3ae --- /dev/null +++ b/internal/topics/manager.go @@ -0,0 +1,209 @@ +// Package topics regular expressions which are used for detecting the topic name. +// topics are prefix with the company name will be trimed before matching +// so they regular expressions should not contain the company prefix. +package topics + +import ( + "crypto/md5" //nolint: gosec + "encoding/hex" + "fmt" + "regexp" + "strconv" + "strings" + "text/template" + + "github.com/speps/go-hashids/v2" + "go.uber.org/zap" +) + +const ( + CabEvent string = "cab_event" + DriverLocation string = "driver_location" + PassengerLocation string = "passenger_location" + SuperappEvent string = "superapp_event" + BoxEvent string = "box_event" + SharedLocation string = "shared_location" + Chat string = "chat" + GeneralCallEntry string = "general_call_entry" + NodeCallEntry string = "node_call_entry" + CallOutgoing string = "call_outgoing" +) + +const ( + Driver string = "driver" + DriverIss string = "0" + + Passenger string = "passenger" + PassengerIss string = "1" + + NoneIss string = "-1" +) + +const ( + // EmqCabHashPrefix is the default prefix for hashing part of cab topic, default value is 'emqch'. + EmqCabHashPrefix = "emqch" + + Default = "default" +) + +type Manager struct { + HashIDSManager map[string]*hashids.HashID + Company string + TopicTemplates []Template + IssEntityMap map[string]string + IssPeerMap map[string]string + Functions template.FuncMap + Logger *zap.Logger +} + +// NewTopicManager returns a topic manager to validate topics. +func NewTopicManager( + topicList []Topic, + hashIDManager map[string]*hashids.HashID, + company string, + issEntityMap, issPeerMap map[string]string, + logger *zap.Logger, +) *Manager { + manager := &Manager{ //nolint: exhaustruct + HashIDSManager: hashIDManager, + Company: company, + IssEntityMap: issEntityMap, + IssPeerMap: issPeerMap, + Logger: logger, + } + + manager.Functions = template.FuncMap{ + "IssToEntity": manager.IssEntityMapper, + "DecodeHashID": manager.DecodeHashID, + "EncodeHashID": manager.EncodeHashID, + "EncodeMD5": manager.EncodeMD5, + "IssToPeer": manager.IssPeerMapper, + } + + templates := make([]Template, 0) + + for _, topic := range topicList { + each := Template{ + Type: topic.Type, + Template: template.Must(template.New(topic.Type).Funcs(manager.Functions).Parse(topic.Template)), + Accesses: topic.Accesses, + } + templates = append(templates, each) + } + + manager.TopicTemplates = templates + + return manager +} + +// ParseTopic checks if a topic is valid based on the given parameters. +func (t *Manager) ParseTopic(topic, iss, sub string, claims map[string]any) *Template { + fields := make(map[string]string) + + for k, v := range claims { + fields[k] = fmt.Sprintf("%v", v) + } + + fields["iss"] = iss + fields["company"] = t.Company + fields["sub"] = sub + + for _, topicTemplate := range t.TopicTemplates { + regex := new(strings.Builder) + + if err := topicTemplate.Template.Execute(regex, fields); err != nil { + t.Logger.Error("template execution failed", zap.Error(err), zap.String("template", topicTemplate.Type)) + + return nil + } + + t.Logger.Debug("topic template generated", + zap.String("topic", regex.String()), + zap.String("iss", iss), + zap.String("sub", sub), + zap.String("company", t.Company), + ) + + if regexp.MustCompile(regex.String()).MatchString(topic) { + return &topicTemplate + } + } + + return nil +} + +func (t *Manager) EncodeMD5(iss string) string { + hid := md5.Sum([]byte(fmt.Sprintf("%s-%s", EmqCabHashPrefix, iss))) //nolint:gosec + + return hex.EncodeToString(hid[:]) +} + +func (t *Manager) DecodeHashID(sub, iss string) string { + id, err := t.HashIDSManager[iss].DecodeWithError(sub) + if err != nil { + t.Logger.Error("decoding sub failed", zap.Error(err), zap.String("sub", sub)) + + return "" + } + + return strconv.Itoa(id[0]) +} + +func (t *Manager) EncodeHashID(sub, iss string) string { + subInt, err := strconv.Atoi(sub) + if err != nil { + t.Logger.Error("encoding sub failed", zap.Error(err), zap.String("sub", sub)) + + return "" + } + + id, err := t.HashIDSManager[iss].Encode([]int{subInt}) + if err != nil { + t.Logger.Error("encoding sub failed", zap.Error(err), zap.String("sub", sub)) + + return "" + } + + return id +} + +func (t *Manager) IssEntityMapper(iss string) string { + result, ok := t.IssEntityMap[iss] + if ok { + return result + } + + return t.IssEntityMap[Default] +} + +func (t *Manager) IssPeerMapper(iss string) string { + result, ok := t.IssPeerMap[iss] + if ok { + return result + } + + return t.IssPeerMap[Default] +} + +func NewHashIDManager(hidmap map[string]HashData) (map[string]*hashids.HashID, error) { + hid := make(map[string]*hashids.HashID) + + for iss, data := range hidmap { + var err error + + hd := hashids.NewData() + hd.Salt = data.Salt + hd.MinLength = data.Length + + if data.Alphabet != "" { + hd.Alphabet = data.Alphabet + } + + hid[iss], err = hashids.NewWithData(hd) + if err != nil { + return nil, fmt.Errorf("cannot create hashid enc/dec %w", err) + } + } + + return hid, nil +} diff --git a/internal/topics/manager_test.go b/internal/topics/manager_test.go new file mode 100644 index 00000000..dd390f47 --- /dev/null +++ b/internal/topics/manager_test.go @@ -0,0 +1,163 @@ +package topics_test + +import ( + "testing" + + "github.com/snapp-incubator/soteria/internal/config" + "github.com/snapp-incubator/soteria/internal/topics" + "go.uber.org/zap" +) + +// nolint: funlen +func TestTopicManager(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + arg string + issuer string + want string + }{ + { + name: "testing cab event", + arg: "passenger-event-152384980615c2bd16143cff29038b67", + issuer: topics.PassengerIss, + want: topics.CabEvent, + }, + { + name: "testing cab event", + arg: "driver-event-152384980615c2bd16143cff29038b67", + issuer: topics.DriverIss, + want: topics.CabEvent, + }, + { + name: "testing invalid event", + arg: "-event-123456789abcdefgABCDEFG", + issuer: topics.NoneIss, + want: "", + }, + { + name: "testing driver location", + arg: "snapp/driver/DXKgaNQa7N5Y7bo/location", + issuer: topics.DriverIss, + want: topics.DriverLocation, + }, + { + name: "testing passenger location", + arg: "snapp/passenger/DXKgaNQa7N5Y7bo/location", + issuer: topics.PassengerIss, + want: topics.PassengerLocation, + }, + { + name: "testing invalid location", + arg: "snapp/thirdparty/DXKgaNQa7N5Y7bo/location", + issuer: topics.NoneIss, + want: "", + }, + { + name: "testing superapp event", + arg: "snapp/passenger/DXKgaNQa7N5Y7bo/superapp", + issuer: topics.PassengerIss, + want: topics.SuperappEvent, + }, + { + name: "testing shared passenger location", + arg: "snapp/passenger/DXKgaNQa7N5Y7bo/driver-location", + issuer: topics.PassengerIss, + want: topics.SharedLocation, + }, + { + name: "testing shared driver location", + arg: "snapp/driver/DXKgaNQa7N5Y7bo/passenger-location", + issuer: topics.DriverIss, + want: topics.SharedLocation, + }, + { + name: "testing passenger chat", + arg: "snapp/passenger/DXKgaNQa7N5Y7bo/chat", + issuer: topics.PassengerIss, + want: topics.Chat, + }, + { + name: "testing driver chat", + arg: "snapp/driver/DXKgaNQa7N5Y7bo/chat", + issuer: topics.DriverIss, + want: topics.Chat, + }, + { + name: "testing passenger general call entry", + arg: "shared/snapp/passenger/DXKgaNQa7N5Y7bo/call/send", + issuer: topics.PassengerIss, + want: topics.GeneralCallEntry, + }, + { + name: "testing driver general call entry", + arg: "shared/snapp/driver/DXKgaNQa7N5Y7bo/call/send", + issuer: topics.DriverIss, + want: topics.GeneralCallEntry, + }, + { + name: "testing passenger node call entry", + arg: "snapp/passenger/DXKgaNQa7N5Y7bo/call/heliograph-0/send", + issuer: topics.PassengerIss, + want: topics.NodeCallEntry, + }, + { + name: "testing driver node call entry", + arg: "snapp/driver/DXKgaNQa7N5Y7bo/call/heliograph-1/send", + issuer: topics.DriverIss, + want: topics.NodeCallEntry, + }, + { + name: "testing passenger call", + arg: "snapp/passenger/DXKgaNQa7N5Y7bo/call/receive", + issuer: topics.PassengerIss, + want: topics.CallOutgoing, + }, + { + name: "testing driver call", + arg: "snapp/driver/DXKgaNQa7N5Y7bo/call/receive", + issuer: topics.DriverIss, + want: topics.CallOutgoing, + }, + { + name: "testing box event", + arg: "bucks", + issuer: topics.DriverIss, + want: topics.BoxEvent, + }, + } + + cfg := config.SnappVendor() + + hid, err := topics.NewHashIDManager(cfg.HashIDMap) + if err != nil { + t.Errorf("invalid default hash-id: %s", err) + } + + // nolint: exhaustruct + topicManager := topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()) + + sub := "DXKgaNQa7N5Y7bo" + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + topic := tc.arg + + topicTemplate := topicManager.ParseTopic(topic, tc.issuer, sub, nil) + if topicTemplate != nil { + if len(tc.want) == 0 { + t.Errorf("topic %s is invalid, must throw error.", tc.arg) + } else if topicTemplate.Type != tc.want { + t.Errorf("GetType() = %v, want %v", topicTemplate.Type, tc.want) + } + } else { + if len(tc.want) != 0 { + t.Errorf("failed to find topicTemplate for %s", tc.arg) + } + } + }) + } +} diff --git a/internal/topics/t.go b/internal/topics/t.go deleted file mode 100644 index 8db908a5..00000000 --- a/internal/topics/t.go +++ /dev/null @@ -1,64 +0,0 @@ -package topics - -import ( - "regexp" - "strings" -) - -type Topic string - -type Type string - -const ( - CabEvent Type = "cab_event" - DriverLocation Type = "driver_location" - PassengerLocation Type = "passenger_location" - SuperappEvent Type = "superapp_event" - BoxEvent Type = "box_event" - DaghighSys Type = "daghigh_sys" - SharedLocation Type = "shared_location" - Chat Type = "chat" -) - -// Topic regular expressions which are used for detecting the topic name. -// topics are prefix with the company name will be trimed before matching -// so they regular expressions should not contain the company prefix. -var ( - CabEventRegexp = regexp.MustCompile(`(\w+)-event-[a-zA-Z0-9]+`) - DriverLocationRegexp = regexp.MustCompile(`/driver/[a-zA-Z0-9+]+/location`) - PassengerLocationRegexp = regexp.MustCompile(`/passenger/[a-zA-Z0-9+]+/location`) - SuperappEventRegexp = regexp.MustCompile(`/(driver|passenger)/[a-zA-Z0-9]+/(superapp)`) - SharedLocationRegexp = regexp.MustCompile(`/(driver|passenger)+/[a-zA-Z0-9]+/(driver-location|passenger-location)`) - DaghighSysRegexp = regexp.MustCompile(`\$SYS/brokers/\+/clients/\+/(connected|disconnected)`) - ChatRegexp = regexp.MustCompile(`/(driver|passenger)+/[a-zA-Z0-9]+/chat`) -) - -func (t Topic) GetType() Type { - return t.GetTypeWithCompany("snapp") -} - -func (t Topic) GetTypeWithCompany(company string) Type { - topic := string(t) - topic = strings.TrimPrefix(topic, company) - - switch { - case CabEventRegexp.MatchString(topic): - return CabEvent - case DriverLocationRegexp.MatchString(topic): - return DriverLocation - case PassengerLocationRegexp.MatchString(topic): - return PassengerLocation - case SharedLocationRegexp.MatchString(topic): - return SharedLocation - case ChatRegexp.MatchString(topic): - return Chat - case SuperappEventRegexp.MatchString(topic): - return SuperappEvent - case topic == "bucks": - return BoxEvent - case DaghighSysRegexp.MatchString(topic): - return DaghighSys - } - - return "" -} diff --git a/internal/topics/t_test.go b/internal/topics/t_test.go deleted file mode 100644 index c37f25e7..00000000 --- a/internal/topics/t_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package topics - -import ( - "fmt" - "testing" -) - -func TestTopic_GetType(t1 *testing.T) { - tests := []struct { - name string - arg Topic - want Type - }{ - { - name: "testing cab event", - arg: Topic("passenger-event-123456789abcdefgABCDEFG"), - want: CabEvent, - }, - { - name: "testing cab event", - arg: "driver-event-123456789abcdefgABCDEFG", - want: CabEvent, - }, - { - name: "testing invalid event", - arg: "-event-123456789abcdefgABCDEFG", - want: "", - }, - { - name: "testing driver location", - arg: "snapp/driver/sfhsdkifs475sfhs/location", - want: DriverLocation, - }, - { - name: "testing passenger location", - arg: "snapp/passenger/sfhsdkifs475sfhs/location", - want: PassengerLocation, - }, - { - name: "testing invalid location", - arg: "snapp/thirdparty/sfhsdkifs475sfhs/location", - want: "", - }, - { - name: "testing superapp event", - arg: "snapp/passenger/fhdyfuiksdf5456456adljada/superapp", - want: SuperappEvent, - }, - { - name: "testing superapp event", - arg: "snapp/driver/+/location", - want: DriverLocation, - }, - { - name: "testing daghigh sys", - arg: "$SYS/brokers/+/clients/+/disconnected", - want: DaghighSys, - }, - { - name: "testing daghigh sys", - arg: "$SYS/brokers/+/clients/+/connected", - want: DaghighSys, - }, - { - name: "testing daghigh sys", - arg: "$share/hello/$SYS/brokers/+/clients/+/connected", - want: DaghighSys, - }, - { - name: "testing shared passenger location", - arg: "snapp/passenger/py9kdjLYB35RP4q/driver-location", - want: SharedLocation, - }, - { - name: "testing shared driver location", - arg: "snapp/driver/py9kdjLYB35RP4q/passenger-location", - want: SharedLocation, - }, - { - name: "testing passenger chat", - arg: "snapp/passenger/py9kdjLYB35RP4q/chat", - want: Chat, - }, - { - name: "testing driver chat", - arg: "snapp/driver/py9kdjLYB35RP4q/chat", - want: Chat, - }, - } - for i, tt := range tests { - t1.Run(fmt.Sprintf("#%d %s", i, tt.name), func(t1 *testing.T) { - t := tt.arg - if got := t.GetType(); got != tt.want { - t1.Errorf("GetType() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/topics/topic.go b/internal/topics/topic.go new file mode 100644 index 00000000..22ea5e36 --- /dev/null +++ b/internal/topics/topic.go @@ -0,0 +1,37 @@ +package topics + +import ( + "strings" + "text/template" + + "github.com/snapp-incubator/soteria/pkg/acl" +) + +type Topic struct { + Type string `json:"type,omitempty" koanf:"type"` + Template string `json:"template,omitempty" koanf:"template"` + Accesses map[string]acl.AccessType `json:"accesses,omitempty" koanf:"accesses"` +} + +type Template struct { + Type string + Template *template.Template + Accesses map[string]acl.AccessType +} + +func (t Template) Parse(fields map[string]string) string { + writer := new(strings.Builder) + + if err := t.Template.Execute(writer, fields); err != nil { + return "" + } + + return writer.String() +} + +// HasAccess check if user has access on topic. +func (t Template) HasAccess(iss string, accessType acl.AccessType) bool { + access := t.Accesses[iss] + + return access == acl.PubSub || access == accessType +} diff --git a/internal/topics/topic_test.go b/internal/topics/topic_test.go new file mode 100644 index 00000000..d4c36243 --- /dev/null +++ b/internal/topics/topic_test.go @@ -0,0 +1,37 @@ +package topics_test + +import ( + "testing" + "text/template" + + "github.com/snapp-incubator/soteria/internal/topics" + "github.com/snapp-incubator/soteria/pkg/acl" + "github.com/stretchr/testify/require" +) + +func TestTopic(t *testing.T) { + t.Parallel() + + require := require.New(t) + + topic := topics.Topic{ + Type: topics.CabEvent, + Template: "^{{.iss}}-event-$", + Accesses: map[string]acl.AccessType{ + topics.DriverIss: acl.Sub, + topics.PassengerIss: acl.Sub, + }, + } + + temp := topics.Template{ + Type: topic.Type, + Template: template.Must(template.New("").Parse(topic.Template)), + Accesses: topic.Accesses, + } + + s := temp.Parse(map[string]string{ + "iss": "passenger", + }) + + require.Equal("^passenger-event-$", s) +} diff --git a/internal/tracing/config.go b/internal/tracing/config.go new file mode 100644 index 00000000..0552396d --- /dev/null +++ b/internal/tracing/config.go @@ -0,0 +1,7 @@ +package tracing + +type Config struct { + Enabled bool `json:"enabled,omitempty" koanf:"enabled"` + Endpoint string `json:"endpoint,omitempty" koanf:"endpoint"` + Ratio float64 `json:"ratio,omitempty" koanf:"ratio"` +} diff --git a/internal/tracing/tracer.go b/internal/tracing/tracer.go new file mode 100644 index 00000000..a009cc37 --- /dev/null +++ b/internal/tracing/tracer.go @@ -0,0 +1,58 @@ +package tracing + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "go.uber.org/zap" +) + +func New(cfg Config, logger *zap.Logger) trace.Tracer { + if !cfg.Enabled { + return noop.NewTracerProvider().Tracer("snapp.dispatching") + } + + exporter, err := otlptracegrpc.New( + context.Background(), + otlptracegrpc.WithEndpoint(cfg.Endpoint), otlptracegrpc.WithInsecure(), + ) + if err != nil { + logger.Fatal("failed to initialize export pipeline for traces (otlp with grpc)", zap.Error(err)) + } + + res, err := resource.Merge( + resource.Default(), + resource.NewSchemaless( + semconv.ServiceNamespaceKey.String("snapp.dispatching"), + semconv.ServiceNameKey.String("soteria"), + ), + ) + if err != nil { + panic(err) + } + + bsp := sdktrace.NewBatchSpanProcessor(exporter) + tp := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(cfg.Ratio))), + sdktrace.WithSpanProcessor(bsp), + sdktrace.WithResource(res), + ) + + otel.SetTracerProvider(tp) + + // register the TraceContext propagator globally. + var tc propagation.TraceContext + + otel.SetTextMapPropagator(tc) + + tracer := otel.Tracer("dispatching/soteria") + + return tracer +} diff --git a/internal/web/grpc/contracts/auth.pb.go b/internal/web/grpc/contracts/auth.pb.go deleted file mode 100644 index cffa4e88..00000000 --- a/internal/web/grpc/contracts/auth.pb.go +++ /dev/null @@ -1,402 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: auth.proto - -package contracts - -import ( - context "context" - fmt "fmt" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type AuthContract struct { - ServiceName string `protobuf:"bytes,1,opt,name=ServiceName,proto3" json:"ServiceName,omitempty"` - IPAddress string `protobuf:"bytes,2,opt,name=IPAddress,proto3" json:"IPAddress,omitempty"` - Endpoint string `protobuf:"bytes,3,opt,name=Endpoint,proto3" json:"Endpoint,omitempty"` - Username string `protobuf:"bytes,4,opt,name=Username,proto3" json:"Username,omitempty"` - Password string `protobuf:"bytes,5,opt,name=Password,proto3" json:"Password,omitempty"` - Token string `protobuf:"bytes,6,opt,name=Token,proto3" json:"Token,omitempty"` - Topic string `protobuf:"bytes,7,opt,name=Topic,proto3" json:"Topic,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *AuthContract) Reset() { *m = AuthContract{} } -func (m *AuthContract) String() string { return proto.CompactTextString(m) } -func (*AuthContract) ProtoMessage() {} -func (*AuthContract) Descriptor() ([]byte, []int) { - return fileDescriptor_8bbd6f3875b0e874, []int{0} -} - -func (m *AuthContract) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_AuthContract.Unmarshal(m, b) -} -func (m *AuthContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_AuthContract.Marshal(b, m, deterministic) -} -func (m *AuthContract) XXX_Merge(src proto.Message) { - xxx_messageInfo_AuthContract.Merge(m, src) -} -func (m *AuthContract) XXX_Size() int { - return xxx_messageInfo_AuthContract.Size(m) -} -func (m *AuthContract) XXX_DiscardUnknown() { - xxx_messageInfo_AuthContract.DiscardUnknown(m) -} - -var xxx_messageInfo_AuthContract proto.InternalMessageInfo - -func (m *AuthContract) GetServiceName() string { - if m != nil { - return m.ServiceName - } - return "" -} - -func (m *AuthContract) GetIPAddress() string { - if m != nil { - return m.IPAddress - } - return "" -} - -func (m *AuthContract) GetEndpoint() string { - if m != nil { - return m.Endpoint - } - return "" -} - -func (m *AuthContract) GetUsername() string { - if m != nil { - return m.Username - } - return "" -} - -func (m *AuthContract) GetPassword() string { - if m != nil { - return m.Password - } - return "" -} - -func (m *AuthContract) GetToken() string { - if m != nil { - return m.Token - } - return "" -} - -func (m *AuthContract) GetTopic() string { - if m != nil { - return m.Topic - } - return "" -} - -type ServiceResponse struct { - Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *ServiceResponse) Reset() { *m = ServiceResponse{} } -func (m *ServiceResponse) String() string { return proto.CompactTextString(m) } -func (*ServiceResponse) ProtoMessage() {} -func (*ServiceResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_8bbd6f3875b0e874, []int{1} -} - -func (m *ServiceResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ServiceResponse.Unmarshal(m, b) -} -func (m *ServiceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ServiceResponse.Marshal(b, m, deterministic) -} -func (m *ServiceResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_ServiceResponse.Merge(m, src) -} -func (m *ServiceResponse) XXX_Size() int { - return xxx_messageInfo_ServiceResponse.Size(m) -} -func (m *ServiceResponse) XXX_DiscardUnknown() { - xxx_messageInfo_ServiceResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_ServiceResponse proto.InternalMessageInfo - -func (m *ServiceResponse) GetCode() int32 { - if m != nil { - return m.Code - } - return 0 -} - -type GetTokenContract struct { - GrantType string `protobuf:"bytes,1,opt,name=GrantType,proto3" json:"GrantType,omitempty"` - ClientID string `protobuf:"bytes,2,opt,name=ClientID,proto3" json:"ClientID,omitempty"` - ClientSecret string `protobuf:"bytes,3,opt,name=ClientSecret,proto3" json:"ClientSecret,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *GetTokenContract) Reset() { *m = GetTokenContract{} } -func (m *GetTokenContract) String() string { return proto.CompactTextString(m) } -func (*GetTokenContract) ProtoMessage() {} -func (*GetTokenContract) Descriptor() ([]byte, []int) { - return fileDescriptor_8bbd6f3875b0e874, []int{2} -} - -func (m *GetTokenContract) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetTokenContract.Unmarshal(m, b) -} -func (m *GetTokenContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetTokenContract.Marshal(b, m, deterministic) -} -func (m *GetTokenContract) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetTokenContract.Merge(m, src) -} -func (m *GetTokenContract) XXX_Size() int { - return xxx_messageInfo_GetTokenContract.Size(m) -} -func (m *GetTokenContract) XXX_DiscardUnknown() { - xxx_messageInfo_GetTokenContract.DiscardUnknown(m) -} - -var xxx_messageInfo_GetTokenContract proto.InternalMessageInfo - -func (m *GetTokenContract) GetGrantType() string { - if m != nil { - return m.GrantType - } - return "" -} - -func (m *GetTokenContract) GetClientID() string { - if m != nil { - return m.ClientID - } - return "" -} - -func (m *GetTokenContract) GetClientSecret() string { - if m != nil { - return m.ClientSecret - } - return "" -} - -type GetTokenResponse struct { - Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` - Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *GetTokenResponse) Reset() { *m = GetTokenResponse{} } -func (m *GetTokenResponse) String() string { return proto.CompactTextString(m) } -func (*GetTokenResponse) ProtoMessage() {} -func (*GetTokenResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_8bbd6f3875b0e874, []int{3} -} - -func (m *GetTokenResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_GetTokenResponse.Unmarshal(m, b) -} -func (m *GetTokenResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_GetTokenResponse.Marshal(b, m, deterministic) -} -func (m *GetTokenResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_GetTokenResponse.Merge(m, src) -} -func (m *GetTokenResponse) XXX_Size() int { - return xxx_messageInfo_GetTokenResponse.Size(m) -} -func (m *GetTokenResponse) XXX_DiscardUnknown() { - xxx_messageInfo_GetTokenResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_GetTokenResponse proto.InternalMessageInfo - -func (m *GetTokenResponse) GetCode() int32 { - if m != nil { - return m.Code - } - return 0 -} - -func (m *GetTokenResponse) GetToken() string { - if m != nil { - return m.Token - } - return "" -} - -func init() { - proto.RegisterType((*AuthContract)(nil), "contracts.AuthContract") - proto.RegisterType((*ServiceResponse)(nil), "contracts.ServiceResponse") - proto.RegisterType((*GetTokenContract)(nil), "contracts.GetTokenContract") - proto.RegisterType((*GetTokenResponse)(nil), "contracts.GetTokenResponse") -} - -func init() { proto.RegisterFile("auth.proto", fileDescriptor_8bbd6f3875b0e874) } - -var fileDescriptor_8bbd6f3875b0e874 = []byte{ - // 312 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xd1, 0x4a, 0xfb, 0x30, - 0x14, 0xc6, 0xff, 0xdd, 0x7f, 0x9b, 0xeb, 0x71, 0xa0, 0x04, 0xc1, 0x50, 0x77, 0x31, 0x02, 0x82, - 0x57, 0xbb, 0xd0, 0x5b, 0xbd, 0x18, 0x55, 0xc7, 0x6e, 0x64, 0x74, 0xf3, 0x01, 0x6a, 0x7a, 0x60, - 0x45, 0x4d, 0x42, 0x92, 0x29, 0x3e, 0x84, 0xaf, 0xe6, 0x33, 0x49, 0x93, 0xa6, 0xad, 0x43, 0xbc, - 0x6a, 0xbe, 0xef, 0x57, 0x4e, 0xbf, 0x2f, 0xa7, 0x00, 0xf9, 0xce, 0x6e, 0x67, 0x4a, 0x4b, 0x2b, - 0x49, 0xcc, 0xa5, 0xb0, 0x3a, 0xe7, 0xd6, 0xb0, 0xaf, 0x08, 0xc6, 0xf3, 0x9d, 0xdd, 0xa6, 0xb5, - 0x43, 0xa6, 0x70, 0xb8, 0x46, 0xfd, 0x56, 0x72, 0x7c, 0xc8, 0x5f, 0x91, 0x46, 0xd3, 0xe8, 0x22, - 0xce, 0xba, 0x16, 0x99, 0x40, 0xbc, 0x5c, 0xcd, 0x8b, 0x42, 0xa3, 0x31, 0xb4, 0xe7, 0x78, 0x6b, - 0x90, 0x04, 0x46, 0x77, 0xa2, 0x50, 0xb2, 0x14, 0x96, 0xfe, 0x77, 0xb0, 0xd1, 0x15, 0x7b, 0x34, - 0xa8, 0x45, 0x35, 0xb8, 0xef, 0x59, 0xd0, 0x15, 0x5b, 0xe5, 0xc6, 0xbc, 0x4b, 0x5d, 0xd0, 0x81, - 0x67, 0x41, 0x93, 0x13, 0x18, 0x6c, 0xe4, 0x33, 0x0a, 0x3a, 0x74, 0xc0, 0x0b, 0xef, 0xaa, 0x92, - 0xd3, 0x83, 0xe0, 0xaa, 0x92, 0xb3, 0x73, 0x38, 0xaa, 0xc3, 0x66, 0x68, 0x94, 0x14, 0x06, 0x09, - 0x81, 0x7e, 0x2a, 0x0b, 0xdf, 0x65, 0x90, 0xb9, 0x33, 0x53, 0x70, 0xbc, 0x40, 0xeb, 0x06, 0x35, - 0xd5, 0x27, 0x10, 0x2f, 0x74, 0x2e, 0xec, 0xe6, 0x43, 0x85, 0xe2, 0xad, 0x51, 0x05, 0x4c, 0x5f, - 0x4a, 0x14, 0x76, 0x79, 0x5b, 0xb7, 0x6e, 0x34, 0x61, 0x30, 0xf6, 0xe7, 0x35, 0x72, 0x8d, 0xa1, - 0xf8, 0x0f, 0x8f, 0x5d, 0xb7, 0x5f, 0xfc, 0x2b, 0x59, 0x5b, 0xb6, 0xd7, 0x29, 0x7b, 0xf9, 0x19, - 0x41, 0xbf, 0xda, 0x13, 0xb9, 0xa9, 0x9f, 0xa7, 0xb3, 0x66, 0x89, 0xb3, 0xee, 0x02, 0x93, 0xa4, - 0x03, 0xf6, 0x6e, 0x82, 0xfd, 0x23, 0xf7, 0x30, 0x0a, 0x29, 0xc8, 0x59, 0xe7, 0xcd, 0xfd, 0xcb, - 0x48, 0x7e, 0x83, 0xed, 0x9c, 0xa7, 0xa1, 0xfb, 0x93, 0xae, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, - 0x00, 0x55, 0xf1, 0x67, 0x57, 0x02, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// AuthClient is the client API for Auth service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type AuthClient interface { - Auth(ctx context.Context, in *AuthContract, opts ...grpc.CallOption) (*ServiceResponse, error) - GetToken(ctx context.Context, in *GetTokenContract, opts ...grpc.CallOption) (*GetTokenResponse, error) -} - -type authClient struct { - cc *grpc.ClientConn -} - -func NewAuthClient(cc *grpc.ClientConn) AuthClient { - return &authClient{cc} -} - -func (c *authClient) Auth(ctx context.Context, in *AuthContract, opts ...grpc.CallOption) (*ServiceResponse, error) { - out := new(ServiceResponse) - err := c.cc.Invoke(ctx, "/contracts.Auth/Auth", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *authClient) GetToken(ctx context.Context, in *GetTokenContract, opts ...grpc.CallOption) (*GetTokenResponse, error) { - out := new(GetTokenResponse) - err := c.cc.Invoke(ctx, "/contracts.Auth/GetToken", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// AuthServer is the server API for Auth service. -type AuthServer interface { - Auth(context.Context, *AuthContract) (*ServiceResponse, error) - GetToken(context.Context, *GetTokenContract) (*GetTokenResponse, error) -} - -// UnimplementedAuthServer can be embedded to have forward compatible implementations. -type UnimplementedAuthServer struct { -} - -func (*UnimplementedAuthServer) Auth(ctx context.Context, req *AuthContract) (*ServiceResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Auth not implemented") -} -func (*UnimplementedAuthServer) GetToken(ctx context.Context, req *GetTokenContract) (*GetTokenResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetToken not implemented") -} - -func RegisterAuthServer(s *grpc.Server, srv AuthServer) { - s.RegisterService(&_Auth_serviceDesc, srv) -} - -func _Auth_Auth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AuthContract) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthServer).Auth(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/contracts.Auth/Auth", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServer).Auth(ctx, req.(*AuthContract)) - } - return interceptor(ctx, in, info, handler) -} - -func _Auth_GetToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetTokenContract) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AuthServer).GetToken(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/contracts.Auth/GetToken", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AuthServer).GetToken(ctx, req.(*GetTokenContract)) - } - return interceptor(ctx, in, info, handler) -} - -var _Auth_serviceDesc = grpc.ServiceDesc{ - ServiceName: "contracts.Auth", - HandlerType: (*AuthServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Auth", - Handler: _Auth_Auth_Handler, - }, - { - MethodName: "GetToken", - Handler: _Auth_GetToken_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "auth.proto", -} diff --git a/internal/web/grpc/contracts/auth.proto b/internal/web/grpc/contracts/auth.proto deleted file mode 100644 index 43e0ecae..00000000 --- a/internal/web/grpc/contracts/auth.proto +++ /dev/null @@ -1,33 +0,0 @@ -syntax = "proto3"; -option go_package = "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/grpc/contracts"; -package contracts; - -message AuthContract { - string ServiceName = 1; - string IPAddress = 2; - string Endpoint = 3; - string Username = 4; - string Password = 5; - string Token = 6; - string Topic = 7; -} - -message ServiceResponse { - int32 Code = 1; -} - -message GetTokenContract { - string GrantType = 1; - string ClientID = 2; - string ClientSecret = 3; -} - -message GetTokenResponse { - int32 Code = 1; - string Token = 2; -} - -service Auth { - rpc Auth(AuthContract) returns (ServiceResponse) {} - rpc GetToken(GetTokenContract) returns (GetTokenResponse) {} -} diff --git a/internal/web/grpc/server.go b/internal/web/grpc/server.go deleted file mode 100644 index 0f816f2e..00000000 --- a/internal/web/grpc/server.go +++ /dev/null @@ -1,133 +0,0 @@ -package grpc - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "gitlab.snapp.ir/dispatching/soteria/v3/internal" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/grpc/contracts" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "go.uber.org/zap" - "google.golang.org/grpc" -) - -type Server struct { - contracts.AuthContract -} - -func (s *Server) Auth(ctx context.Context, in *contracts.AuthContract) (*contracts.ServiceResponse, error) { - start := time.Now() - - username := in.GetUsername() - password := in.GetPassword() - endpoint := in.GetEndpoint() - ip := in.GetIPAddress() - zap.L().Debug("grpc auth call", - zap.String("username", username), - zap.String("endpoint", endpoint), - zap.String("ip", ip), - ) - - var ok bool - var err error - if len(password) > 0 { - ok, err = app.GetInstance().Authenticator.EndPointBasicAuth(ctx, username, password, endpoint) - } else if len(ip) > 0 { - ok, err = app.GetInstance().Authenticator.EndpointIPAuth(ctx, username, ip, endpoint) - } else { - ok = false - err = fmt.Errorf("both password and ip address are empty") - } - - if !ok { - if errors.Is(err, db.ErrDb) { - app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Auth, http.StatusInternalServerError) - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Auth, internal.Failure, "database error happened") - app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Auth, float64(time.Since(start).Nanoseconds())) - - zap.L().Error("grpc auth returned", zap.Int("code", http.StatusInternalServerError), zap.Error(err)) - return &contracts.ServiceResponse{Code: http.StatusInternalServerError}, fmt.Errorf("internal server error") - } - - app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Auth, http.StatusUnauthorized) - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Auth, internal.Failure, "request is not authorized") - app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Auth, float64(time.Since(start).Nanoseconds())) - - zap.L().Error("grpc auth returned", zap.Int("code", http.StatusUnauthorized), zap.Error(err)) - return &contracts.ServiceResponse{Code: http.StatusUnauthorized}, fmt.Errorf("request is unauthorized") - } - - zap.L().Info("grpc auth returned", zap.Int("code", http.StatusOK)) - - app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Auth, http.StatusOK) - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Auth, internal.Success, "ok") - app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Auth, float64(time.Since(start).Nanoseconds())) - - return &contracts.ServiceResponse{Code: http.StatusOK}, nil -} - -func (s *Server) GetToken(ctx context.Context, in *contracts.GetTokenContract) (*contracts.GetTokenResponse, error) { - start := time.Now() - - grantType := in.GetGrantType() - clientID := in.GetClientID() - clientSecret := in.GetClientSecret() - zap.L().Debug("grpc token call", - zap.String("grant_type", grantType), - zap.String("client_id", clientID), - zap.String("client_secret", clientSecret), - ) - - tokenString, err := app.GetInstance().Authenticator.Token(ctx, acl.AccessType(grantType), clientID, clientSecret) - if err != nil { - if errors.Is(err, db.ErrDb) { - app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Token, http.StatusInternalServerError) - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, "database error happened") - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, clientID) - app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Token, float64(time.Since(start).Nanoseconds())) - - return &contracts.GetTokenResponse{ - Code: http.StatusInternalServerError, - Token: "", - }, fmt.Errorf("internal server error") - } - - app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Token, http.StatusUnauthorized) - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, "request is not authorized") - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, clientID) - app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Token, float64(time.Since(start).Nanoseconds())) - - return &contracts.GetTokenResponse{ - Code: http.StatusUnauthorized, - Token: "", - }, fmt.Errorf("request is unauthorized") - } - - zap.L(). - Info("token request accepted", - zap.String("grant_type", grantType), - zap.String("client_id", clientID), - zap.String("client_secret", clientSecret), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Token, http.StatusOK) - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Success, "token request accepted") - app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Success, clientID) - app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Token, float64(time.Since(start).Nanoseconds())) - - return &contracts.GetTokenResponse{ - Code: 200, - Token: tokenString, - }, nil -} - -func NewServer() *grpc.Server { - s := grpc.NewServer() - contracts.RegisterAuthServer(s, &Server{}) - return s -} diff --git a/internal/web/rest/api/accounts.go b/internal/web/rest/api/accounts.go deleted file mode 100644 index bc3e2503..00000000 --- a/internal/web/rest/api/accounts.go +++ /dev/null @@ -1,218 +0,0 @@ -package api - -import ( - "fmt" - "time" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - accountsInfo "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" -) - -// Response is the response structure of the REST API. -type Response struct { - Code accountsInfo.Code `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data"` -} - -// CreateResponse returns a HTTP Status Code and a response. -func CreateResponse(code accountsInfo.Code, data interface{}, details ...string) (int, *Response) { - return code.HttpStatusCode(), &Response{ - Code: code, - Message: fmt.Sprintf("%s: %s", code.Message(), details), - Data: data, - } -} - -// createAccountPayload is the body payload structure of create account endpoint -type createAccountPayload struct { - Username string `json:"username" form:"username" binding:"required"` - Password string `json:"password" form:"password" binding:"required"` - UserType user.Type `json:"user_type" form:"user_type" binding:"required"` -} - -// CreateAccount is the handler of the create account endpoint -func CreateAccount(ctx *gin.Context) { - var p createAccountPayload - if err := ctx.ShouldBind(&p); err != nil { - ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error())) - return - } - - if err := app.GetInstance().AccountsService.SignUp(ctx, p.Username, p.Password, p.UserType); err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil)) -} - -// ReadAccount is the handler of the read account endpoint -func ReadAccount(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - password := ctx.MustGet("password").(string) - - u, err := app.GetInstance().AccountsService.Info(ctx, username, password) - if err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, u)) -} - -// updateAccountPayload is the body payload structure of update account endpoint -type updateAccountPayload struct { - NewPassword string `json:"new_password" form:"new_password"` - IPs []string `json:"ips" form:"ips"` - Secret string `json:"secret" form:"secret"` - Type user.Type `json:"type" form:"type"` - TokenExpiration time.Duration `json:"token_expiration" form:"token_expiration"` -} - -// UpdateAccount is the handler of the update account endpoint -func UpdateAccount(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - - var p updateAccountPayload - if err := ctx.ShouldBind(&p); err != nil { - ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error())) - return - } - - if err := app.GetInstance().AccountsService.Update(ctx, username, p.NewPassword, p.Type, p.IPs, p.Secret, p.TokenExpiration); err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil)) -} - -// DeleteAccount is the handler of the delete account endpoint -func DeleteAccount(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - - if err := app.GetInstance().AccountsService.Delete(ctx, username); err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil)) -} - -// createAccountRulePayload is the body payload structure of create account rule endpoint -type createAccountRulePayload struct { - Endpoint string `json:"endpoint" form:"endpoint"` - Topic topics.Type `json:"topic" form:"topic"` - AccessType acl.AccessType `json:"access_type" form:"access_type"` -} - -// CreateAccountRule is the handler of the create account rule endpoint -func CreateAccountRule(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - - var p createAccountRulePayload - if err := ctx.ShouldBind(&p); err != nil { - ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error())) - return - } - - r, err := app.GetInstance().AccountsService.CreateRule(ctx, username, p.Endpoint, p.Topic, p.AccessType) - if err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, r)) -} - -// ReadAccountRule is the handler of the read account rule endpoint -func ReadAccountRule(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - - plainUUID := ctx.Param("uuid") - if plainUUID == "" { - ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil)) - return - } - - ruleUUID, err := uuid.Parse(plainUUID) - if err != nil { - ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil, err.Error())) - return - } - - r, rErr := app.GetInstance().AccountsService.GetRule(ctx, username, ruleUUID) - if rErr != nil { - ctx.JSON(CreateResponse(rErr.Code, nil, rErr.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, r)) -} - -// updateAccountRulePayload is the body payload structure of update account rule endpoint -type updateAccountRulePayload struct { - Endpoint string `json:"endpoint" form:"endpoint"` - Topic topics.Type `json:"topic" form:"topic"` - AccessType acl.AccessType `json:"access_type" form:"access_type"` -} - -// UpdateAccountRule is the handler of the update account rule endpoint -func UpdateAccountRule(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - - var p updateAccountRulePayload - if err := ctx.ShouldBind(&p); err != nil { - ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error())) - return - } - - plainUUID := ctx.Param("uuid") - if plainUUID == "" { - ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil)) - return - } - - ruleUUID, err := uuid.Parse(plainUUID) - if err != nil { - ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil, err.Error())) - return - } - - if err := app.GetInstance().AccountsService.UpdateRule(ctx, username, ruleUUID, p.Endpoint, p.Topic, p.AccessType); err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil)) -} - -// DeleteAccountRule is the handler of the delete account rule endpoint -func DeleteAccountRule(ctx *gin.Context) { - username := ctx.MustGet("username").(string) - - plainUUID := ctx.Param("uuid") - if plainUUID == "" { - ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil)) - return - } - - ruleUUID, err := uuid.Parse(plainUUID) - if err != nil { - ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil, err.Error())) - return - } - - if err := app.GetInstance().AccountsService.DeleteRule(ctx, username, ruleUUID); err != nil { - ctx.JSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil)) -} diff --git a/internal/web/rest/api/accounts_test.go b/internal/web/rest/api/accounts_test.go deleted file mode 100644 index badb1dc5..00000000 --- a/internal/web/rest/api/accounts_test.go +++ /dev/null @@ -1,487 +0,0 @@ -package api - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis/v8" - "github.com/stretchr/testify/assert" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - redisModel "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" -) - -func init() { - mr, err := miniredis.Run() - if err != nil { - panic(err) - } - client := redis.NewClient(&redis.Options{ - Addr: mr.Addr(), - }) - app.GetInstance().SetAccountsService(&accounts.Service{ - Handler: redisModel.ModelHandler{ - Client: client, - }, - }) -} - -func TestCreateAccount(t *testing.T) { - router := setupRouter("debug") - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"username":"user","password":"123","user_type":"driver"}`) - req, _ := http.NewRequest("POST", "/accounts", bytes.NewBuffer(payload)) - req.Header.Set("Content-Type", "application/json") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - expectedResponse := Response{ - Code: errors.SuccessfulOperation, - Message: fmt.Sprintf("%s: []", errors.SuccessfulOperation.Message()), - Data: nil, - } - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, expectedResponse, actualResponse) - }) - - t.Run("testing without content type header", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"username":"user","password":"123","user_type":"driver"}`) - req, _ := http.NewRequest(http.MethodPost, "/accounts", bytes.NewBuffer(payload)) - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusBadRequest, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.BadRequestPayload, actualResponse.Code) - }) -} - -func TestReadAccount(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/accounts/user", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - u := actualResponse.Data.(map[string]interface{}) - assert.Equal(t, "user", u["username"]) - assert.Equal(t, "passenger", u["type"]) - }) -} - -func TestUpdateAccount(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"new_password":"password2","ips":["127.0.0.1"],"secret":"12345678","type":"EMQUser","token_expiration":1000000}`) - req, _ := http.NewRequest(http.MethodPut, "/accounts/user", bytes.NewBuffer(payload)) - req.Header.Set("Content-Type", "application/json") - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password2") - assert.Nil(t, err) - assert.Equal(t, 1, len(u.IPs)) - assert.Equal(t, "12345678", u.Secret) - assert.Equal(t, user.EMQUser, u.Type) - assert.Equal(t, time.Duration(1000000), u.TokenExpirationDuration) - }) -} - -func TestDeleteAccount(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest(http.MethodDelete, "/accounts/user", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - _, err = app.GetInstance().AccountsService.Info(context.Background(), "user", "password2") - assert.NotNil(t, err) - }) -} - -func TestCreateAccountRule(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - - t.Run("testing with invalid rule info", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"endpoint":"/notification","topic":"","access_type":""}`) - req, _ := http.NewRequest(http.MethodPost, "/accounts/user/rules", bytes.NewBuffer(payload)) - req.Header.Set("Content-Type", "application/json") - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusBadRequest, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.InvalidRule, actualResponse.Code) - - u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password") - assert.Nil(t, err) - assert.Equal(t, 0, len(u.Rules)) - }) - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"endpoint":"/notification","topic":"","access_type":"2"}`) - req, _ := http.NewRequest(http.MethodPost, "/accounts/user/rules", bytes.NewBuffer(payload)) - req.Header.Set("Content-Type", "application/json") - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password") - assert.Nil(t, err) - assert.Equal(t, 1, len(u.Rules)) - assert.Equal(t, "/notification", u.Rules[0].Endpoint) - assert.Equal(t, topics.Type(""), u.Rules[0].Topic) - assert.Equal(t, acl.Pub, u.Rules[0].AccessType) - }) -} - -func TestReadAccountRule(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - createdRule, _ := app.GetInstance().AccountsService.CreateRule(context.Background(), "user", "/notification", "", "2") - - t.Run("testing with invalid UUID", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/accounts/user/rules/invalid-uuid", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusBadRequest, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.InvalidRuleUUID, actualResponse.Code) - }) - - t.Run("testing with undefined rule", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusNotFound, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.RuleNotFound, actualResponse.Code) - }) - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/accounts/user/rules/%s", createdRule.UUID), nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - returnedRule := actualResponse.Data.(map[string]interface{}) - assert.Equal(t, createdRule.UUID.String(), returnedRule["uuid"]) - assert.Equal(t, "/notification", returnedRule["endpoint"]) - assert.Equal(t, "", returnedRule["topic"]) - assert.Equal(t, "", returnedRule["topic"]) - }) -} - -func TestUpdateAccountRule(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - createdRule, _ := app.GetInstance().AccountsService.CreateRule(context.Background(), "user", "/notification", "", "2") - - t.Run("testing with no payload", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodPut, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusBadRequest, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.BadRequestPayload, actualResponse.Code) - }) - - t.Run("testing with invalid UUID", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"endpoint":"/notification","topic":"","access_type":""}`) - req, _ := http.NewRequest(http.MethodPut, "/accounts/user/rules/invalid-uuid", bytes.NewBuffer(payload)) - req.SetBasicAuth("user", "password") - req.Header.Set("Content-Type", "application/json") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusBadRequest, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.InvalidRuleUUID, actualResponse.Code) - }) - - t.Run("testing with undefined rule", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"endpoint":"/notification","topic":"","access_type":"2"}`) - req, _ := http.NewRequest(http.MethodPut, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", bytes.NewBuffer(payload)) - req.SetBasicAuth("user", "password") - req.Header.Set("Content-Type", "application/json") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusNotFound, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.RuleNotFound, actualResponse.Code) - }) - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - payload := []byte(`{"endpoint":"","topic":"cab_event","access_type":"2"}`) - req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/accounts/user/rules/%s", createdRule.UUID), bytes.NewBuffer(payload)) - req.SetBasicAuth("user", "password") - req.Header.Set("Content-Type", "application/json") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password") - assert.Nil(t, err) - assert.Equal(t, 1, len(u.Rules)) - assert.Equal(t, createdRule.UUID, u.Rules[0].UUID) - assert.Equal(t, "", u.Rules[0].Endpoint) - assert.Equal(t, topics.CabEvent, u.Rules[0].Topic) - assert.Equal(t, acl.Pub, u.Rules[0].AccessType) - }) -} - -func TestDeleteAccountRule(t *testing.T) { - router := setupRouter("debug") - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - createdRule, _ := app.GetInstance().AccountsService.CreateRule(context.Background(), "user", "/notification", "", "2") - - t.Run("testing with invalid UUID", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodDelete, "/accounts/user/rules/invalid-uuid", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusBadRequest, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.InvalidRuleUUID, actualResponse.Code) - }) - - t.Run("testing with undefined rule", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodDelete, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusNotFound, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.RuleNotFound, actualResponse.Code) - }) - - t.Run("testing successful request", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("/accounts/user/rules/%s", createdRule.UUID), nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - resBody, err := ioutil.ReadAll(w.Body) - assert.NoError(t, err) - - var actualResponse Response - err = json.Unmarshal(resBody, &actualResponse) - assert.NoError(t, err) - - assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code) - - u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password") - assert.Nil(t, err) - assert.Equal(t, 0, len(u.Rules)) - }) -} diff --git a/internal/web/rest/api/acl.go b/internal/web/rest/api/acl.go deleted file mode 100644 index 763e7bec..00000000 --- a/internal/web/rest/api/acl.go +++ /dev/null @@ -1,142 +0,0 @@ -package api - -import ( - "errors" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/opentracing/opentracing-go" - "gitlab.snapp.ir/dispatching/soteria/v3/internal" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "go.uber.org/zap" -) - -// aclRequest is the body payload structure of the ACL endpoint. -type aclRequest struct { - Access acl.AccessType `form:"access"` - Token string `form:"token"` - Username string `from:"username"` - Password string `form:"password"` - Topic string `form:"topic"` -} - -// ACL is the handler responsible for ACL requests. -func ACL(ctx *gin.Context) { - aclSpan := app.GetInstance().Tracer.StartSpan("api.rest.acl") - defer aclSpan.Finish() - - s := time.Now() - request := &aclRequest{} - err := ctx.ShouldBind(request) - if err != nil { - - zap.L(). - Warn("acl bad request", - zap.Error(err), - zap.String("access", request.Access.String()), - zap.String("topic", request.Topic), - zap.String("token", request.Token), - zap.String("username", request.Password), - zap.String("password", request.Username), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusBadRequest) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "bad request") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusBadRequest, "bad request") - return - } - tokenString := request.Token - if len(request.Token) == 0 { - tokenString = request.Username - } - if len(tokenString) == 0 { - tokenString = request.Password - } - - topic := topics.Topic(request.Topic) - topicType := topic.GetType() - if len(topicType) == 0 { - zap.L(). - Warn("acl bad request", - zap.Error(err), - zap.String("access", request.Access.String()), - zap.String("topic", request.Topic), - zap.String("token", request.Token), - zap.String("username", request.Password), - zap.String("password", request.Username), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusBadRequest) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "bad request") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusBadRequest, "bad request") - return - } - - aclCheckSpan := app.GetInstance().Tracer.StartSpan("acl check", opentracing.ChildOf(aclSpan.Context())) - - ok, err := app.GetInstance().Authenticator.ACL(ctx, request.Access, tokenString, topic) - if err != nil || !ok { - aclCheckSpan.SetTag("success", false) - if err != nil { - aclCheckSpan.SetTag("error", err.Error()) - } - - // nolint: exhaustivestruct - if errors.Is(err, authenticator.ErrTopicNotAllowed{}) { - zap.L(). - Warn("acl request is not authorized", - zap.Error(err)) - } else { - zap.L(). - Error("acl request is not authorized", - zap.Error(err)) - } - - if errors.Is(err, db.ErrDb) { - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusInternalServerError) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, request.Access.String(), internal.Failure, string(topicType)) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "database error happened") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusInternalServerError, "internal server error") - - aclCheckSpan.Finish() - - return - } - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusUnauthorized) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, request.Access.String(), internal.Failure, string(topicType)) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "request is not authorized") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusUnauthorized, "request is not authorized") - - aclCheckSpan.Finish() - - return - } - - zap.L(). - Info("acl ok", - zap.String("access", request.Access.String()), - zap.String("topic", request.Topic), - zap.String("token", request.Token), - zap.String("username", request.Password), - zap.String("password", request.Username), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusOK) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Success, "ok") - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, request.Access.String(), internal.Success, string(topicType)) - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds())) - - aclCheckSpan.SetTag("success", true) - - ctx.String(http.StatusOK, "ok") -} diff --git a/internal/web/rest/api/api.go b/internal/web/rest/api/api.go deleted file mode 100644 index 9641cf73..00000000 --- a/internal/web/rest/api/api.go +++ /dev/null @@ -1,58 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "time" - - ginzap "github.com/gin-contrib/zap" - "github.com/gin-gonic/gin" - "github.com/prometheus/client_golang/prometheus/promhttp" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api/emq" - "go.uber.org/zap" -) - -// setupRouter will attach all routes needed for Soteria to gin's default router -func setupRouter(mode string) *gin.Engine { - gin.SetMode(mode) - - router := gin.New() - router.Use(gin.Recovery()) - - router.Use(ginzap.Ginzap(zap.L(), time.RFC3339, false)) - router.Use(ginzap.RecoveryWithZap(zap.L(), true)) - - a := router.Group("/accounts") - { - a.POST("", CreateAccount) - authorizedRoutes := a.Use(accountsBasicAuth()) - { - authorizedRoutes.GET("/:username", ReadAccount) - authorizedRoutes.PUT("/:username", UpdateAccount) - authorizedRoutes.DELETE("/:username", DeleteAccount) - - authorizedRoutes.POST("/:username/rules", CreateAccountRule) - authorizedRoutes.GET("/:username/rules/:uuid", ReadAccountRule) - authorizedRoutes.PUT("/:username/rules/:uuid", UpdateAccountRule) - authorizedRoutes.DELETE("/:username/rules/:uuid", DeleteAccountRule) - } - } - - emq.Register(router.Group("/emq")) - - router.POST("/auth", Auth) - router.POST("/acl", ACL) - router.POST("/token", Token) - - router.GET("/metrics", gin.WrapH(promhttp.Handler())) - - return router -} - -// RestServer will return an HTTP.Server with given port and gin mode -func RestServer(mode string, port int) *http.Server { - return &http.Server{ - Addr: fmt.Sprintf(":%d", port), - Handler: setupRouter(mode), - } -} diff --git a/internal/web/rest/api/auth.go b/internal/web/rest/api/auth.go deleted file mode 100644 index bc7fe499..00000000 --- a/internal/web/rest/api/auth.go +++ /dev/null @@ -1,110 +0,0 @@ -package api - -import ( - "github.com/gin-gonic/gin" - "github.com/opentracing/opentracing-go" - "gitlab.snapp.ir/dispatching/soteria/v3/internal" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "go.uber.org/zap" - "net/http" - "time" -) - -// authRequest is the body payload structure of the auth endpoint -type authRequest struct { - Token string `form:"token"` - Username string `from:"username"` - Password string `form:"password"` -} - -// Auth is the handler responsible for authentication -func Auth(ctx *gin.Context) { - authSpan := app.GetInstance().Tracer.StartSpan("api.rest.auth") - defer authSpan.Finish() - - s := time.Now() - request := &authRequest{} - err := ctx.ShouldBind(request) - if err != nil { - zap.L(). - Warn("bad request", - zap.Error(err), - ) - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusBadRequest) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Failure, "bad request") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusBadRequest, "bad request") - return - } - tokenString := request.Token - if len(tokenString) == 0 { - tokenString = request.Username - } - if len(tokenString) == 0 { - tokenString = request.Password - } - - authCheckSpan := app.GetInstance().Tracer.StartSpan("auth check", opentracing.ChildOf(authSpan.Context())) - - superuser, err := app.GetInstance().Authenticator.Auth(ctx, tokenString) - if err != nil { - authCheckSpan.SetTag("success", false) - authCheckSpan.SetTag("error", err.Error()) - - zap.L(). - Error("auth request is not authorized", - zap.Error(err), - zap.String("token", request.Token), - zap.String("username", request.Password), - zap.String("password", request.Username), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusUnauthorized) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Failure, "request is not authorized") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusUnauthorized, "request is not authorized") - - authCheckSpan.Finish() - - return - } - - if superuser == true { - authCheckSpan.SetTag("success", false) - authCheckSpan.SetTag("ignored", true) - - zap.L(). - Error("auth request is ignored", - zap.Error(err), - zap.String("token", request.Token), - zap.String("username", request.Password), - zap.String("password", request.Username), - zap.Bool("superuser", superuser), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusOK) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Ignore, "request is ignored") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds())) - - ctx.String(http.StatusOK, "ignore") - - authCheckSpan.Finish() - - return - } - - zap.L(). - Info("auth ok", - zap.String("token", request.Token), - zap.String("username", request.Password), - zap.String("password", request.Username), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusOK) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Success, "ok") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds())) - - authSpan.SetTag("success", true) - - ctx.String(http.StatusOK, "ok") -} diff --git a/internal/web/rest/api/emq/emq.go b/internal/web/rest/api/emq/emq.go deleted file mode 100644 index f3cd430d..00000000 --- a/internal/web/rest/api/emq/emq.go +++ /dev/null @@ -1,89 +0,0 @@ -package emq - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api/emq/request" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api/emq/response" -) - -func Register(group *gin.RouterGroup) { - group.POST("", Create) - group.GET("", List) -} - -// List returns the list of registered users for emqx-redis-auth. -func List(ctx *gin.Context) { - users, err := app.GetInstance().EMQStore.LoadAll(ctx) - if err != nil { - ctx.JSON(http.StatusInternalServerError, response.Response{ - Message: err.Error(), - Data: nil, - }) - - return - } - - ctx.JSON(http.StatusCreated, response.Response{ - Message: "success", - Data: users, - }) -} - -// Create is the handler of the create emq redis account endpoint. -func Create(ctx *gin.Context) { - var p request.Create - - if err := ctx.ShouldBind(&p); err != nil { - ctx.JSON(http.StatusBadRequest, response.Response{ - Message: err.Error(), - Data: nil, - }) - - return - } - - if err := p.Validate(); err != nil { - ctx.JSON(http.StatusBadRequest, response.Response{ - Message: err.Error(), - Data: nil, - }) - - return - } - - token, err := app.GetInstance().Authenticator.SuperuserToken(p.Username, time.Duration(p.Duration)) - if err != nil { - ctx.JSON(http.StatusInternalServerError, response.Response{ - Message: err.Error(), - Data: nil, - }) - - return - } - - if err := app.GetInstance().EMQStore.Save(ctx, emq.User{ - Username: token, - Password: p.Password, - IsSuperuser: true, - }); err != nil { - ctx.JSON(http.StatusInternalServerError, response.Response{ - Message: err.Error(), - Data: nil, - }) - - return - } - - ctx.JSON(http.StatusCreated, response.Response{ - Message: "success", - Data: response.Create{ - Token: token, - Password: p.Password, - }, - }) -} diff --git a/internal/web/rest/api/emq/request/create.go b/internal/web/rest/api/emq/request/create.go deleted file mode 100644 index 7d7b9b23..00000000 --- a/internal/web/rest/api/emq/request/create.go +++ /dev/null @@ -1,26 +0,0 @@ -package request - -import ( - "fmt" - - validation "github.com/go-ozzo/ozzo-validation/v4" -) - -// Create is the body payload structure of create emq endpoint. -type Create struct { - Password string `json:"password"` - Username string `json:"username"` - Duration int64 `json:"duration"` -} - -func (r Create) Validate() error { - if err := validation.ValidateStruct(&r, - validation.Field(&r.Password, validation.Required), - validation.Field(&r.Username, validation.Required), - validation.Field(&r.Duration, validation.Required), - ); err != nil { - return fmt.Errorf("create request validation failed: %w", err) - } - - return nil -} diff --git a/internal/web/rest/api/emq/response/create.go b/internal/web/rest/api/emq/response/create.go deleted file mode 100644 index 358ec09d..00000000 --- a/internal/web/rest/api/emq/response/create.go +++ /dev/null @@ -1,6 +0,0 @@ -package response - -type Create struct { - Token string - Password string -} diff --git a/internal/web/rest/api/emq/response/response.go b/internal/web/rest/api/emq/response/response.go deleted file mode 100644 index 46d17bc6..00000000 --- a/internal/web/rest/api/emq/response/response.go +++ /dev/null @@ -1,7 +0,0 @@ -package response - -// Response is the response structure of the REST API. -type Response struct { - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} diff --git a/internal/web/rest/api/middlewares.go b/internal/web/rest/api/middlewares.go deleted file mode 100644 index 9170a535..00000000 --- a/internal/web/rest/api/middlewares.go +++ /dev/null @@ -1,45 +0,0 @@ -package api - -import ( - "encoding/base64" - "github.com/gin-gonic/gin" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - accountsInfo "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors" - "strings" -) - -// accountsBasicAuth is the authentication middleware for the accounts API -func accountsBasicAuth() gin.HandlerFunc { - return func(ctx *gin.Context) { - auth := strings.SplitN(ctx.Request.Header.Get("Authorization"), " ", 2) - - if len(auth) != 2 || auth[0] != "Basic" { - ctx.AbortWithStatusJSON(CreateResponse(accountsInfo.WrongUsernameOrPassword, nil)) - return - } - - payload, _ := base64.StdEncoding.DecodeString(auth[1]) - pair := strings.SplitN(string(payload), ":", 2) - - if len(pair) != 2 { - ctx.AbortWithStatusJSON(CreateResponse(accountsInfo.WrongUsernameOrPassword, nil)) - return - } - - if pair[0] != ctx.Param("username") { - ctx.AbortWithStatusJSON(CreateResponse(accountsInfo.UsernameMismatch, nil)) - return - } - - _, err := app.GetInstance().AccountsService.Info(ctx, pair[0], pair[1]) - if err != nil { - ctx.AbortWithStatusJSON(CreateResponse(err.Code, nil, err.Message)) - return - } - - ctx.Set("username", pair[0]) - ctx.Set("password", pair[1]) - - ctx.Next() - } -} diff --git a/internal/web/rest/api/middlewares_test.go b/internal/web/rest/api/middlewares_test.go deleted file mode 100644 index 188383ed..00000000 --- a/internal/web/rest/api/middlewares_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package api - -import ( - "context" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "net/http" - "net/http/httptest" - "testing" -) - -func TestAccountsBasicAuth(t *testing.T) { - router := gin.Default() - router.Use(accountsBasicAuth()) - router.GET("/:username", func(ctx *gin.Context) { - ctx.String(http.StatusOK, "done") - return - }) - - _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger") - - t.Run("testing successful authentication", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/user", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - }) - - t.Run("testing without a Authorization header", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/user", nil) - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusUnauthorized, w.Code) - }) - - t.Run("testing with an invalid auth", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/user", nil) - req.Header.Set("Authorization", "Basic invalid") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusUnauthorized, w.Code) - }) - - t.Run("testing different username in path", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/user2", nil) - req.SetBasicAuth("user", "password") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusUnauthorized, w.Code) - }) - - t.Run("testing with wrong password", func(t *testing.T) { - w := httptest.NewRecorder() - - req, _ := http.NewRequest(http.MethodGet, "/user", nil) - req.SetBasicAuth("user", "password2") - - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusUnauthorized, w.Code) - }) -} diff --git a/internal/web/rest/api/token.go b/internal/web/rest/api/token.go deleted file mode 100644 index 918895fd..00000000 --- a/internal/web/rest/api/token.go +++ /dev/null @@ -1,97 +0,0 @@ -package api - -import ( - "errors" - "github.com/gin-gonic/gin" - "github.com/opentracing/opentracing-go" - "gitlab.snapp.ir/dispatching/soteria/v3/internal" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/app" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" - "go.uber.org/zap" - "net/http" - "time" -) - -// TokenRequest is the body payload structure of the token endpoint -type TokenRequest struct { - GrantType acl.AccessType `json:"grant_type" form:"grant_type" query:"grant_type"` - ClientID string `json:"client_id" form:"client_id" query:"client_id"` - ClientSecret string `json:"client_secret" form:"client_secret" query:"client_secret"` -} - -// Token is the handler responsible for Token requests -func Token(ctx *gin.Context) { - tokenSpan := app.GetInstance().Tracer.StartSpan("api.rest.token") - defer tokenSpan.Finish() - - s := time.Now() - request := &TokenRequest{} - err := ctx.Bind(request) - if err != nil { - zap.L(). - Warn("bad request", - zap.Error(err), - ) - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusBadRequest) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, "bad request") - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusBadRequest, "bad request") - return - } - - tokenIssueSpan := app.GetInstance().Tracer.StartSpan("issue token", opentracing.ChildOf(tokenSpan.Context())) - - tokenString, err := app.GetInstance().Authenticator.Token(ctx, request.GrantType, request.ClientID, request.ClientSecret) - if err != nil { - zap.L(). - Error("token request is not authorized", - zap.Error(err), - zap.String("grant_type", request.GrantType.String()), - zap.String("client_id", request.ClientID), - zap.String("client_secret", request.ClientSecret), - ) - - tokenIssueSpan.SetTag("success", false) - - tokenIssueSpan.SetTag("error", err.Error()).Finish() - - if errors.Is(err, db.ErrDb) { - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusInternalServerError) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, "database error happened") - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, request.ClientID) - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusInternalServerError, "internal server error") - - tokenIssueSpan.Finish() - - return - } - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusUnauthorized) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, "request is not authorized") - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, request.ClientID) - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds())) - ctx.String(http.StatusUnauthorized, "request is not authorized") - - tokenIssueSpan.Finish() - - return - } - - zap.L(). - Info("token request accepted", - zap.String("grant_type", request.GrantType.String()), - zap.String("client_id", request.ClientID), - zap.String("client_secret", request.ClientSecret), - ) - - app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusAccepted) - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Success, "token request accepted") - app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Success, request.ClientID) - app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds())) - - tokenIssueSpan.SetTag("success", true) - - ctx.String(http.StatusAccepted, tokenString) -} diff --git a/pkg/acl/acl.go b/pkg/acl/acl.go new file mode 100644 index 00000000..bfdb4ce0 --- /dev/null +++ b/pkg/acl/acl.go @@ -0,0 +1,26 @@ +package acl + +// AccessType Types for EMQ contains subscribe, publish and publish-subscribe. +type AccessType string + +const ( + Sub AccessType = "1" + Pub AccessType = "2" + PubSub AccessType = "3" + None AccessType = "-1" + + ClientCredentials = "client_credentials" +) + +func (a AccessType) String() string { + switch a { //nolint:exhaustive + case Sub: + return "subscribe" + case Pub: + return "publish" + case PubSub: + return "publish-subscribe" + } + + return "" +} diff --git a/pkg/acl/token.go b/pkg/acl/token.go deleted file mode 100644 index 604c395d..00000000 --- a/pkg/acl/token.go +++ /dev/null @@ -1,26 +0,0 @@ -package acl - -import ( - "github.com/dgrijalva/jwt-go" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" -) - -type Claims struct { - jwt.StandardClaims - Topics []Topic `json:"topics"` - Endpoints []Endpoint `json:"endpoints"` -} - -type Topic struct { - Type topics.Type `json:"type"` - AccessType AccessType `json:"access_type"` -} - -type Endpoint struct { - Name string `json:"name"` -} - -type SuperuserClaims struct { - jwt.StandardClaims - IsSuperuser bool `json:"is_superuser"` -} diff --git a/pkg/acl/utils.go b/pkg/acl/utils.go deleted file mode 100644 index ab7639b8..00000000 --- a/pkg/acl/utils.go +++ /dev/null @@ -1,88 +0,0 @@ -package acl - -import "net" - -// Access Types for EMQ contains subscribe, publish and publish-subscribe. -type AccessType string - -const ( - Sub AccessType = "1" - Pub AccessType = "2" - PubSub AccessType = "3" - - ClientCredentials = "client_credentials" -) - -func (a AccessType) String() string { - switch a { - case Sub: - return "subscribe" - case Pub: - return "publish" - case PubSub: - return "publish-subscribe" - } - - return "" -} - -// ValidateEndpoint takes authorizedEndpoints and unauthorizedEndpoints and -// tell whether a endpoint is authorized or not. -func ValidateEndpoint(endpoint string, authorizedEndpoints, unauthorizedEndpoints []string) bool { - if len(authorizedEndpoints) == 0 && len(unauthorizedEndpoints) == 0 { - return true - } - - isValid := false - - for _, e := range authorizedEndpoints { - if e == endpoint { - isValid = true - - break - } - } - - for _, e := range unauthorizedEndpoints { - if e == endpoint { - isValid = false - - break - } - } - - return isValid -} - -// ValidateIP takes validIPs and invalidIPs and tell whether a IP is valid or not. -func ValidateIP(ip string, validIPs, invalidIPs []string) bool { - isValid := false - - for _, validIP := range validIPs { - _, network, err := net.ParseCIDR(validIP) - if err != nil && validIP == ip { - isValid = true - - break - } else if err == nil && network.Contains(net.ParseIP(ip)) { - isValid = true - - break - } - } - - for _, invalidIP := range invalidIPs { - _, network, err := net.ParseCIDR(invalidIP) - if err != nil && invalidIP == ip { - isValid = false - - break - } else if err == nil && network.Contains(net.ParseIP(ip)) { - isValid = false - - break - } - } - - return isValid -} diff --git a/pkg/acl/utils_test.go b/pkg/acl/utils_test.go deleted file mode 100644 index 2582a151..00000000 --- a/pkg/acl/utils_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package acl_test - -import ( - "testing" - - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" -) - -// nolint: funlen -func TestValidateEndpoint(t *testing.T) { - t.Parallel() - - type args struct { - endpoint string - authorizedEndpoints []string - unauthorizedEndpoints []string - } - - tests := []struct { - name string - args args - want bool - }{ - { - name: "#1", - args: args{ - endpoint: "", - authorizedEndpoints: []string{}, - unauthorizedEndpoints: []string{}, - }, - want: true, - }, - { - name: "#2", - args: args{ - endpoint: "/push", - authorizedEndpoints: []string{"/push"}, - unauthorizedEndpoints: []string{"/push"}, - }, - want: false, - }, - { - name: "#3", - args: args{ - endpoint: "/push", - authorizedEndpoints: []string{"/push"}, - unauthorizedEndpoints: []string{"/events"}, - }, - want: true, - }, - { - name: "#4", - args: args{ - endpoint: "/events", - authorizedEndpoints: []string{"/push"}, - unauthorizedEndpoints: []string{"/events"}, - }, - want: false, - }, - { - name: "#5", - args: args{ - endpoint: "/events", - authorizedEndpoints: []string{"/push"}, - unauthorizedEndpoints: []string{"/event"}, - }, - want: false, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - if got := acl.ValidateEndpoint(tt.args.endpoint, tt.args.authorizedEndpoints, - tt.args.unauthorizedEndpoints); got != tt.want { - t.Errorf("ValidateEndpoint() = %v, want %v", got, tt.want) - } - }) - } -} - -// nolint: funlen -func TestValidateIP(t *testing.T) { - t.Parallel() - - type args struct { - IP string - validIPs []string - invalidIPs []string - } - - tests := []struct { - name string - args args - want bool - }{ - { - name: "#1", - args: args{ - IP: "192.168.24.56", - validIPs: []string{}, - invalidIPs: []string{}, - }, - want: false, - }, - { - name: "#2", - args: args{ - IP: "192.168.24.56", - validIPs: []string{"192.168.24.56"}, - invalidIPs: []string{"192.168.24.56"}, - }, - want: false, - }, - { - name: "#3", - args: args{ - IP: "192.168.24.56", - validIPs: []string{"192.168.24.0/8"}, - invalidIPs: []string{"192.168.24.56"}, - }, - want: false, - }, - { - name: "#4", - args: args{ - IP: "192.168.24.56", - validIPs: []string{"192.168.24.0/8"}, - invalidIPs: []string{"192.168.24.8"}, - }, - want: true, - }, - { - name: "#5", - args: args{ - IP: "192.168.24.56", - validIPs: []string{"192.168.24.56"}, - invalidIPs: []string{"192.168.24.8/8"}, - }, - want: false, - }, - { - name: "#6", - args: args{ - IP: "192.168.24.56", - validIPs: []string{"192.168.24.1", "192.168.0.0/16"}, - invalidIPs: []string{"192.168.24.89"}, - }, - want: true, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - if got := acl.ValidateIP(tt.args.IP, tt.args.validIPs, tt.args.invalidIPs); got != tt.want { - t.Errorf("ValidateIP() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/errors/code.go b/pkg/errors/code.go deleted file mode 100644 index 3c22d79c..00000000 --- a/pkg/errors/code.go +++ /dev/null @@ -1,115 +0,0 @@ -package errors - -import "net/http" - -// Code is the type of the error returned to user in api -type Code string - -const ( - SuccessfulOperation Code = "successful_operation" - BadRequestPayload Code = "bad_request_payload" - SignUpUserFailure Code = "sign_up_user_failure" - WrongUsernameOrPassword Code = "wrong_username_or_password" - DatabaseSaveFailure Code = "database_save_failure" - DatabaseGetFailure Code = "database_get_failure" - DatabaseUpdateFailure Code = "database_update_failure" - DatabaseDeleteFailure Code = "database_delete_failure" - PasswordHashGenerationFailure Code = "password_hash_generation_failure" - UsernameMismatch Code = "username_mismatch" - IPMisMatch Code = "ip_mismatch" - PublicKeyReadFormFailure Code = "public_key_read_form_failure" - PublicKeyOpenFailure Code = "public_key_open_failure" - PublicKeyReadFileFailure Code = "public_key_read_file_failure" - PublicKeyParseFailure Code = "public_key_parse_failure" - InvalidRuleUUID Code = "invalid_rule_uuid" - RuleNotFound Code = "rule_not_found" - InvalidRule Code = "invalid_rule" -) - -// Message will return detailed error information -func (e Code) Message() string { - switch e { - case SuccessfulOperation: - return "operation done successfully" - case BadRequestPayload: - return "request payload is not valid" - case SignUpUserFailure: - return "could not save user" - case WrongUsernameOrPassword: - return "username or password is not correct" - case DatabaseSaveFailure: - return "could not save info to database" - case DatabaseGetFailure: - return "could not get info from database" - case DatabaseUpdateFailure: - return "could update info in database" - case DatabaseDeleteFailure: - return "could delete info from database" - case PasswordHashGenerationFailure: - return "could not generate hash from password" - case UsernameMismatch: - return "provided username does not match with username in path" - case IPMisMatch: - return "ip is not authorized" - case PublicKeyReadFormFailure: - return "cloud not read public key file from form" - case PublicKeyOpenFailure: - return "could not open public key file" - case PublicKeyReadFileFailure: - return "could not read from opened public key file" - case PublicKeyParseFailure: - return "could not parse public key" - case InvalidRuleUUID: - return "provided rule UUID is not valid" - case RuleNotFound: - return "account has no rule with provided UUID" - case InvalidRule: - return "provided rule is not valid" - default: - return "" - } -} - -// HttpStatusCode returns the perfect HTTP Status Code for the error -func (e Code) HttpStatusCode() int { - switch e { - case SuccessfulOperation: - return http.StatusOK - case BadRequestPayload: - return http.StatusBadRequest - case SignUpUserFailure: - return http.StatusInternalServerError - case WrongUsernameOrPassword: - return http.StatusUnauthorized - case DatabaseSaveFailure: - return http.StatusInternalServerError - case DatabaseGetFailure: - return http.StatusInternalServerError - case DatabaseUpdateFailure: - return http.StatusInternalServerError - case DatabaseDeleteFailure: - return http.StatusInternalServerError - case PasswordHashGenerationFailure: - return http.StatusInternalServerError - case UsernameMismatch: - return http.StatusUnauthorized - case IPMisMatch: - return http.StatusUnauthorized - case PublicKeyReadFormFailure: - return http.StatusBadRequest - case PublicKeyOpenFailure: - return http.StatusBadRequest - case PublicKeyReadFileFailure: - return http.StatusInternalServerError - case PublicKeyParseFailure: - return http.StatusBadRequest - case InvalidRuleUUID: - return http.StatusBadRequest - case RuleNotFound: - return http.StatusNotFound - case InvalidRule: - return http.StatusBadRequest - default: - return http.StatusInternalServerError - } -} diff --git a/pkg/errors/error.go b/pkg/errors/error.go deleted file mode 100644 index 5d7dae66..00000000 --- a/pkg/errors/error.go +++ /dev/null @@ -1,23 +0,0 @@ -package errors - -import ( - "fmt" -) - -// Error is the error type that contains a Code and a message about the error -type Error struct { - Code Code - Message string -} - -func (e Error) Error() string { - return fmt.Sprintf("%s: %s", e.Code.Message(), e.Message) -} - -// CreateError returns an instance of Error with given information -func CreateError(code Code, message string) *Error { - return &Error{ - Code: code, - Message: message, - } -} diff --git a/pkg/log/l.go b/pkg/log/l.go deleted file mode 100644 index 9b0daeb7..00000000 --- a/pkg/log/l.go +++ /dev/null @@ -1,41 +0,0 @@ -package log - -import ( - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "os" - "strings" -) - -var atom zap.AtomicLevel - -// InitLogger will replace global zap.L() with our own preferred configs -func InitLogger() func() { - config := zap.NewProductionEncoderConfig() - encoder := zapcore.NewJSONEncoder(config) - atom = zap.NewAtomicLevel() - logger := zap.New(zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), atom)) - return zap.ReplaceGlobals(logger) -} - -// SetLevel will change zap's log level -func SetLevel(lvl string) { - atom.SetLevel(parseLevel(lvl)) -} - -// parseLevel will convert a string based log level to zapcore.Level -func parseLevel(lvl string) zapcore.Level { - lvl = strings.ToLower(lvl) - switch lvl { - case "debug": - return zapcore.DebugLevel - case "info": - return zapcore.InfoLevel - case "warn": - return zapcore.WarnLevel - case "error": - return zapcore.ErrorLevel - default: - return zapcore.ErrorLevel - } -} diff --git a/pkg/memoize/m.go b/pkg/memoize/m.go deleted file mode 100644 index 9762158c..00000000 --- a/pkg/memoize/m.go +++ /dev/null @@ -1,26 +0,0 @@ -package memoize - -import ( - "golang.org/x/crypto/bcrypt" -) - -func MemoizedCompareHashAndPassword() func([]byte, []byte) error { - type args struct { - hashedPassword string - password string - } - - cache := make(map[args]error) - - return func(hashedPassword []byte, password []byte) error { - key := args{ - hashedPassword: string(hashedPassword), - password: string(password), - } - if _, ok := cache[key]; !ok { - cache[key] = bcrypt.CompareHashAndPassword(hashedPassword, password) - } - - return cache[key] - } -} diff --git a/pkg/memoize/m_test.go b/pkg/memoize/m_test.go deleted file mode 100644 index 1f37858d..00000000 --- a/pkg/memoize/m_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package memoize_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/memoize" - "golang.org/x/crypto/bcrypt" -) - -func TestMemoizedCompareHashAndPassword(t *testing.T) { - t.Parallel() - - fn := memoize.MemoizedCompareHashAndPassword() - hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost) - actual := fn(hashedPassword, []byte("test")) - expected := bcrypt.CompareHashAndPassword(hashedPassword, []byte("test")) - assert.Equal(t, actual, expected) - - s1 := time.Now() - - for i := 0; i < 10; i++ { - _ = bcrypt.CompareHashAndPassword(hashedPassword, []byte("test")) - } - - d1 := time.Since(s1) - - s2 := time.Now() - - for i := 0; i < 10; i++ { - _ = fn(hashedPassword, []byte("test")) - } - - d2 := time.Since(s2) - - assert.Less(t, d2.Nanoseconds(), d1.Nanoseconds()) -} diff --git a/pkg/metrics/m.go b/pkg/metrics/m.go deleted file mode 100644 index 1c3d5886..00000000 --- a/pkg/metrics/m.go +++ /dev/null @@ -1,13 +0,0 @@ -package metrics - -// Metrics is an interface used to capture metrics in soteria. -type Metrics interface { - // ObserveStatusCode is the method for status code metrics - ObserveStatusCode(api, serviceName, function string, code int) - - // ObserveStatus is the method for the status of the done operations - ObserveStatus(api, serviceName, function, status, info string) - - // ObserveResponseTime is the method for times takes by a specific function - ObserveResponseTime(api, serviceName, function string, time float64) -} diff --git a/pkg/metrics/prom.go b/pkg/metrics/prom.go deleted file mode 100644 index 87ebe700..00000000 --- a/pkg/metrics/prom.go +++ /dev/null @@ -1,40 +0,0 @@ -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - "strconv" -) - -// Handler is a implementation `Metrics` -type Handler struct { - StatusCodeCounterVec *prometheus.CounterVec - StatusCounterVec *prometheus.CounterVec - ResponseTimeVec *prometheus.SummaryVec -} - -func (h *Handler) ObserveStatusCode(api, serviceName, function string, code int) { - h.StatusCodeCounterVec.With(prometheus.Labels{ - "api": api, - "service": serviceName, - "function": function, - "code": strconv.Itoa(code), - }).Inc() -} - -func (h *Handler) ObserveStatus(api, serviceName, function, status, info string) { - h.StatusCounterVec.With(prometheus.Labels{ - "api": api, - "service": serviceName, - "function": function, - "status": status, - "info": info, - }).Inc() -} - -func (h *Handler) ObserveResponseTime(api, serviceName, function string, time float64) { - h.ResponseTimeVec.With(prometheus.Labels{ - "api": api, - "service": serviceName, - "function": function, - }).Observe(time) -} diff --git a/pkg/prettify.go b/pkg/prettify.go deleted file mode 100644 index d59b55d3..00000000 --- a/pkg/prettify.go +++ /dev/null @@ -1,8 +0,0 @@ -package pkg - -import "encoding/json" - -func PrettifyStruct(i interface{}) string { - s, _ := json.MarshalIndent(i, "", "\t") - return string(s) -} diff --git a/pkg/tracer/tracer.go b/pkg/tracer/tracer.go deleted file mode 100644 index 3f49bcc5..00000000 --- a/pkg/tracer/tracer.go +++ /dev/null @@ -1,32 +0,0 @@ -package tracer - -import ( - "fmt" - "io" - - "github.com/opentracing/opentracing-go" - jaegerConf "github.com/uber/jaeger-client-go/config" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/config" -) - -// New receives a `configs.TracerConfig` and returns a `opentracing.Tracer` and a `io.Closer` and a `error` if there was one -func New(cfg *config.TracerConfig) (opentracing.Tracer, io.Closer, error) { - trc, cl, err := jaegerConf.Configuration{ - ServiceName: cfg.ServiceName, - Disabled: !cfg.Enabled, - Sampler: &jaegerConf.SamplerConfig{ - Type: cfg.SamplerType, - Param: cfg.SamplerParam, - }, - Reporter: &jaegerConf.ReporterConfig{ - LogSpans: true, - LocalAgentHostPort: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), - }, - }.NewTracer() - - if err != nil { - return nil, nil, fmt.Errorf("failed to create new tracer: %w", err) - } - - return trc, cl, nil -} diff --git a/pkg/user/user.go b/pkg/user/user.go deleted file mode 100644 index 96d57944..00000000 --- a/pkg/user/user.go +++ /dev/null @@ -1,80 +0,0 @@ -package user - -import ( - "time" - - "github.com/google/uuid" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/db" - "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics" - "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl" -) - -// Type indicates user type which is herald, emq and staff. -type Type string - -const ( - HeraldUser Type = "HeraldUser" - EMQUser Type = "EMQUser" - Staff Type = "Staff" -) - -// Issuer indicate issuers. -type Issuer string - -const ( - Driver Issuer = "0" - Passenger Issuer = "1" - ThirdParty Issuer = "100" -) - -// User is Soteria's users db model. -type User struct { - MetaData db.MetaData `json:"meta_data"` - Username string `json:"username"` - Password string `json:"password"` - Type Type `json:"type"` - IPs []string `json:"ips"` - Secret string `json:"secret"` - TokenExpirationDuration time.Duration `json:"token_expiration_duration"` - Rules []Rule `json:"rules"` -} - -// Rule tells about a access to a specific topic or endpoint. -type Rule struct { - UUID uuid.UUID `json:"uuid"` - Endpoint string `json:"endpoint"` - Topic topics.Type `json:"topic"` - AccessType acl.AccessType `json:"access_type"` -} - -// GetMetadata is for getting metadata of a user model such as date created. -func (u User) GetMetadata() db.MetaData { - return u.MetaData -} - -// GetPrimaryKey is for knowing a model primary key. -func (u User) GetPrimaryKey() string { - return u.Username -} - -// CheckTopicAllowance checks whether the user is allowed to pub/sub/pubsub to a topic or not. -func (u User) CheckTopicAllowance(topic topics.Type, accessType acl.AccessType) bool { - for _, rule := range u.Rules { - if rule.Topic == topic && (rule.AccessType == acl.PubSub || rule.AccessType == accessType) { - return true - } - } - - return false -} - -// CheckEndpointAllowance checks whether the user is allowed to use a Herald endpoint or not. -func (u User) CheckEndpointAllowance(endpoint string) bool { - for _, rule := range u.Rules { - if rule.Endpoint == endpoint && rule.AccessType == acl.Pub { - return true - } - } - - return false -} diff --git a/pkg/validator/client.go b/pkg/validator/client.go new file mode 100644 index 00000000..b8980711 --- /dev/null +++ b/pkg/validator/client.go @@ -0,0 +1,176 @@ +package validator + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +const ( + ServiceNameHeader = "X-Service-Name" + + validateURI = "/api/v3/internal/validate" + authHeader = "Authorization" + userDataHeader = "X-User-Data" + modeQueryParam = "mode" +) + +var ( + ErrEmptyServiceName = errors.New("x-service-name can not be empty") + ErrInvalidJWT = errors.New("invalid jwt") + ErrInvalidUserDataHeader = errors.New("invalid X-User-Data header") + ErrRequestFailed = errors.New("validator request failed") +) + +type Client struct { + baseURL string + client *http.Client + timeout time.Duration + isOptional bool +} + +type Payload struct { + IAT int `json:"iat"` + Aud string `json:"aud"` + Iss int `json:"iss"` + Sub string `json:"sub"` + UserID int `json:"user_id"` + Email string `json:"email"` + Exp int `json:"exp"` + Locale string `json:"locale"` + Sid string `json:"sid"` +} + +// New creates a new Client with default attributes. +func New(url string, timeout time.Duration) Client { + return Client{ + baseURL: url, + client: new(http.Client), + timeout: timeout, + isOptional: false, + } +} + +// WithOptionalValidate enables you to bypass the signature validation. +// If the validation of the JWT signature is optional for you, and you just want to extract +// the payload from the token, you can use the client in `WithOptionalValidate` mode. +func (c *Client) WithOptionalValidate() { + c.isOptional = true +} + +// Validate gets the parent context, headers, and JWT token and calls the validate API of the JWT validator service. +// The parent context is helpful in canceling the process in the upper hand (a function that used the SDK) and in case +// you have something like tracing spans in your context and want to extend these things in your custom HTTP handler. +// Otherwise, you can use `context.Background()`. +// The headers argument is used when you want to pass some headers like user-agent, +// X-Service-Name, X-App-Name, X-App-Version and +// X-App-Version-Code to the validator. It is extremely recommended to pass these headers (if you have them) because +// it increases the visibility in the logs and metrics of the JWT Validator service. +// You must place your Authorization header content in the bearerToken argument. +// Consider that the bearerToken must contain Bearer keyword and JWT. +// For `X-Service-Name` you should put your project/service name in this header. +// nolint: funlen, cyclop +func (c *Client) Validate(parentCtx context.Context, headers http.Header, bearerToken string) (*Payload, error) { + if headers.Get(ServiceNameHeader) == "" { + return nil, ErrEmptyServiceName + } + + segments := strings.Split(bearerToken, " ") + if len(segments) < 2 || strings.ToLower(segments[0]) != "bearer" { + return nil, ErrInvalidJWT + } + + ctx, cancel := context.WithTimeout(parentCtx, c.timeout) + defer cancel() + + url := c.baseURL + validateURI + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("validator creating request failed %w", err) + } + + request.Header = headers + request.Header.Set(authHeader, bearerToken) + + query := request.URL.Query() + if c.isOptional { + query.Add(modeQueryParam, "optional") + } + + request.URL.RawQuery = query.Encode() + + response, err := c.client.Do(request) + if err != nil { + return nil, fmt.Errorf("validator sending request failed %w", err) + } + + closeBody(response) + + if response.StatusCode != http.StatusOK { + return nil, ErrRequestFailed + } + + userDataHeader := response.Header.Get(userDataHeader) + if userDataHeader == "" { + return nil, ErrInvalidJWT + } + + userData := map[string]interface{}{} + + if err := json.Unmarshal([]byte(userDataHeader), &userData); err != nil { + return nil, fmt.Errorf("X-User-Data header unmarshal failed: %w", err) + } + + payload := new(Payload) + if iat, ok := userData["iat"].(float64); ok { + payload.IAT = int(iat) + } + + if aud, ok := userData["aud"].(string); ok { + payload.Aud = aud + } + + if iss, ok := userData["iss"].(float64); ok { + payload.Iss = int(iss) + } + + if sub, ok := userData["sub"].(string); ok { + payload.Sub = sub + } + + if userID, ok := userData["user_id"].(float64); ok { + payload.UserID = int(userID) + } + + if email, ok := userData["email"].(string); ok { + payload.Email = email + } + + if exp, ok := userData["exp"].(float64); ok { + payload.Exp = int(exp) + } + + if locale, ok := userData["locale"].(string); ok { + payload.Locale = locale + } + + if sid, ok := userData["sid"].(string); ok { + payload.Sid = sid + } + + return payload, nil +} + +// closeBody to avoid memory leak when reusing http connection. +func closeBody(response *http.Response) { + if response != nil { + _, _ = io.Copy(io.Discard, response.Body) + _ = response.Body.Close() + } +} diff --git a/scripts/cli/.gitignore b/scripts/cli/.gitignore deleted file mode 100644 index 57feb1a3..00000000 --- a/scripts/cli/.gitignore +++ /dev/null @@ -1,152 +0,0 @@ - -# Created by https://www.toptal.com/developers/gitignore/api/python -# Edit at https://www.toptal.com/developers/gitignore?templates=python - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -pytestdebug.log - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ -doc/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -#poetry.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -# .env -.env/ -.venv/ -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ -pythonenv* - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# operating system-related files -*.DS_Store #file properties cache/storage on macOS -Thumbs.db #thumbnail cache on Windows - -# profiling data -.prof - - -# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/scripts/cli/acl.py b/scripts/cli/acl.py deleted file mode 100644 index 11748df6..00000000 --- a/scripts/cli/acl.py +++ /dev/null @@ -1,18 +0,0 @@ -import requests - -from vars import DRIVER_TOKEN, PUBLISH - -base_url = "https://soteria-snapp-ode-012.apps.private.teh-1.snappcloud.io" -acl_url = base_url + "/acl" - - -res = requests.post( - acl_url, - data={ - "access": PUBLISH, - "token": DRIVER_TOKEN, - "topic": "snapp/driver/1234/location", - }, -) - -print(res.content) diff --git a/scripts/cli/auth.py b/scripts/cli/auth.py deleted file mode 100644 index 0131e60b..00000000 --- a/scripts/cli/auth.py +++ /dev/null @@ -1,16 +0,0 @@ -import requests - -from vars import DRIVER_TOKEN - -base_url = "https://soteria-snapp-ode-012.apps.private.teh-1.snappcloud.io" -auth_url = base_url + "/auth" - - -res = requests.post( - auth_url, - data={ - "token": DRIVER_TOKEN, - }, -) - -print(res.content) diff --git a/scripts/cli/main.py b/scripts/cli/main.py deleted file mode 100644 index 7d2651fd..00000000 --- a/scripts/cli/main.py +++ /dev/null @@ -1,267 +0,0 @@ -import pprint - -import click - -import soteria - -pp = pprint.PrettyPrinter(indent=4) - - -@click.group() -@click.option("--base", "-b", required=True, type=str) -@click.pass_context -def cli(ctx, base): - ctx.ensure_object(dict) - - ctx.obj["BASE"] = base - - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--password", - "-p", - required=True, - type=str, - help="account password e.g. password", -) -@click.pass_context -def show(ctx, username: str, password: str): - account_manager = soteria.AccountManager(ctx.obj["BASE"]) - try: - resp = account_manager.show(username, password) - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--password", - "-p", - required=True, - type=str, - help="account password e.g. password", -) -@click.option( - "--user-type", - "-t", - required=True, - type=click.Choice(["herald", "emq", "staff"]), -) -@click.pass_context -def new(ctx, username: str, password: str, user_type: str): - account_manager = soteria.AccountManager(ctx.obj["BASE"]) - try: - resp = account_manager.new(username, password, user_type) - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--password", - "-p", - required=True, - type=str, - help="account password e.g. password", -) -@click.option( - "--access-type", - "-a", - required=True, - type=click.Choice(["pub", "sub", "pubsub"]), -) -@click.option( - "--topic", - "-t", - required=True, - type=str, - help="topic regular expressions are defined in soteria and has their name" - "so please refer to soteria", -) -@click.pass_context -def rules_add(ctx, username: str, password: str, access_type: str, topic: str): - account_manager = soteria.AccountManager(ctx.obj["BASE"]) - try: - resp = account_manager.add_rule(username, password, topic, access_type) - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--password", - "-p", - required=True, - type=str, - help="account password e.g. password", -) -@click.option( - "--expire", - "-e", - required=True, - type=int, - help="token expiration time in nanoseconds", -) -@click.pass_context -def set_expire( - ctx, - username: str, - password: str, - expire: int, -): - account_manager = soteria.AccountManager(ctx.obj["BASE"]) - try: - resp = account_manager.set_expiration(username, password, expire) - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--password", - "-p", - required=True, - type=str, - help="account password e.g. password", -) -@click.option( - "--secret", - "-s", - required=True, - type=str, - help="account secret which is different from password and" - "is used to generate token", -) -@click.pass_context -def set_secret( - ctx, - username: str, - password: str, - secret: str, -): - account_manager = soteria.AccountManager(ctx.obj["BASE"]) - try: - resp = account_manager.set_secret(username, password, secret) - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--secret", - "-s", - required=True, - type=str, - help="account secret which is different from password and" - "is used to generate token", -) -@click.option( - "--grant-type", - "-t", - required=True, - type=click.Choice(["pub", "sub", "pubsub"]), -) -@click.pass_context -def token( - ctx, - username: str, - secret: str, - grant_type: str, -): - account_manager = soteria.AccountManager(ctx.obj["BASE"]) - try: - token = account_manager.token(username, secret, grant_type) - click.echo(token) - except Exception as exep: - raise click.ClickException(str(exep)) - -@cli.command() -@click.option( - "--username", - "-u", - required=True, - type=str, - help="account username e.g. driver", -) -@click.option( - "--password", - "-p", - required=True, - type=str, - help="account password e.g. password", -) -@click.option( - "--duration", - "-d", - required=True, - type=int, - help="token expiration time in nanoseconds", -) -@click.pass_context -def superuser(ctx, username: str, password: str, duration: int): - emq_store = soteria.EMQStore(ctx.obj["BASE"]) - try: - resp = emq_store.new(username, password, duration) - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - -@cli.command() -@click.pass_context -def superusers(ctx): - emq_store = soteria.EMQStore(ctx.obj["BASE"]) - try: - resp = emq_store.get() - click.echo(pp.pformat(resp)) - except Exception as exep: - raise click.ClickException(str(exep)) - -if __name__ == "__main__": - cli(obj={}) diff --git a/scripts/cli/poetry.lock b/scripts/cli/poetry.lock deleted file mode 100644 index 36844897..00000000 --- a/scripts/cli/poetry.lock +++ /dev/null @@ -1,217 +0,0 @@ -[[package]] -name = "certifi" -version = "2021.5.30" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "click" -version = "8.0.1" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "dacite" -version = "1.6.0" -description = "Simple creation of data classes from dictionaries." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pytest (>=5)", "pytest-cov", "coveralls", "black", "mypy", "pylint"] - -[[package]] -name = "idna" -version = "2.10" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "mypy" -version = "0.910" -description = "Optional static typing for Python" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "requests" -version = "2.25.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] - -[[package]] -name = "requests-toolbelt" -version = "0.9.1" -description = "A utility belt for advanced users of python-requests" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "typing-extensions" -version = "3.10.0.0" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "urllib3" -version = "1.26.6" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.9" -content-hash = "aafe737381b5e1135e4a1554f4c893eb297ef5428f84f75e6c5b5cd3006a5c66" - -[metadata.files] -certifi = [ - {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, - {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -dacite = [ - {file = "dacite-1.6.0-py3-none-any.whl", hash = "sha256:4331535f7aabb505c732fa4c3c094313fc0a1d5ea19907bf4726a7819a68b93f"}, - {file = "dacite-1.6.0.tar.gz", hash = "sha256:d48125ed0a0352d3de9f493bf980038088f45f3f9d7498f090b50a847daaa6df"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -mypy = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] -requests-toolbelt = [ - {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, - {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, -] -urllib3 = [ - {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, - {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, -] diff --git a/scripts/cli/pyproject.toml b/scripts/cli/pyproject.toml deleted file mode 100644 index 0063ee33..00000000 --- a/scripts/cli/pyproject.toml +++ /dev/null @@ -1,19 +0,0 @@ -[tool.poetry] -name = "cli" -version = "0.1.0" -description = "soteria command line client" -authors = ["Parham Alvani "] - -[tool.poetry.dependencies] -python = "^3.9" -requests = "*" -click = "*" -requests-toolbelt = "*" -dacite = "*" - -[tool.poetry.dev-dependencies] -mypy = "^0.910" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/scripts/cli/soteria/__init__.py b/scripts/cli/soteria/__init__.py deleted file mode 100644 index c6fb535a..00000000 --- a/scripts/cli/soteria/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .account_manager import * -from .emq_store import * diff --git a/scripts/cli/soteria/account.py b/scripts/cli/soteria/account.py deleted file mode 100644 index 41923a22..00000000 --- a/scripts/cli/soteria/account.py +++ /dev/null @@ -1,14 +0,0 @@ -import dataclasses -import typing - -from .rule import Rule - - -@dataclasses.dataclass -class Account: - password: str - rules: typing.Optional[typing.List[Rule]] - secret: str - type: str - username: str - token_expiration_duration: int diff --git a/scripts/cli/soteria/account_manager.py b/scripts/cli/soteria/account_manager.py deleted file mode 100644 index c9aa575a..00000000 --- a/scripts/cli/soteria/account_manager.py +++ /dev/null @@ -1,92 +0,0 @@ -import dacite -from requests_toolbelt import sessions - -from .account import Account -from .rule import Rule - - -class AccountManager: - user_types = { - "herald": "HeraldUser", - "emq": "EMQUser", - "staff": "Staff", - } - - grant_types = { - "sub": "1", - "pub": "2", - "pubsub": "3", - } - - def __init__(self, base_url: str): - self.session = sessions.BaseUrlSession(base_url=base_url) - - def show(self, username: str, password: str) -> Account: - res = self.session.get( - f"accounts/{username}", auth=(username, password) - ).json() - - if res["data"] is not None: - return dacite.from_dict(data_class=Account, data=res["data"]) - raise Exception(res["message"]) - - def new(self, username: str, password: str, user_type: str): - res = self.session.post( - "accounts/", - json={ - "username": username, - "password": password, - "user_type": self.user_types[user_type], - }, - ) - return res.json() - - def add_rule( - self, username: str, password: str, topic: str, access_type: str - ) -> Rule: - res = self.session.post( - f"accounts/{username}/rules", - json={ - "topic": topic, - "access_type": self.grant_types[access_type], - }, - auth=(username, password), - ).json() - - if res["data"] is not None: - return dacite.from_dict(data_class=Rule, data=res["data"]) - raise Exception(res["message"]) - - def set_secret(self, username: str, password: str, secret: str): - res = self.session.put( - f"accounts/{username}", - json={ - "secret": secret, - }, - auth=(username, password), - ) - return res.json() - - def set_expiration(self, username: str, password: str, expiration: int): - res = self.session.put( - f"accounts/{username}", - json={ - "token_expiration": expiration, - }, - auth=(username, password), - ) - return res.json() - - def token(self, username: str, secret: str, grant_type: str) -> bytes: - """ - Generates a token from soteria. - """ - res = self.session.post( - "token", - json={ - "client_id": username, - "client_secret": secret, - "grant_type": self.grant_types[grant_type], - }, - ) - return res.content diff --git a/scripts/cli/soteria/emq_store.py b/scripts/cli/soteria/emq_store.py deleted file mode 100644 index f998b90c..00000000 --- a/scripts/cli/soteria/emq_store.py +++ /dev/null @@ -1,23 +0,0 @@ -from requests_toolbelt import sessions - - -class EMQStore: - def __init__(self, base_url: str): - self.session = sessions.BaseUrlSession(base_url=base_url) - - def new(self, username: str, password: str, duration: int): - res = self.session.post( - "emq/", - json={ - "username": username, - "password": password, - "duration": duration, - }, - ) - return res.json() - - def get(self): - res = self.session.get( - "emq/", - ) - return res.json() diff --git a/scripts/cli/soteria/rule.py b/scripts/cli/soteria/rule.py deleted file mode 100644 index 7cf507d3..00000000 --- a/scripts/cli/soteria/rule.py +++ /dev/null @@ -1,9 +0,0 @@ -import dataclasses - - -@dataclasses.dataclass -class Rule: - access_type: str - endpoint: str - topic: str - uuid: str diff --git a/scripts/cli/vars.py b/scripts/cli/vars.py deleted file mode 100644 index 035b30cb..00000000 --- a/scripts/cli/vars.py +++ /dev/null @@ -1,11 +0,0 @@ - -COLONY_TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjMzMDI3ODM0MjcsImlzcyI6IjEwMCIsInN1YiI6ImNvbG9ueS1zdWJzY3JpYmVyIn0.BChKcMUDFJ_GP9jdd2vl5GtDmtO0ha0EnLsmt4dgIwkxom8LQkpQD1sfAeEw53iClV8RYJOo_qLIdy1M0X7G26ePQuxaqi9FHpIvpM83w6BazINavM3SUZZmvhvoZN412eTwzjSPWr3v6OhJ-l-Lv7HUet7BOZ6D4IUKC3jjchHrJFXE9NKeBz886HE20y6iY5YIeuOqha-bfON3fzD5HcUs3WsvqcPNCL2AydkeafjS0k8SOY3m6vV5QQWGoIL7rRSDKbUf7KJXXs6BV3LFr3LblDxqh9P0zVUaKN8AQf4KnoqJJG8Awxab3OqTioV3OdWDiSuPQkl2s03BW1E-Xw' -DRIVER_TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDU4MzQwNjksImlzcyI6IjEwMCIsInN1YiI6ImNvbG9ueS1zdWJzY3JpYmVyIn0.hM3o2Tt2Q6-kshNT43YQh9LMzDZxbYlf11g9XcLkmrsuYokatM1iT1-IsKpcT2wiWQVkf5APtFxyzEYUMB2VGSbzl-C3kFbMicvlpT8fIpOlfo2FzOg_Ey8pLq-qnbWmxW7Mii6bS4m208OH15FM8-25f0Kmv-64TFHpZqpqpZI' -PASSENGER_TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDU4MzQwNjksImlzcyI6IjEwMCIsInN1YiI6ImNvbG9ueS1zdWJzY3JpYmVyIn0.hM3o2Tt2Q6-kshNT43YQh9LMzDZxbYlf11g9XcLkmrsuYokatM1iT1-IsKpcT2wiWQVkf5APtFxyzEYUMB2VGSbzl-C3kFbMicvlpT8fIpOlfo2FzOg_Ey8pLq-qnbWmxW7Mii6bS4m208OH15FM8-25f0Kmv-64TFHpZqpqpZI' - - -SUBSCRIE = '1' -PUBLISH = '2' -PUBLISH_SUBSCRIE = '3' - - diff --git a/test/100.pem b/test/100.pem deleted file mode 100644 index ad20ae77..00000000 --- a/test/100.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8 -OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu -GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o -Wsupn2HVhMSjFbE6dwIDAQAB ------END PUBLIC KEY----- \ No newline at end of file diff --git a/test/100.private.pem b/test/100.private.pem deleted file mode 100644 index 467519f4..00000000 --- a/test/100.private.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n -gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs -AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB -AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1 -j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP -hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs -hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh -1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E -pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg -FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT -6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r -m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ -l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g== ------END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/test/0.pem b/test/snapp-0.pem similarity index 100% rename from test/0.pem rename to test/snapp-0.pem diff --git a/test/0.private.pem b/test/snapp-0.private.pem similarity index 100% rename from test/0.private.pem rename to test/snapp-0.private.pem diff --git a/test/1.pem b/test/snapp-1.pem similarity index 100% rename from test/1.pem rename to test/snapp-1.pem diff --git a/test/1.private.pem b/test/snapp-1.private.pem similarity index 100% rename from test/1.private.pem rename to test/snapp-1.private.pem diff --git a/test/snapp-admin.pem b/test/snapp-admin.pem new file mode 100644 index 00000000..c73d6089 --- /dev/null +++ b/test/snapp-admin.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lNRwyNsDieWs6LvHOJ+ +GyehhRC4Pn5yL5edKP3565F3LtRDMrkzwDRsQbqnUtTea9HCdTdBv+lI8vE17qRi +RQn10IMaIH6e4Aa3OWNClFhuqNOag7VmffsjTOgxHgHpfGAKVF/4BwqOHrdHFbAD +VOiWB1hv9Uc0C5laffGAub7fj+EAI02zlrsNDxYW8vyF2H47N7VWcvgd3RhZpxlG +8bq9phl7Ja55YmQiT2Ic3/K5tsazg5z9lz6OTrx+JvWbefHFlJpjCLz5yefEaRmX +9L/zyDMi4jgFTZEWNXC2vIrxwZMFwFhBXEp0PcCbuHJgJIucbRrbwukQC16uHJwP +zQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/test/snapp-admin.private.pem b/test/snapp-admin.private.pem new file mode 100644 index 00000000..bffadbc9 --- /dev/null +++ b/test/snapp-admin.private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDWU1HDI2wOJ5az +ou8c4n4bJ6GFELg+fnIvl50o/fnrkXcu1EMyuTPANGxBuqdS1N5r0cJ1N0G/6Ujy +8TXupGJFCfXQgxogfp7gBrc5Y0KUWG6o05qDtWZ9+yNM6DEeAel8YApUX/gHCo4e +t0cVsANU6JYHWG/1RzQLmVp98YC5vt+P4QAjTbOWuw0PFhby/IXYfjs3tVZy+B3d +GFmnGUbxur2mGXslrnliZCJPYhzf8rm2xrODnP2XPo5OvH4m9Zt58cWUmmMIvPnJ +58RpGZf0v/PIMyLiOAVNkRY1cLa8ivHBkwXAWEFcSnQ9wJu4cmAki5xtGtvC6RAL +Xq4cnA/NAgMBAAECggEBAKC93JR9/IyhJGWVzb/cHrg/AOTLpWM5cmo/S59y2/3R +G5IDoGJqhGWi645gbx2wiSBUMYO37ZgBXrTWM5zHrOwajEHWEcJNNNrQLprb1xNr +P5NfIIOniTbzI4aKnkvjIzokzZp6X4roX66pxqZ9XAJSbjMUIOPUgzQvz66lceXb +4aKXSEPdBIRQRmwEUhOV3W8aXNDULR+LwPRt1PYxRm1+W0OPtOwnjfSxBdTbFf8+ +Lqsg92/kI/qfPnFq0LsbIdCF9yS/0GhmmJZNErwGl320SF2oXJXKiK2PXKj0mbPV +LLXOMvEkbwlD+6Nbgz3vIrTdcJXc0TfawZOwvin3AAECgYEA+s5Wl+qS+AEP2gPS +w0J/KqXA22jYN/bBXwZInBBFM7lugmX1A1OGh3Sbu7S1aGQI81OQ7aDSzhkeK2sI +irKnnaLQKzXsalQZvPSG9Q6De2Tj9sN5N0eI7VdB72b+sz/NpI2IsRE6+pBAvEzJ +YXFwH4mrj4SbHbcaUA6bhdkAP4UCgYEA2sOT2b8zsGk6JLrfq5CdbywdpT/xITdA +PivmsRF2bQ9F2j2V1y0B+cgfGJLWJgN5iSwg7SGlalUcFs188JcTgfJB5XsPXH8E +kNmD7fkfAoohn5d480fQRKuEboNa//F+zHq7yQVTcTnFsWctsXppv8A1ME7DNJrp +c2mAEHVU7akCgYEAqmEL8G3ZY4MNKrTYM+9zhhxOFH94CySlHpGdN+/Rox7AVPNA +a/8M7+4mcXCEoCL89Zf6Z4OOUZY8qZAvoFFXjr3xHrmmHmF9jqCrIcS3S1cxigwm +x4fgHCPf1euo8UpRwAyqJGepIlhmscSUNY8jdTlIA9o4qgoeZO5XdqkBAyUCgYEA +m+Ykx1hrDZzvwp0qKKm2iDN4LPuUa4dkUOoYTLeVHcN0lEKvNdjtP4ROJMT/t7di +NU8tZ9BCgbSFf/qQvyPq0wBB1bgNCm26Yz+ftUeDwduOep0HpNfYpBdXGSqi/yKq +qi4NBQS2okn5iKNu/LuwAOaJARQgKKz9ETJuAUycaYECgYA43841te0rJ81lNKE5 +wnCL/tx3OURDhSUJVf6tjwB+vo2FOIUfUJTBowzdWn5cTXCwGTp04JZBrZlY5RDH +24Tzo+/PTIbH9jiSYtKJCwJ+umPxek3OuBQo+4zAejN55jwcPOJcadp6u4hwq6yy +wpqfsglQldbuiZ0TilCRNhv00g== +-----END PRIVATE KEY----- \ No newline at end of file