├── public └── .gitkeep ├── deps ├── config │ ├── ca │ │ └── .gitkeep │ ├── certificates │ │ └── .gitkeep │ ├── apikey │ │ └── sample.csv │ └── manifests │ │ ├── nodes.yml.template │ │ ├── roles.yml.template │ │ └── store.yml.template ├── besu │ ├── logs │ │ ├── besu │ │ │ └── .gitkeep │ │ └── orion │ │ │ └── .gitkeep │ └── config │ │ ├── orion │ │ ├── networkFiles │ │ │ ├── orion1 │ │ │ │ ├── nodeKey.pub │ │ │ │ └── nodeKey.key │ │ │ ├── orion2 │ │ │ │ ├── nodeKey.pub │ │ │ │ └── nodeKey.key │ │ │ ├── orion3 │ │ │ │ ├── nodeKey.pub │ │ │ │ └── nodeKey.key │ │ │ └── orion4 │ │ │ │ ├── nodeKey.pub │ │ │ │ └── nodeKey.key │ │ ├── orion.conf │ │ └── log-config.xml │ │ └── besu │ │ ├── networkFiles │ │ ├── validator1 │ │ │ └── keys │ │ │ │ ├── key │ │ │ │ └── key.pub │ │ ├── validator2 │ │ │ └── keys │ │ │ │ ├── key │ │ │ │ └── key.pub │ │ ├── validator3 │ │ │ └── keys │ │ │ │ ├── key │ │ │ │ └── key.pub │ │ └── validator4 │ │ │ └── keys │ │ │ ├── key │ │ │ └── key.pub │ │ ├── permissions_config.toml │ │ ├── log-config.xml │ │ └── config.toml ├── geth │ └── config │ │ ├── password │ │ └── keystore │ │ ├── README.md │ │ ├── UTC--2019-06-28T21-12-24.010489227Z--7e654d251da770a068413677967f6d3ea2fea9e4 │ │ ├── UTC--2019-06-28T21-12-25.640448102Z--ff778b716fc07d98839f48ddb88d8be583beb684 │ │ ├── UTC--2019-06-28T21-12-27.070140387Z--664895b5fe3ddf049d2fb508cfa03923859763c6 │ │ ├── UTC--2019-06-28T21-12-28.571951431Z--f5956eb46b377ae41b41bda94e6270208d8202bb │ │ ├── UTC--2019-06-28T21-12-30.118221907Z--93f7274c9059e601be4512f656b57b830e019e41 │ │ ├── UTC--2019-06-28T21-12-31.614359712Z--bfc7137876d7ac275019d70434b0f0779824a969 │ │ ├── UTC--2019-06-28T21-12-33.201986679Z--a8d8db1d8919665a18212374d623fc7c0dfda410 │ │ ├── UTC--2019-06-28T21-12-34.637398304Z--ffbba394def3ff1df0941c6429887107f58d4e9b │ │ ├── UTC--2019-06-28T21-14-57.148249075Z--dbb881a51cd4023e4400cef3ef73046743f08da3 │ │ └── UTC--2019-06-28T21-14-58.600072289Z--6009608a02a7a15fd6689d6dad560c44e9ab61ff ├── pgadmin │ ├── password │ └── servers.json ├── go-quorum │ └── config │ │ └── quorum │ │ ├── passwords.txt │ │ ├── support │ │ ├── disallowed-nodes.json │ │ ├── static-nodes.json │ │ └── permissioned-nodes.json │ │ └── networkFiles │ │ ├── tessera1 │ │ ├── tm.pub │ │ ├── tma.pub │ │ ├── tm.key │ │ └── tma.key │ │ ├── tessera2 │ │ ├── tm.pub │ │ ├── tma.pub │ │ ├── tm.key │ │ └── tma.key │ │ ├── tessera3 │ │ ├── tm.pub │ │ ├── tma.pub │ │ ├── tm.key │ │ └── tma.key │ │ ├── validator1 │ │ ├── address │ │ ├── nodekey │ │ ├── nodekey.pub │ │ └── accountkey │ │ ├── validator2 │ │ ├── address │ │ ├── nodekey │ │ ├── nodekey.pub │ │ └── accountkey │ │ ├── validator3 │ │ ├── address │ │ ├── nodekey │ │ ├── nodekey.pub │ │ └── accountkey │ │ └── validator4 │ │ ├── address │ │ ├── nodekey │ │ ├── nodekey.pub │ │ └── accountkey ├── hashicorp │ ├── Dockerfile │ └── config │ │ ├── config.hcl │ │ ├── config-tls.hcl │ │ ├── agent-config.hcl │ │ └── agent-config-tls.hcl ├── migrations │ ├── 000002_create_alias.down.sql │ ├── 000001_create_tables.down.sql │ └── 000002_create_alias.up.sql ├── cfssl │ ├── root.json │ ├── qkm-client-auth.json │ ├── qkm-client-no-auth.json │ ├── intermediate.json │ ├── postgres.json │ ├── vault.json │ └── qkm.json └── docker-compose-tools.yml ├── tests ├── e2e │ ├── ca │ ├── certificates │ └── environment.go └── acceptance │ ├── docker │ ├── container │ │ └── container.go │ └── config │ │ ├── postgres │ │ └── postgres.go │ │ └── composition.go │ └── utils │ ├── reflect.go │ └── service.go ├── doc.quorum-key-manager-main ├── .nvmrc ├── static │ ├── .nojekyll │ └── img │ │ └── favicon.ico ├── .husky │ ├── pre-commit │ ├── commit-msg │ └── prepare-commit-msg ├── .eslintignore ├── docs │ ├── HowTo │ │ ├── Use-Manifest-File │ │ │ └── _category_.json │ │ └── Authenticate │ │ │ └── _category_.json │ ├── Reference │ │ ├── CLI │ │ │ └── _category_.json │ │ ├── Rest.md │ │ ├── Release-Types.md │ │ └── Responsible-Disclosure.md │ ├── Images │ │ ├── Architecture.png │ │ ├── EtherscanDeposit.png │ │ ├── EtherscanMessage.png │ │ └── Simplified_Architecture.png │ ├── Concepts │ │ ├── ArchitectureOverview.md │ │ ├── Authentication.md │ │ └── Nodes.md │ └── Get-Started │ │ └── Use-Docker.md ├── babel.config.js ├── .stylelintignore ├── tsconfig.json ├── .prettierrc ├── .editorconfig ├── .gitignore ├── README.md ├── vercel.json ├── .stylelintrc.js ├── .github │ ├── ISSUE_TEMPLATE │ │ └── bug_report.md │ └── workflows │ │ ├── build.yml │ │ ├── case.yml │ │ ├── release.yaml │ │ └── lint.yml ├── .releaserc.js ├── src │ └── pages │ │ └── index.module.css └── .cspell.json ├── .github ├── actions │ └── update-api-doc │ │ ├── .gitignore │ │ ├── package.json │ │ └── action.yml ├── pull_request_template.md ├── dependabot.yml ├── workflows │ └── codeql-analysis.yaml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pkg ├── common │ ├── writer.go │ ├── interfaces.go │ ├── arrays.go │ ├── pointer.go │ ├── interfaces_test.go │ └── rand.go ├── net │ └── dialer │ │ ├── dialer_test.go │ │ ├── dialer.go │ │ └── config.go ├── app │ └── config.go ├── client │ ├── config.go │ └── errors.go ├── tcp │ └── dialer.go ├── http │ ├── response │ │ ├── config.go │ │ ├── modifier_proxy.go │ │ ├── modifier_backend_server.go │ │ ├── modifier_compression.go │ │ ├── json.go │ │ ├── modifier_headers.go │ │ └── modifier.go │ ├── request │ │ ├── preparer_user_agent.go │ │ ├── preparer_http_proto.go │ │ ├── preparer_headers.go │ │ ├── preparer_host.go │ │ ├── preparer_url.go │ │ ├── preparer_test.go │ │ ├── config.go │ │ ├── preparer_proxy.go │ │ ├── preparer_body.go │ │ ├── json.go │ │ ├── json_test.go │ │ ├── preparer_uri.go │ │ └── preparer_forwarded_for.go │ ├── transport │ │ └── transport_test.go │ ├── client │ │ ├── client_test.go │ │ ├── config.go │ │ └── client.go │ ├── header │ │ ├── utils.go │ │ └── overide.go │ ├── server │ │ ├── server.go │ │ └── config.go │ ├── testutils │ │ └── request_matcher.go │ └── proxy │ │ ├── proxy_test.go │ │ └── config.go ├── errors │ ├── utils.go │ ├── errors.go │ └── data.go ├── jsonrpc │ ├── proxy.go │ └── route_matcher.go ├── tls │ └── verify_ca.go ├── json │ ├── duration.go │ ├── duration_test.go │ └── encode.go ├── ethereum │ ├── utils.go │ ├── caller.go │ └── caller_eea.go └── websocket │ └── forwarder.go ├── src ├── auth │ ├── entities │ │ ├── specs.go │ │ ├── role.go │ │ ├── testdata │ │ │ └── faker.go │ │ ├── permission_test.go │ │ ├── operation.go │ │ └── user.go │ ├── api │ │ ├── types │ │ │ └── roles.go │ │ └── http │ │ │ ├── no_auth.go │ │ │ ├── context.go │ │ │ └── accesslog.go │ └── service │ │ └── roles │ │ ├── list.go │ │ ├── get.go │ │ ├── create.go │ │ └── user_permissions.go ├── stores │ ├── entities │ │ ├── secret.go │ │ ├── metadata.go │ │ ├── store.go │ │ ├── ethereum.go │ │ ├── annotations.go │ │ ├── key.go │ │ └── attributes.go │ ├── store │ │ └── secrets │ │ │ └── aws │ │ │ └── parser.go │ ├── api │ │ ├── http │ │ │ └── context.go │ │ ├── types │ │ │ ├── stores.go │ │ │ └── secrets.go │ │ └── formatters │ │ │ ├── secrets.go │ │ │ └── keys.go │ ├── connectors │ │ ├── keys │ │ │ ├── decrypt.go │ │ │ ├── encrypt.go │ │ │ ├── sign.go │ │ │ ├── list.go │ │ │ ├── delete.go │ │ │ ├── destroy.go │ │ │ └── get.go │ │ ├── secrets │ │ │ ├── secrets.go │ │ │ ├── list.go │ │ │ ├── set.go │ │ │ └── delete.go │ │ ├── ethereum │ │ │ ├── decrypt.go │ │ │ ├── encrypt.go │ │ │ └── ethereum.go │ │ └── stores │ │ │ └── create_eth.go │ ├── app │ │ └── service.go │ └── secrets.go ├── infra │ ├── api-key │ │ ├── csv │ │ │ └── config.go │ │ └── reader.go │ ├── manifests │ │ ├── yaml │ │ │ └── config.go │ │ └── reader.go │ ├── tls │ │ ├── filesystem │ │ │ ├── config.go │ │ │ └── reader.go │ │ └── reader.go │ ├── jwt │ │ ├── validator.go │ │ └── jose │ │ │ └── config.go │ ├── log │ │ ├── zap │ │ │ └── config.go │ │ ├── logger.go │ │ └── testutils │ │ │ └── faker.go │ ├── http │ │ └── types.go │ ├── akv │ │ └── client │ │ │ └── client.go │ ├── postgres │ │ └── client │ │ │ ├── parsers.go │ │ │ └── queries.go │ ├── aws │ │ └── client │ │ │ ├── config.go │ │ │ └── client.go │ └── hashicorp │ │ └── client │ │ └── parsers.go ├── entities │ ├── alias_registry.go │ ├── algo.go │ ├── manifest.go │ └── testutils │ │ └── faker.go ├── nodes │ ├── entities │ │ └── node.go │ ├── node │ │ ├── proxy │ │ │ ├── context_test.go │ │ │ ├── context.go │ │ │ └── session.go │ │ └── node.go │ ├── interceptor │ │ ├── eth_accounts.go │ │ └── eth_sign.go │ ├── service.go │ ├── service │ │ └── nodes │ │ │ ├── list.go │ │ │ └── get.go │ └── app │ │ └── service.go ├── aliases │ ├── api │ │ └── types │ │ │ ├── testutils │ │ │ └── faker.go │ │ │ └── registry.go │ ├── service │ │ ├── aliases │ │ │ ├── parse.go │ │ │ ├── aliases.go │ │ │ ├── delete.go │ │ │ └── get.go │ │ └── registries │ │ │ ├── registries.go │ │ │ ├── delete.go │ │ │ ├── get.go │ │ │ └── create.go │ ├── database │ │ ├── database.go │ │ └── models │ │ │ ├── registry.go │ │ │ └── alias.go │ └── app │ │ └── service.go ├── vaults │ ├── app │ │ └── service.go │ ├── service │ │ └── vaults │ │ │ ├── get.go │ │ │ ├── create_aws.go │ │ │ ├── create_azure.go │ │ │ ├── create_hashicorp_test.go │ │ │ └── create_aws_test.go │ └── service.go ├── utils │ ├── service │ │ └── utils │ │ │ ├── utils.go │ │ │ └── ec_recover.go │ ├── app │ │ └── service.go │ └── service.go ├── config.go └── docs.go ├── main.go ├── scripts ├── install_swagger.sh └── coverage.sh ├── AUTHORS ├── cmd └── flags │ ├── app.go │ ├── sync.go │ ├── apikey.go │ ├── tls.go │ └── manifest.go ├── CLA.md ├── sonar-project.properties ├── .env.sample └── Dockerfile /public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deps/config/ca/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deps/besu/logs/besu/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deps/besu/logs/orion/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deps/config/certificates/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deps/geth/config/password: -------------------------------------------------------------------------------- 1 | password -------------------------------------------------------------------------------- /tests/e2e/ca: -------------------------------------------------------------------------------- 1 | ../../deps/config/ca -------------------------------------------------------------------------------- /deps/pgadmin/password: -------------------------------------------------------------------------------- 1 | postgres 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/passwords.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.nvmrc: -------------------------------------------------------------------------------- 1 | v19.4.0 2 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/e2e/certificates: -------------------------------------------------------------------------------- 1 | ../../deps/config/certificates/ -------------------------------------------------------------------------------- /.github/actions/update-api-doc/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/support/disallowed-nodes.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion1/nodeKey.pub: -------------------------------------------------------------------------------- 1 | A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo= -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion2/nodeKey.pub: -------------------------------------------------------------------------------- 1 | Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs= -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion3/nodeKey.pub: -------------------------------------------------------------------------------- 1 | k2zXEin4Ip/qBGlRkJejnGWdP9cjkK+DAvKNW31L2C8= -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion4/nodeKey.pub: -------------------------------------------------------------------------------- 1 | hwHS3n8i5XXzx8vfivDgwaULFZOEsl42v+l+J1rRBBE= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera1/tm.pub: -------------------------------------------------------------------------------- 1 | BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera1/tma.pub: -------------------------------------------------------------------------------- 1 | 8SjRHlUBe4hAmTk3KDeJ96RhN+s10xRrHDrxEi1O5W0= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera2/tm.pub: -------------------------------------------------------------------------------- 1 | QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera2/tma.pub: -------------------------------------------------------------------------------- 1 | 2T7xkjblN568N1QmPeElTjoeoNT4tkWYOJYxSMDO5i0= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera3/tm.pub: -------------------------------------------------------------------------------- 1 | 1iTZde/ndBHvzhcl7V68x44Vx7pl8nwx9LqnM/AfJUg= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera3/tma.pub: -------------------------------------------------------------------------------- 1 | 3nLS1GSlPs3/AccoZ20WTBrYP/ua5KDlUM1uGrDKHTs= -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator1/address: -------------------------------------------------------------------------------- 1 | 0x93917cadbace5dfce132b991732c6cda9bcc5b8a 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator2/address: -------------------------------------------------------------------------------- 1 | 0x27a97c9aaf04f18f3014c32e036dd0ac76da5f18 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator3/address: -------------------------------------------------------------------------------- 1 | 0xce412f988377e31f4d0ff12d74df73b51c42d0ca 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator4/address: -------------------------------------------------------------------------------- 1 | 0x98c1334496614aed49d2e81526d089f7264fed9c 2 | -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator1/keys/key: -------------------------------------------------------------------------------- 1 | 0fd4aecd8f02b24f468325aa06e1428ab8076d283bac3ed804c9f70187dedb63 -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator2/keys/key: -------------------------------------------------------------------------------- 1 | 47c63c0afd1a85b16915934a2d75cf5a0d3bd13c509d6ee9d7ef1315a36bdc0a -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator3/keys/key: -------------------------------------------------------------------------------- 1 | 35dcc853354b24e98889fa4b2e214d5e92e759ef8312bb6a444bad3182187b68 -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator4/keys/key: -------------------------------------------------------------------------------- 1 | d38e71552943e18061fdb44a72eca14ea193e0505a7ead404864f9840e275b49 -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /deps/geth/config/keystore/README.md: -------------------------------------------------------------------------------- 1 | These addresses are the same as the default accounts available in the Quorum key manager 2 | -------------------------------------------------------------------------------- /deps/hashicorp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.14 2 | 3 | RUN apk add --no-cache \ 4 | jq \ 5 | curl \ 6 | bash 7 | 8 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator1/nodekey: -------------------------------------------------------------------------------- 1 | 1a2c4ff0f1b38e2322658dba692816138eb22d002515df1fffca21278f406aa9 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator2/nodekey: -------------------------------------------------------------------------------- 1 | 7f9af699dd2bb1af76c90b3f67183dd48abae509c315eb8f2c55301ad90ba978 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator3/nodekey: -------------------------------------------------------------------------------- 1 | fe006b00c738e7e5af7f7623290ffc83f394741ae6fb6afc6081cab49e1e1a70 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator4/nodekey: -------------------------------------------------------------------------------- 1 | 8f6ae009cdbbf6e6fa739b91a4483f251bbe89f6570d34856554533b36c93c55 2 | -------------------------------------------------------------------------------- /pkg/common/writer.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "io" 4 | 5 | type WriterWrapper interface { 6 | Writer() io.Writer 7 | } 8 | -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion1/nodeKey.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"hBsuQsGJzx4QHmFmBkNoI7YGnTmaZP4P+wBOdu56ljk="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion2/nodeKey.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"wjeHURxQJUqZvhjMv+lxoRlXYnX/mpm0Iu/ldBr32Qs="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion3/nodeKey.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"QmBfzDOxF99iInkXAPBAZpQQfelvPmHqbcO8tNHYtJM="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/besu/config/orion/networkFiles/orion4/nodeKey.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"GMNLqPR+9/HR+88l32Oal6waSYplgOCgMLZwLaMjIcs="},"type":"unlocked"} -------------------------------------------------------------------------------- /src/auth/entities/specs.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type RoleSpecs struct { 4 | Permissions []Permission `json:"permission"` 5 | } 6 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera1/tm.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera1/tma.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"wGEar7J9G0JAgdisp61ZChyrJWeW2QPyKvecjjeVHOY="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera2/tm.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"nDFwJNHSiT1gNzKBy9WJvMhmYRkW3TzFUmPsNzR6oFk="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera2/tma.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"rwfJC1kNa8BjPfc+zZXug+it9sdWa0vbdN6pp6IXlAs="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera3/tm.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"tMxUVR8bX7aq/TbpVHc2QV3SN2iUuExBwefAuFsO0Lg="},"type":"unlocked"} -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/tessera3/tma.key: -------------------------------------------------------------------------------- 1 | {"data":{"bytes":"yLcbICXicELZOnvpkDXB2UkQUiNAMIfsEOsgtFOGkQU="},"type":"unlocked"} -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .eslintrc.js 3 | docs/test-api 4 | ./node_modules/* 5 | *.md 6 | *.mdx 7 | LICENSE 8 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/HowTo/Use-Manifest-File/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Use a manifest file", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /deps/migrations/000002_create_alias.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP TABLE IF EXISTS aliases; 4 | DROP TABLE IF EXISTS registries; 5 | 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve("@docusaurus/core/lib/babel/preset")], 3 | }; 4 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/HowTo/Authenticate/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Authenticate user requests", 3 | "position": 1 4 | } 5 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Reference/CLI/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Quorum Key Manager command line", 3 | "position": 1 4 | } 5 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && npx git-cz --hook || true 5 | -------------------------------------------------------------------------------- /pkg/net/dialer/dialer_test.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import "testing" 4 | 5 | func TestDialer(t *testing.T) { 6 | _ = New(new(Config).SetDefault()) 7 | } 8 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/quorum-key-manager/HEAD/doc.quorum-key-manager-main/static/img/favicon.ico -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Images/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/quorum-key-manager/HEAD/doc.quorum-key-manager-main/docs/Images/Architecture.png -------------------------------------------------------------------------------- /src/auth/entities/role.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Role struct { 4 | Name string 5 | Permissions []Permission 6 | } 7 | 8 | const AnonymousRole = "anonymous" 9 | -------------------------------------------------------------------------------- /deps/migrations/000001_create_tables.down.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | DROP TABLE IF EXISTS secrets; 4 | DROP TABLE IF EXISTS keys; 5 | DROP TABLE IF EXISTS eth_accounts; 6 | 7 | COMMIT; 8 | -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator1/keys/key.pub: -------------------------------------------------------------------------------- 1 | c1979a8a48693db804316b5acebe35e11731e1fb1c9c21ff7268ab25db6f6e03390a429b83cf0ec0865a7205f2669ec1ace652a3def11e2e01571c74939cbe22 -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator2/keys/key.pub: -------------------------------------------------------------------------------- 1 | e40129f02c9e29a02049668346d4777bb55809042746882b33b20a8b5a7310eb5f107a53f0aa3da766ee77f401557a79c0c328329ea48bf0996c6c9dff817f76 -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator3/keys/key.pub: -------------------------------------------------------------------------------- 1 | a3e4af081a0ab853c959b9acd0596f818b91a9409b9d04c50af055072c929abfa340e14111dcfa76e049fdb16bb9198e722d5e7be3e8ef37562ea0d0ce1eda11 -------------------------------------------------------------------------------- /deps/besu/config/besu/networkFiles/validator4/keys/key.pub: -------------------------------------------------------------------------------- 1 | 8f4e444a73034236ab4244c7a572aa2c6198b9e0d483ef17bf4b751cac5c0370bc527a5b0c5d01aa3ef41704af838c74730aeecac0f0c22dc4c17b0a9f03ad76 -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Images/EtherscanDeposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/quorum-key-manager/HEAD/doc.quorum-key-manager-main/docs/Images/EtherscanDeposit.png -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Images/EtherscanMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/quorum-key-manager/HEAD/doc.quorum-key-manager-main/docs/Images/EtherscanMessage.png -------------------------------------------------------------------------------- /src/stores/entities/secret.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Secret struct { 4 | ID string 5 | Value string 6 | Metadata *Metadata 7 | Tags map[string]string 8 | } 9 | -------------------------------------------------------------------------------- /pkg/app/config.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/http/server" 5 | ) 6 | 7 | type Config struct { 8 | HTTP *server.Config 9 | } 10 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator1/nodekey.pub: -------------------------------------------------------------------------------- 1 | 8208a3f344695d44e9cf2c023683cbea7b9343e2f70a5e804bd2c93858e945f8f91439eef96a4ab6c47ff06637d6fbe6472f96de1655a1bee57ea896654f3a22 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator2/nodekey.pub: -------------------------------------------------------------------------------- 1 | b9050e002aa42464e6b07c811a1f9dfec01249af03f67b753e8415420649b184447bb2a784863ccbf327ad9e31aaba803464979dfe6a7facc669151a5fa6ad1b 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator3/nodekey.pub: -------------------------------------------------------------------------------- 1 | 59cf0c623c582fa9b19bdf70fb6bade07f4ae32218dd4d1c7e2c7e65acf87da45cf2ab55d16d27360aafef17622c37c09db60d7680ebcc17b78867f4c05bcaa4 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator4/nodekey.pub: -------------------------------------------------------------------------------- 1 | 2fd5b5b6ad529f55b71602026d1849d0036f06482368b5812fa793014195d3571b0840dbc4175617de2a12db8f1222c012420d471ae5c0d982118625cae58868 2 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Images/Simplified_Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/quorum-key-manager/HEAD/doc.quorum-key-manager-main/docs/Images/Simplified_Architecture.png -------------------------------------------------------------------------------- /pkg/client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type Config struct { 4 | URL string 5 | } 6 | 7 | func NewConfig(url string) *Config { 8 | return &Config{ 9 | URL: url, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.stylelintignore: -------------------------------------------------------------------------------- 1 | # Stylelint runs on everything by default; we only lint CSS files. 2 | * 3 | !*/ 4 | !*.css 5 | __tests__/ 6 | build 7 | api/ 8 | docs/ 9 | blog/ 10 | -------------------------------------------------------------------------------- /pkg/tcp/dialer.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | type Dialer interface { 9 | DialContext(ctx context.Context, network, addr string) (net.Conn, error) 10 | } 11 | -------------------------------------------------------------------------------- /src/infra/api-key/csv/config.go: -------------------------------------------------------------------------------- 1 | package csv 2 | 3 | type Config struct { 4 | Path string 5 | } 6 | 7 | func NewConfig(path string) *Config { 8 | return &Config{ 9 | Path: path, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/manifests/yaml/config.go: -------------------------------------------------------------------------------- 1 | package yaml 2 | 3 | type Config struct { 4 | Path string 5 | } 6 | 7 | func NewConfig(path string) *Config { 8 | return &Config{ 9 | Path: path, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/infra/tls/filesystem/config.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | type Config struct { 4 | Path string 5 | } 6 | 7 | func NewConfig(path string) *Config { 8 | return &Config{ 9 | Path: path, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/http/response/config.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type ProxyConfig struct { 4 | Headers map[string][]string `json:"headers,omitempty"` 5 | } 6 | 7 | func (cfg *ProxyConfig) SetDefault() *ProxyConfig { 8 | return cfg 9 | } 10 | -------------------------------------------------------------------------------- /pkg/net/dialer/dialer.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import "net" 4 | 5 | func New(cfg *Config) *net.Dialer { 6 | return &net.Dialer{ 7 | Timeout: cfg.Timeout.Duration, 8 | KeepAlive: cfg.KeepAlive.Duration, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /deps/besu/config/orion/orion.conf: -------------------------------------------------------------------------------- 1 | nodeport = 8080 2 | nodenetworkinterface = "0.0.0.0" 3 | clientport = 8888 4 | clientnetworkinterface = "0.0.0.0" 5 | publickeys = ["/keys/nodeKey.pub"] 6 | privatekeys = ["/keys/nodeKey.key"] 7 | tls = "off" 8 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/stores/entities/metadata.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type Metadata struct { 6 | Version string 7 | Disabled bool 8 | ExpireAt time.Time 9 | CreatedAt time.Time 10 | UpdatedAt time.Time 11 | DeletedAt time.Time 12 | } 13 | -------------------------------------------------------------------------------- /src/entities/alias_registry.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type AliasRegistry struct { 6 | Name string 7 | Aliases []Alias 8 | AllowedTenants []string 9 | CreatedAt time.Time 10 | UpdatedAt time.Time 11 | } 12 | -------------------------------------------------------------------------------- /src/auth/api/types/roles.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/consensys/quorum-key-manager/src/auth/entities" 4 | 5 | type CreateRoleRequest struct { 6 | Permissions []entities.Permission `json:"permissions" yaml:"permissions" validate:"required" example:"*:*"` 7 | } 8 | -------------------------------------------------------------------------------- /src/nodes/entities/node.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | proxynode "github.com/consensys/quorum-key-manager/src/nodes/node/proxy" 5 | ) 6 | 7 | type Node struct { 8 | Name string 9 | Node *proxynode.Node 10 | AllowedTenants []string 11 | } 12 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": true, 4 | "bracketSpacing": true, 5 | "printWidth": 80, 6 | "proseWrap": "never", 7 | "singleQuote": false, 8 | "trailingComma": "all", 9 | "tabWidth": 2, 10 | "semi": true 11 | } 12 | -------------------------------------------------------------------------------- /deps/cfssl/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Tanuki Root CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "ST": "Paris", 12 | "O": "Consensys", 13 | "OU": "Tanuki team Root CA" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/cmd" 5 | log "github.com/sirupsen/logrus" 6 | ) 7 | 8 | func main() { 9 | command := cmd.NewCommand() 10 | if err := command.Execute(); err != nil { 11 | log.WithError(err).Fatalf("execution failed") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /deps/cfssl/qkm-client-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "tenant0|user0", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "O": "admin", 12 | "OU": "*:*", 13 | "ST": "Paris" 14 | } 15 | 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /pkg/http/response/modifier_proxy.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | // Proxy creates a preparer for proxying request 4 | func Proxy(cfg *ProxyConfig) Modifier { 5 | var modifiers = []Modifier{ 6 | BackendServer(), 7 | Headers(cfg.Headers), 8 | GZIP(), 9 | } 10 | 11 | return CombineModifier(modifiers...) 12 | } 13 | -------------------------------------------------------------------------------- /src/infra/tls/reader.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | ) 7 | 8 | //go:generate mockgen -source=reader.go -destination=mock/reader.go -package=mock 9 | 10 | // Reader reads TLS certificates 11 | type Reader interface { 12 | Load(ctx context.Context) (*x509.CertPool, error) 13 | } 14 | -------------------------------------------------------------------------------- /src/stores/entities/store.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | const ( 4 | EthereumStoreType = "ethereum" 5 | KeyStoreType = "key" 6 | SecretStoreType = "secret" 7 | ) 8 | 9 | type Store struct { 10 | Name string 11 | AllowedTenants []string 12 | Store interface{} 13 | StoreType string 14 | } 15 | -------------------------------------------------------------------------------- /deps/cfssl/qkm-client-no-auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "tenant1|user1", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "O": "Consensys", 12 | "OU": "Vault Tanuki", 13 | "ST": "Paris" 14 | } 15 | 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/aliases/api/types/testutils/faker.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/aliases/api/types" 5 | ) 6 | 7 | func FakeCreateRegistryRequest() *types.CreateRegistryRequest { 8 | return &types.CreateRegistryRequest{ 9 | AllowedTenants: []string{"tenant_1"}, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/install_swagger.sh: -------------------------------------------------------------------------------- 1 | download_url=$(curl -s https://api.github.com/repos/go-swagger/go-swagger/releases/latest | \ 2 | jq -r '.assets[] | select(.name | contains("'"$(uname | tr '[:upper:]' '[:lower:]')"'_amd64")) | .browser_download_url') 3 | curl -o /usr/local/bin/swagger -L'#' "$download_url" 4 | chmod +x /usr/local/bin/swagger 5 | -------------------------------------------------------------------------------- /pkg/common/interfaces.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func InterfaceToObject(data, result interface{}) error { 8 | dataB, err := json.Marshal(data) 9 | if err != nil { 10 | return err 11 | } 12 | 13 | err = json.Unmarshal(dataB, result) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /pkg/errors/utils.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func CombineErrors(errs ...error) error { 8 | var err error 9 | for _, e := range errs { 10 | if e == nil { 11 | continue 12 | } 13 | 14 | if err == nil { 15 | err = e 16 | } else { 17 | err = fmt.Errorf("%v; %v", e, err) 18 | } 19 | } 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /src/stores/entities/ethereum.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/ethereum/go-ethereum/common" 4 | 5 | type ETHAccount struct { 6 | Address common.Address 7 | KeyID string 8 | PublicKey []byte 9 | CompressedPublicKey []byte 10 | Metadata *Metadata 11 | Tags map[string]string 12 | } 13 | -------------------------------------------------------------------------------- /deps/hashicorp/config/config.hcl: -------------------------------------------------------------------------------- 1 | backend "file" { 2 | path = "/vault/file" 3 | } 4 | 5 | listener "tcp" { 6 | address = "hashicorp:8200" 7 | tls_disable = true 8 | } 9 | 10 | default_lease_ttl = "15m" 11 | max_lease_ttl = 99999999 12 | api_addr = "http://hashicorp:8200" 13 | plugin_directory = "/vault/plugins" 14 | log_level = "Debug" 15 | 16 | ui = false 17 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | max_line_length = 80 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /pkg/http/response/modifier_backend_server.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "net/http" 4 | 5 | // BackendServer set "X-Backend-Server" header to the URL of the request 6 | func BackendServer() Modifier { 7 | return ModifierFunc(func(resp *http.Response) error { 8 | resp.Header.Set("X-Backend-Server", resp.Request.URL.String()) 9 | return nil 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | .vercel 22 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_user_agent.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // UserAgent sets User-Agent header 8 | func UserAgent(agent string) Preparer { 9 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 10 | if agent != "" { 11 | req.Header.Set("User-Agent", agent) 12 | } 13 | 14 | return req, nil 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/http/transport/transport_test.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestTransport(t *testing.T) { 10 | cfg := (&Config{ 11 | EnableHTTP2: true, 12 | EnableH2C: true, 13 | }).SetDefault() 14 | 15 | _, err := New(cfg) 16 | 17 | require.NoError(t, err, "New should not error") 18 | } 19 | -------------------------------------------------------------------------------- /src/entities/algo.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Curve string 4 | type KeyType string 5 | 6 | const ( 7 | Ecdsa KeyType = "ecdsa" 8 | Eddsa KeyType = "eddsa" 9 | 10 | Babyjubjub Curve = "babyjubjub" 11 | Secp256k1 Curve = "secp256k1" 12 | Curve25519 Curve = "curve25519" 13 | ) 14 | 15 | type Algorithm struct { 16 | Type KeyType 17 | EllipticCurve Curve 18 | } 19 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | # This is the official list of Quorum Key Manager authors 3 | 4 | Christian Tran 5 | Dario Varela 6 | Gabriel Garrido Calvo 7 | Julien Marchand 8 | Nicolas Maurice 9 | Sylvain Decourval 10 | -------------------------------------------------------------------------------- /src/auth/entities/testdata/faker.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/auth/entities" 5 | ) 6 | 7 | func FakeUserClaims() *entities.UserClaims { 8 | return &entities.UserClaims{ 9 | Tenant: "TenantOne|Alice", 10 | Permissions: []string{"read:key", "write:key"}, 11 | Roles: []string{"guest", "admin"}, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/infra/manifests/reader.go: -------------------------------------------------------------------------------- 1 | package manifests 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | ) 8 | 9 | //go:generate mockgen -source=reader.go -destination=mock/reader.go -package=mock 10 | 11 | // Reader reads manifests 12 | type Reader interface { 13 | Load(ctx context.Context) (map[string][]entities.Manifest, error) 14 | } 15 | -------------------------------------------------------------------------------- /deps/cfssl/intermediate.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Tanuki Intermediate CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "ST": "Paris", 12 | "O": "Consensys", 13 | "OU": "Tanuki team Intermediate CA" 14 | } 15 | ], 16 | "ca": { 17 | "expiry": "42720h" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/infra/api-key/reader.go: -------------------------------------------------------------------------------- 1 | package apikey 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | //go:generate mockgen -source=reader.go -destination=mock/reader.go -package=mock 10 | 11 | // Reader reads manifests from filesystem 12 | type Reader interface { 13 | Load(ctx context.Context) (map[string]*entities.UserClaims, error) 14 | } 15 | -------------------------------------------------------------------------------- /src/stores/store/secrets/aws/parser.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/stores/entities" 5 | ) 6 | 7 | func formatAwsSecret(id, value string, tags map[string]string, metadata *entities.Metadata) *entities.Secret { 8 | return &entities.Secret{ 9 | ID: id, 10 | Value: value, 11 | Tags: tags, 12 | Metadata: metadata, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/vaults/app/service.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/auth" 5 | "github.com/consensys/quorum-key-manager/src/infra/log" 6 | "github.com/consensys/quorum-key-manager/src/vaults/service/vaults" 7 | ) 8 | 9 | func RegisterService(logger log.Logger, roles auth.Roles) *vaults.Vaults { 10 | // Business layer 11 | return vaults.New(roles, logger) 12 | } 13 | -------------------------------------------------------------------------------- /deps/config/apikey/sample.csv: -------------------------------------------------------------------------------- 1 | ## Notes: Request headers should be "Authorization: Basic base64({user-key})" 2 | ## Column separator is "," 3 | 4 | # sha256("admin-user") 5 | f470213d9ae659187a19b9cb2169b4b400544f4d3f59250eda657154700da616,tenant1|user1,"*:*","admin" 6 | # sha256("guest-user") 7 | 8605f70ff5f55e2a9323d97de3dbf8e61f38314d93298ca00e19f8918fe8971b,tenant2|user2,"*:secrets *:keys read:ethereum","anonymous" 8 | -------------------------------------------------------------------------------- /deps/pgadmin/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": { 3 | "1": { 4 | "Name": "QKM", 5 | "Group": "QKM", 6 | "Port": 5432, 7 | "Username": "postgres", 8 | "Host": "postgres", 9 | "SSLMode": "prefer", 10 | "MaintenanceDB": "postgres", 11 | "PassFile": "/pgadmin4/password", 12 | "BGColor": "#3E5D6B", 13 | "FGColor": "#67C39B" 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/nodes/node/proxy/context_test.go: -------------------------------------------------------------------------------- 1 | package proxynode 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestContext(t *testing.T) { 11 | sess := SessionFromContext(context.Background()) 12 | assert.Nil(t, sess) 13 | 14 | s := &session{} 15 | ctx := WithSession(context.Background(), s) 16 | assert.Equal(t, s, SessionFromContext(ctx)) 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/service/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/infra/log" 5 | "github.com/consensys/quorum-key-manager/src/utils" 6 | ) 7 | 8 | type Utilities struct { 9 | logger log.Logger 10 | } 11 | 12 | var _ utils.Utilities = &Utilities{} 13 | 14 | func New(logger log.Logger) *Utilities { 15 | return &Utilities{ 16 | logger: logger, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deps/config/manifests/nodes.yml.template: -------------------------------------------------------------------------------- 1 | - kind: Node 2 | name: quorum-node 3 | specs: 4 | rpc: 5 | addr: http://quorum1:8545 6 | tessera: 7 | addr: http://tessera1:9080 8 | 9 | - kind: Node 10 | name: besu-node 11 | specs: 12 | rpc: 13 | addr: http://validator1:8545 14 | 15 | - kind: Node 16 | name: geth-node 17 | specs: 18 | rpc: 19 | addr: http://geth:8545 20 | -------------------------------------------------------------------------------- /src/nodes/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | //go:generate mockgen -source=node.go -destination=mock/node.go -package=mock 8 | 9 | // Node holds interface to connect to a downstream Quorum node including JSON-RPC server and private transaction manager 10 | type Node interface { 11 | // Proxy returns an JSON-RPC proxy to the downstream JSON-RPC node 12 | http.Handler 13 | } 14 | -------------------------------------------------------------------------------- /src/stores/entities/annotations.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type Annotation struct { 4 | AWSKeyID string `json:"AWSKeyID,omitempty"` 5 | AWSCustomKeyStoreID string `json:"AWSCustomKeyStoreID,omitempty"` 6 | AWSCloudHsmClusterID string `json:"AWSCloudHsmClusterID,omitempty"` 7 | AWSAccountID string `json:"AWSAccountID,omitempty"` 8 | AWSArn string `json:"AWSArn,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /src/infra/jwt/validator.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | //go:generate mockgen -source=validator.go -destination=mock/validator.go -package=mock 10 | 11 | type Validator interface { 12 | ValidateToken(ctx context.Context, token string) (interface{}, error) 13 | ParseClaims(interface{}) (*entities.UserClaims, error) 14 | } 15 | -------------------------------------------------------------------------------- /deps/cfssl/postgres.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "postgres", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "O": "Consensys", 12 | "OU": "Vault Tanuki", 13 | "ST": "Paris" 14 | } 15 | 16 | ], 17 | "hosts": [ 18 | "host1.example-company.com", 19 | "localhost", 20 | "postgres", 21 | "postgres-ssl" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /pkg/http/client/client_test.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestNew(t *testing.T) { 11 | cfg := new(Config).SetDefault() 12 | client, err := New(cfg, nil) 13 | require.NoError(t, err, "New must not error") 14 | assert.Implements(t, (*Client)(nil), client, "Client should match Client interface") 15 | } 16 | -------------------------------------------------------------------------------- /pkg/http/response/modifier_compression.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "compress/gzip" 5 | "net/http" 6 | ) 7 | 8 | func GZIP() Modifier { 9 | return ModifierFunc(func(resp *http.Response) error { 10 | if resp.Header.Get("Content-Encoding") == "gzip" { 11 | reader, err := gzip.NewReader(resp.Body) 12 | if err != nil { 13 | return err 14 | } 15 | resp.Body = reader 16 | } 17 | return nil 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -Eeu 5 | 6 | # Ignore generated & testutils files 7 | cat $1 | grep -Fv -e "_mock.go" -e "/tests" -e "/mock" -e "/testutils" -e "/testdata" > "$1.tmp" 8 | 9 | # Print total coverage 10 | go tool cover -func="$1.tmp" | grep total: 11 | 12 | # Generate coverage report in html format 13 | go tool cover -html="$1.tmp" -o $2 14 | 15 | cat "$1.tmp" > $1 16 | 17 | rm "$1.tmp" 18 | -------------------------------------------------------------------------------- /pkg/http/header/utils.go: -------------------------------------------------------------------------------- 1 | package header 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func Copy(dst, src http.Header) { 8 | for k, vv := range src { 9 | for _, v := range vv { 10 | dst.Add(k, v) 11 | } 12 | } 13 | } 14 | 15 | func FromMap(m map[string][]string) http.Header { 16 | header := make(http.Header) 17 | for k, vv := range m { 18 | for _, v := range vv { 19 | header.Add(k, v) 20 | } 21 | } 22 | return header 23 | } 24 | -------------------------------------------------------------------------------- /pkg/http/header/overide.go: -------------------------------------------------------------------------------- 1 | package header 2 | 3 | import "net/http" 4 | 5 | func Override(overrides map[string][]string) func(http.Header) error { 6 | return func(dst http.Header) error { 7 | for header, vv := range overrides { 8 | if len(vv) == 0 { 9 | dst.Del(header) 10 | } else { 11 | for _, v := range vv { 12 | if v != "" { 13 | dst.Set(header, v) 14 | } 15 | } 16 | } 17 | } 18 | 19 | return nil 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/aliases/service/aliases/parse.go: -------------------------------------------------------------------------------- 1 | package aliases 2 | 3 | import "strings" 4 | 5 | func (s *Aliases) Parse(alias string) (regName, aliasKey string, isAlias bool) { 6 | if strings.HasPrefix(alias, "{{") && strings.HasSuffix(alias, "}}") { 7 | chunks := strings.Split(alias[2:len(alias)-2], ":") 8 | 9 | if len(chunks) != 2 { 10 | return "", "", false 11 | } 12 | 13 | return chunks[0], chunks[1], true 14 | } 15 | 16 | return "", "", false 17 | } 18 | -------------------------------------------------------------------------------- /.github/actions/update-api-doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "update-api-doc", 3 | "version": "1.0.0", 4 | "description": "Update API Github Action", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@actions/core": "^1.2.6", 8 | "@actions/github": "^4.0.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "author": "Nicolas Massart", 15 | "license": "Apache-2.0" 16 | } 17 | -------------------------------------------------------------------------------- /deps/cfssl/vault.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "hashicorp", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "O": "Consensys", 12 | "OU": "Vault Tanuki", 13 | "ST": "Paris" 14 | } 15 | 16 | ], 17 | "hosts": [ 18 | "host1.example-company.com", 19 | "localhost", 20 | "127.0.0.1", 21 | "hashicorp", 22 | "vault", 23 | "vault.qa-qkm" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /pkg/http/response/json.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | func ReadJSON(resp *http.Response, msg interface{}) error { 10 | contentType := resp.Header.Get("Content-Type") 11 | switch contentType { 12 | case "application/json": 13 | defer resp.Body.Close() 14 | return json.NewDecoder(resp.Body).Decode(msg) 15 | default: 16 | return fmt.Errorf("invalid response Content-Type: %v", contentType) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deps/config/manifests/roles.yml.template: -------------------------------------------------------------------------------- 1 | - kind: Role 2 | name: anonymous 3 | specs: 4 | permissions: 5 | - "proxy:nodes" 6 | 7 | - kind: Role 8 | name: guest 9 | specs: 10 | permissions: 11 | - "read:*" 12 | 13 | - kind: Role 14 | name: signer 15 | specs: 16 | permissions: 17 | - "read:*" 18 | - "sign:keys" 19 | - "sign:ethereum" 20 | 21 | - kind: Role 22 | name: admin 23 | specs: 24 | permissions: 25 | - "*:*" 26 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_http_proto.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // HTTPProtocol sets HTTP protocol on request 9 | // 10 | // Example: HTTP/1.1 or HTTP/2. 11 | func HTTPProtocol(major, minor int) Preparer { 12 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 13 | req.Proto = fmt.Sprintf("HTTP/%v.%v", major, minor) 14 | req.ProtoMajor = major 15 | req.ProtoMinor = minor 16 | return req, nil 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/jsonrpc/proxy.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/http/proxy" 7 | ) 8 | 9 | // HandleProxyRoundTripError allows to transform a ProxiedRoundTrip Error 10 | func HandleProxyRoundTripError(rw http.ResponseWriter, req *http.Request, err error) { 11 | rpcRw, ok := rw.(ResponseWriter) 12 | if !ok { 13 | proxy.HandleRoundTripError(rw, req, err) 14 | } 15 | 16 | _ = WriteError(rpcRw, DownstreamError(err)) 17 | } 18 | -------------------------------------------------------------------------------- /src/stores/api/http/context.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "context" 4 | 5 | type ctxKeyType string 6 | 7 | const storeNameCtxKey ctxKeyType = "storeName" 8 | 9 | func WithStoreName(ctx context.Context, name string) context.Context { 10 | return context.WithValue(ctx, storeNameCtxKey, name) 11 | } 12 | 13 | func StoreNameFromContext(ctx context.Context) string { 14 | name, ok := ctx.Value(storeNameCtxKey).(string) 15 | if ok { 16 | return name 17 | } 18 | 19 | return "" 20 | } 21 | -------------------------------------------------------------------------------- /deps/cfssl/qkm.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "qkm", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "FR", 10 | "L": "Paris", 11 | "O": "admin", 12 | "OU": "*:*", 13 | "ST": "Paris" 14 | } 15 | 16 | ], 17 | "hosts": [ 18 | "host1.example-company.com", 19 | "qa-qkm.ops.consensys.net", 20 | "health-qa-qkm.ops.consensys.net", 21 | "localhost", 22 | "qkm", 23 | "quorum-key-manager" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/stores/entities/key.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "github.com/consensys/quorum-key-manager/src/entities" 4 | 5 | // Key public part of a key 6 | type Key struct { 7 | ID string 8 | PublicKey []byte 9 | Algo *entities.Algorithm 10 | Metadata *Metadata 11 | Tags map[string]string 12 | Annotations *Annotation 13 | } 14 | 15 | func (k *Key) IsETHAccount() bool { 16 | return k.Algo.EllipticCurve == entities.Secp256k1 && k.Algo.Type == entities.Ecdsa 17 | } 18 | -------------------------------------------------------------------------------- /deps/hashicorp/config/config-tls.hcl: -------------------------------------------------------------------------------- 1 | backend "file" { 2 | path = "/vault/file" 3 | } 4 | 5 | listener "tcp" { 6 | address = "hashicorp:8200" 7 | tls_disable = false 8 | tls_client_ca_file = "/vault/tls/ca.crt" 9 | tls_cert_file = "/vault/tls/tls.crt" 10 | tls_key_file = "/vault/tls/tls.key" 11 | } 12 | 13 | default_lease_ttl = "15m" 14 | max_lease_ttl = 99999999 15 | api_addr = "https://hashicorp:8200" 16 | plugin_directory = "/vault/plugins" 17 | log_level = "Debug" 18 | 19 | ui = false 20 | -------------------------------------------------------------------------------- /deps/hashicorp/config/agent-config.hcl: -------------------------------------------------------------------------------- 1 | exit_after_auth = false 2 | 3 | vault { 4 | address = "http://hashicorp:8200" 5 | } 6 | 7 | auto_auth { 8 | method "approle" { 9 | config = { 10 | role_id_file_path = "/vault/token/role" 11 | secret_id_file_path = "/vault/token/secret" 12 | remove_secret_id_file_after_reading = false 13 | } 14 | } 15 | 16 | sink "file" { 17 | config = { 18 | path = "/vault/token/.vault-token" 19 | mode = 0666 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/nodes/node/proxy/context.go: -------------------------------------------------------------------------------- 1 | package proxynode 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type ctxKeyType string 8 | 9 | const ( 10 | ctxSessionKey ctxKeyType = "session" 11 | ) 12 | 13 | func SessionFromContext(ctx context.Context) Session { 14 | n, ok := ctx.Value(ctxSessionKey).(Session) 15 | if !ok { 16 | return nil 17 | } 18 | 19 | return n 20 | } 21 | 22 | func WithSession(ctx context.Context, n Session) context.Context { 23 | return context.WithValue(ctx, ctxSessionKey, n) 24 | } 25 | -------------------------------------------------------------------------------- /deps/hashicorp/config/agent-config-tls.hcl: -------------------------------------------------------------------------------- 1 | exit_after_auth = false 2 | 3 | vault { 4 | address = "https://hashicorp:8200" 5 | } 6 | 7 | auto_auth { 8 | method "approle" { 9 | config = { 10 | role_id_file_path = "/vault/token/role" 11 | secret_id_file_path = "/vault/token/secret" 12 | remove_secret_id_file_after_reading = false 13 | } 14 | } 15 | 16 | sink "file" { 17 | config = { 18 | path = "/vault/token/.vault-token" 19 | mode = 0666 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/auth/api/http/no_auth.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | type NoAuth struct { 10 | } 11 | 12 | func NewNoAuth() *NoAuth { 13 | return &NoAuth{} 14 | } 15 | 16 | func (m *NoAuth) Middleware(next http.Handler) http.Handler { 17 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 18 | next.ServeHTTP(rw, r.WithContext(WithUserInfo(r.Context(), entities.NewWildcardUser()))) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/auth/service/roles/list.go: -------------------------------------------------------------------------------- 1 | package roles 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (i *Roles) List(_ context.Context, _ *entities.UserInfo) ([]string, error) { 10 | // TODO: Implement {Resource/Role}BAC for roles 11 | 12 | roles := make([]string, 0, len(i.roles)) 13 | for role := range i.roles { 14 | roles = append(roles, role) 15 | } 16 | 17 | i.logger.Debug("roles listed successfully") 18 | return roles, nil 19 | } 20 | -------------------------------------------------------------------------------- /pkg/http/response/modifier_headers.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/http/header" 7 | ) 8 | 9 | func HeadersModifier(h func(http.Header) error) Modifier { 10 | return ModifierFunc(func(resp *http.Response) error { 11 | return h(resp.Header) 12 | }) 13 | } 14 | 15 | // Headers sets or deletes custom request headers 16 | func Headers(overrides map[string][]string) Modifier { 17 | return HeadersModifier(header.Override(overrides)) 18 | } 19 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Concepts/ArchitectureOverview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture 3 | description: Quorum Key Manager architecture 4 | sidebar_position: 1 5 | --- 6 | 7 | # Quorum Key Manager architecture 8 | 9 | The following diagram outlines the Quorum Key Manager high-level architecture. 10 | 11 | ![Architecture](../Images/Architecture.png) 12 | 13 | For more information about the Quorum Key Manager architecture, contact us on [Quorum Key Manager Discord channel](https://discord.com/invite/consensys). 14 | -------------------------------------------------------------------------------- /tests/acceptance/docker/container/container.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/docker/docker/api/types/container" 8 | "github.com/docker/docker/api/types/network" 9 | ) 10 | 11 | type DockerContainerFactory interface { 12 | GenerateContainerConfig(ctx context.Context, configuration interface{}) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) 13 | WaitForService(ctx context.Context, configuration interface{}, timeout time.Duration) error 14 | } 15 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_headers.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/http/header" 7 | ) 8 | 9 | func HeadersPreparer(h func(http.Header) error) Preparer { 10 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 11 | return req, h(req.Header) 12 | }) 13 | } 14 | 15 | // Headers sets or deletes custom request headers 16 | func Headers(overrides map[string][]string) Preparer { 17 | return HeadersPreparer(header.Override(overrides)) 18 | } 19 | -------------------------------------------------------------------------------- /src/infra/jwt/jose/config.go: -------------------------------------------------------------------------------- 1 | package jose 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Config struct { 8 | IssuerURL string 9 | CacheTTL time.Duration 10 | Audience []string 11 | CustomClaimPath string 12 | } 13 | 14 | func NewConfig(issuerURL string, audience []string, customClaimPath string, cacheTTL time.Duration) *Config { 15 | return &Config{ 16 | IssuerURL: issuerURL, 17 | CacheTTL: cacheTTL, 18 | Audience: audience, 19 | CustomClaimPath: customClaimPath, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator1/accountkey: -------------------------------------------------------------------------------- 1 | {"address":"ed9d02e382b34818e88b88a309c7fe71e65f419d","crypto":{"cipher":"aes-128-ctr","ciphertext":"4e77046ba3f699e744acb4a89c36a3ea1158a1bd90a076d36675f4c883864377","cipherparams":{"iv":"a8932af2a3c0225ee8e872bc0e462c11"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8ca49552b3e92f79c51f2cd3d38dfc723412c212e702bd337a3724e8937aff0f"},"mac":"6d1354fef5aa0418389b1a5d1f5ee0050d7273292a1171c51fd02f9ecff55264"},"id":"a65d1ac3-db7e-445d-a1cc-b6c5eeaa05e0","version":3} -------------------------------------------------------------------------------- /pkg/http/request/preparer_host.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // Host set request Host 8 | // This is useful when proxying request (to set the request host to match downstream server) 9 | // If host is nil then it sets the Host to request URL host 10 | func Host(host *string) Preparer { 11 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 12 | if host == nil { 13 | req.Host = req.URL.Host 14 | } else { 15 | req.Host = *host 16 | } 17 | return req, nil 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator2/accountkey: -------------------------------------------------------------------------------- 1 | {"address":"b30f304642de3fee4365ed5cd06ea2e69d3fd0ca","crypto":{"cipher":"aes-128-ctr","ciphertext":"cf7f44a86510c497f2a6727f62b8e89c8cd42dbfd8e3377658d659c776c75f30","cipherparams":{"iv":"5d320e3072939817faa2caefdb810239"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"e637b069dacfe77f3a495d8a0d3735544e73973c03e107976545df78e6f04289"},"mac":"e20c97b8ee7d54072b9cfac201d52ef360ecacdbd1f7187e7547b3cd9be00599"},"id":"5bf73d6b-340b-4e9f-a7f3-f9ab41f39726","version":3} 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator3/accountkey: -------------------------------------------------------------------------------- 1 | {"address":"0886328869e4e1f401e1052a5f4aae8b45f42610","crypto":{"cipher":"aes-128-ctr","ciphertext":"6e0e228b810a88f2cea85c439b005a92bba6220de8cd6ba73a8f7ecd681fde09","cipherparams":{"iv":"5264c2defa48f7f9fa103899acaea021"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"15ef956dab453387ddf7afd6775f3727fb560a77028b5a14d774c3bfff17f101"},"mac":"6ca1d88defee18378789d191ca7b32bbecbd6dd5a6c39427ed45b13c9595edc3"},"id":"734d1189-4e1e-44bf-854a-642485532715","version":3} 2 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/networkFiles/validator4/accountkey: -------------------------------------------------------------------------------- 1 | {"address":"f48de4a0c2939e62891f3c6aca68982975477e45","crypto":{"cipher":"aes-128-ctr","ciphertext":"402a2d8eb1ff6bab713ddb81f68142c4f0113d32e9f0cc8969347a4945cd1e5f","cipherparams":{"iv":"a1f708815212854c0c49081869198dd5"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"a24d95de8680d8d214ae5e9060e919eb563ce513bf63021497e0d218ed68f89d"},"mac":"571164da79472f210caeb45af2b9715fb22eef8d485ce5c6a4e0c52865398ea7"},"id":"d5723032-0a81-46ec-ac34-9d8362e2250c","version":3} 2 | -------------------------------------------------------------------------------- /src/auth/api/http/context.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | type contextKey struct{} 10 | 11 | func UserInfoFromContext(ctx context.Context) *entities.UserInfo { 12 | if reqCtx, ok := ctx.Value(contextKey{}).(*entities.UserInfo); ok { 13 | return reqCtx 14 | } 15 | return nil 16 | } 17 | 18 | func WithUserInfo(ctx context.Context, reqCtx *entities.UserInfo) context.Context { 19 | return context.WithValue(ctx, contextKey{}, reqCtx) 20 | } 21 | -------------------------------------------------------------------------------- /src/auth/service/roles/get.go: -------------------------------------------------------------------------------- 1 | package roles 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (i *Roles) Get(ctx context.Context, name string, _ *entities.UserInfo) (*entities.Role, error) { 10 | logger := i.logger.With("name", name) 11 | 12 | // TODO: Implement {Resource/Role}BAC for roles 13 | 14 | vault, err := i.getRole(ctx, name) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | logger.Debug("role found successfully") 20 | return vault, nil 21 | } 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## PR description 5 | 6 | ## Fixed Issue(s) 7 | 8 | 9 | 10 | ## Changelog 11 | 12 | - [ ] I thought about the changelog and included a [changelog update if required](https://github.com/consensys/quorum-key-manager/blob/master/CHANGELOG.md). -------------------------------------------------------------------------------- /src/utils/app/service.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/infra/log" 5 | "github.com/consensys/quorum-key-manager/src/utils/api/http" 6 | "github.com/consensys/quorum-key-manager/src/utils/service/utils" 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | func RegisterService(router *mux.Router, logger log.Logger) *utils.Utilities { 11 | // Business layer 12 | utilsService := utils.New(logger) 13 | 14 | // Service layer 15 | http.NewUtilsHandler(utilsService).Register(router) 16 | 17 | return utilsService 18 | } 19 | -------------------------------------------------------------------------------- /pkg/common/arrays.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // Diff returns the elements in `a` that aren't in `b` 4 | // Good algorithm as it's O(n) instead of a naive O(n2) 5 | func Diff(a, b []string) []string { 6 | mb := ToMap(b) 7 | 8 | var diff []string 9 | for _, x := range a { 10 | if _, found := mb[x]; !found { 11 | diff = append(diff, x) 12 | } 13 | } 14 | return diff 15 | } 16 | 17 | func ToMap(a []string) map[string]struct{} { 18 | mb := make(map[string]struct{}, len(a)) 19 | for _, x := range a { 20 | mb[x] = struct{}{} 21 | } 22 | 23 | return mb 24 | } 25 | -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-24.010489227Z--7e654d251da770a068413677967f6d3ea2fea9e4: -------------------------------------------------------------------------------- 1 | {"address":"7e654d251da770a068413677967f6d3ea2fea9e4","crypto":{"cipher":"aes-128-ctr","ciphertext":"d6f726e0f8855550d121f3e173f1d3ae2da5b7c972bfbec611a984b78b69b5eb","cipherparams":{"iv":"3e322d22e2013549eab44a3d11365d46"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"7db66650951384077634b2222af4ac0d24f2d0a464ae0a10d078f22349458be0"},"mac":"fda71fd859f20ceecb90fa400ac59bf505dd4cabf0ba1eafa43f948a994697aa"},"id":"646311d4-5e97-4e87-983e-c58d69b88121","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-25.640448102Z--ff778b716fc07d98839f48ddb88d8be583beb684: -------------------------------------------------------------------------------- 1 | {"address":"ff778b716fc07d98839f48ddb88d8be583beb684","crypto":{"cipher":"aes-128-ctr","ciphertext":"6ad63ecbca297a134b8f089931032efa2505ede6dc7186d79b140dd57d7848f7","cipherparams":{"iv":"c873727891a498872a37f1365fadd026"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"6fe7b0c6c9913348b73dc3125fd7001576fe613b683a5ad55da5d509c507e14e"},"mac":"c5a888a0db3e38b3d4b1f348b82a2d2c06275b4787b9b8bc0503bf17b1e05733"},"id":"75775da1-60e5-425d-b2b8-625353ff3102","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-27.070140387Z--664895b5fe3ddf049d2fb508cfa03923859763c6: -------------------------------------------------------------------------------- 1 | {"address":"664895b5fe3ddf049d2fb508cfa03923859763c6","crypto":{"cipher":"aes-128-ctr","ciphertext":"69cf979b9e4fd251b8d6fc9e85928af91ef300f11f863a22999ec6bf730f67a5","cipherparams":{"iv":"1f58072399b3071f4c72c19b61ffd458"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"edb35cd57156787825e50ca98ac0e6998a42037fbf1d6020442020679825527a"},"mac":"65fe5bd9d75008e741a9927072f3bba3b01555a88db12a9825549286803dd010"},"id":"100eab9d-a320-484b-9f5c-aa39be76af74","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-28.571951431Z--f5956eb46b377ae41b41bda94e6270208d8202bb: -------------------------------------------------------------------------------- 1 | {"address":"f5956eb46b377ae41b41bda94e6270208d8202bb","crypto":{"cipher":"aes-128-ctr","ciphertext":"685a7075fd11d994f0cfc77db89c1b12df443c2532a9cf046325dca0936c5a47","cipherparams":{"iv":"10555290374c95332513f8ca6dde6126"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"fa8248242643370be811333860a8279810a23860ec6d09cc5dc076621373ff20"},"mac":"82fd6b68013b918be289af29a691f37a15939df6cf97482f31ec93073a6d9618"},"id":"5b4f104c-930f-4ad2-a664-2d97df912ce3","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-30.118221907Z--93f7274c9059e601be4512f656b57b830e019e41: -------------------------------------------------------------------------------- 1 | {"address":"93f7274c9059e601be4512f656b57b830e019e41","crypto":{"cipher":"aes-128-ctr","ciphertext":"6d8e724627e00ff8ef080ce7ff99c2b030ebf990207505d9d1d0f932f75083ed","cipherparams":{"iv":"dd5b18f1e348460d1e461e0060a84300"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"0e78b5e5e5bebd52f42c5f3e4ad90d17bf2ce9284c04a69153d9368d531d9c27"},"mac":"666e21b64e68466aa13cc6aa03e8c1eddfb6550ab972bcb2480cc232a36e8e9d"},"id":"d82a8995-a375-4c60-9cb7-16ef3beadbf3","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-31.614359712Z--bfc7137876d7ac275019d70434b0f0779824a969: -------------------------------------------------------------------------------- 1 | {"address":"bfc7137876d7ac275019d70434b0f0779824a969","crypto":{"cipher":"aes-128-ctr","ciphertext":"e3d17442508a5489b8eb8d094fd4c6072f4800d6a51c512a7bf8039c8fa81fd5","cipherparams":{"iv":"884e8b4bdfaf1863dd8a71e4cf563948"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"f87aaa3d996d4c64ecc5626324d6cb7a33f0315653040b45e938519ccf018a62"},"mac":"062210f8b7aa7fbcb8c5331c96d23e31899e0f94df63459f9a1d36732b5f4d4d"},"id":"71639c81-ab5b-4f0f-be3a-84e4c4c5633b","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-33.201986679Z--a8d8db1d8919665a18212374d623fc7c0dfda410: -------------------------------------------------------------------------------- 1 | {"address":"a8d8db1d8919665a18212374d623fc7c0dfda410","crypto":{"cipher":"aes-128-ctr","ciphertext":"8bc3de4fc583e44ccaafeabdb11a97e0798bb6548669a0b33e600c894b5a3f6e","cipherparams":{"iv":"588be3903d8ca6a1ade33a706510a349"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"a6c010e087936702e1d724be0a7139be93b1c3c3678a2dc3dfbed354c17317fd"},"mac":"5cc77f6582aa102517d4d1052009142943749509cd076f21e197b8e47d327fd7"},"id":"fd3c15e4-74bf-42dc-9c32-9518cc0ae5df","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-12-34.637398304Z--ffbba394def3ff1df0941c6429887107f58d4e9b: -------------------------------------------------------------------------------- 1 | {"address":"ffbba394def3ff1df0941c6429887107f58d4e9b","crypto":{"cipher":"aes-128-ctr","ciphertext":"1784492639e0c85581c4416fbf324b905f1b7ad7fe1207d6b7372566ebe362be","cipherparams":{"iv":"b1185845161f05e8abf0ec3815f5ad58"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"63a3e22c1902b421210d259798a24c958c8cd1a2c70cfaf78e90fbf8e84699c3"},"mac":"bd22fab1df6d524f20be2e755b773ca3592fc855146c8df0013d4571aec02059"},"id":"9935f0ad-82bf-4b26-b1eb-7203151dbf5a","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-14-57.148249075Z--dbb881a51cd4023e4400cef3ef73046743f08da3: -------------------------------------------------------------------------------- 1 | {"address":"dbb881a51cd4023e4400cef3ef73046743f08da3","crypto":{"cipher":"aes-128-ctr","ciphertext":"74dc5c68ce76e740fe64e9feb1988d47b1b01d4d0a40718a3202fe22b155982b","cipherparams":{"iv":"385472a83fc01509fb8061df6536525a"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"197d96172e8bd7edf0ada50af94318fc15a800666f3b2ff8606df7cf52dc0b86"},"mac":"681775134e8257f73992d75b13902d20c9f7d018df02eb814b295ed109bcc5fa"},"id":"6e52009b-e312-4f9d-b8d0-f135b309f523","version":3} -------------------------------------------------------------------------------- /deps/geth/config/keystore/UTC--2019-06-28T21-14-58.600072289Z--6009608a02a7a15fd6689d6dad560c44e9ab61ff: -------------------------------------------------------------------------------- 1 | {"address":"6009608a02a7a15fd6689d6dad560c44e9ab61ff","crypto":{"cipher":"aes-128-ctr","ciphertext":"9aa0ee26b26e6537fe0d2a523a313acdb9a0c672c62c3f4d3bc7e4b398a3d64c","cipherparams":{"iv":"1afce84c8d2bfa3ce4bdf82529b3ab6f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"ac67a32cf648003cc21304ef30e6505ec6e4e85810ddf353e68659d09dbe5f32"},"mac":"e50116f4968dfb5cd7115092082fa5b9754f9e5a4b43b4d9bb7cd06af4a8cbad"},"id":"6e0e993e-f53f-46b0-acaa-de2962b4c86b","version":3} -------------------------------------------------------------------------------- /src/auth/entities/permission_test.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestListWildcardPermission(t *testing.T) { 10 | list := ListWildcardPermission("*:*") 11 | assert.Equal(t, list, ListPermissions()) 12 | 13 | list = ListWildcardPermission("read:*") 14 | assert.Equal(t, list, []Permission{ReadSecret, ReadKey, ReadEth, ReadAlias}) 15 | 16 | list = ListWildcardPermission("*:ethereum") 17 | assert.Equal(t, list, []Permission{ReadEth, WriteEth, DeleteEth, DestroyEth, SignEth, EncryptEth}) 18 | } 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | labels: 13 | - dependencies 14 | target-branch: "main" 15 | -------------------------------------------------------------------------------- /pkg/tls/verify_ca.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "crypto/x509" 5 | ) 6 | 7 | func VerifyCertificateAuthority(certs []*x509.Certificate, serverName string, rootCAs *x509.CertPool, skipVerify bool) error { 8 | opts := x509.VerifyOptions{ 9 | Intermediates: x509.NewCertPool(), 10 | Roots: rootCAs, 11 | } 12 | 13 | if !skipVerify { 14 | opts.DNSName = serverName 15 | } 16 | 17 | for i, cert := range certs { 18 | if i == 0 { 19 | continue 20 | } 21 | opts.Intermediates.AddCert(cert) 22 | } 23 | 24 | _, err := certs[0].Verify(opts) 25 | 26 | return err 27 | } 28 | -------------------------------------------------------------------------------- /tests/acceptance/docker/config/postgres/postgres.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | const ( 4 | DefaultPostgresImage = "postgres:13.3-alpine" 5 | defaultPassword = "postgres" 6 | defaultHostPort = "5432" 7 | ) 8 | 9 | type Config struct { 10 | Image string 11 | Port string 12 | Password string 13 | } 14 | 15 | func NewDefault() *Config { 16 | return &Config{ 17 | Image: DefaultPostgresImage, 18 | Port: defaultHostPort, 19 | Password: defaultPassword, 20 | } 21 | } 22 | 23 | func (c *Config) SetPort(port string) *Config { 24 | c.Port = port 25 | return c 26 | } 27 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/README.md: -------------------------------------------------------------------------------- 1 | # ConsenSys Quorum Key Manager 2 | 3 | This documentation repo is built using [Docusaurus 2](https://docusaurus.io/). 4 | 5 | ### Local Development 6 | 7 | $ npm install 8 | $ npm run prepare 9 | $ npm start 10 | 11 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 12 | 13 | ### Build 14 | 15 | $ npm run build 16 | 17 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 18 | -------------------------------------------------------------------------------- /pkg/common/pointer.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "reflect" 4 | 5 | func Tomapstrptr(m map[string]string) map[string]*string { 6 | nm := make(map[string]*string) 7 | for k, v := range m { 8 | nm[k] = &(&struct{ x string }{v}).x 9 | } 10 | return nm 11 | } 12 | 13 | func Tomapstr(m map[string]*string) map[string]string { 14 | nm := make(map[string]string) 15 | for k, v := range m { 16 | nm[k] = *v 17 | } 18 | return nm 19 | } 20 | 21 | func ToPtr(v interface{}) interface{} { 22 | p := reflect.New(reflect.TypeOf(v)) 23 | p.Elem().Set(reflect.ValueOf(v)) 24 | return p.Interface() 25 | } 26 | -------------------------------------------------------------------------------- /src/stores/api/types/stores.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type CreateSecretStoreRequest struct { 4 | Vault string `json:"vault" validate:"required" yaml:"vault" example:"hashicorp-kv-v2"` 5 | } 6 | 7 | type CreateKeyStoreRequest struct { 8 | SecretStore string `json:"secretStore,omitempty" yaml:"secret_store,omitempty" example:"my-secret-store"` 9 | Vault string `json:"vault,omitempty" yaml:"vault,omitempty" example:"hashicorp-quorum"` 10 | } 11 | 12 | type CreateEthereumStoreRequest struct { 13 | KeyStore string `json:"keyStore" yaml:"key_store" validate:"required" example:"my-key-store"` 14 | } 15 | -------------------------------------------------------------------------------- /cmd/flags/app.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | app "github.com/consensys/quorum-key-manager/src" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | func NewAppConfig(vipr *viper.Viper) (*app.Config, error) { 9 | httpCfg, err := newHTTPConfig(vipr) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | return &app.Config{ 15 | Logger: NewLoggerConfig(vipr), 16 | HTTP: httpCfg, 17 | Manifest: NewManifestConfig(vipr), 18 | OIDC: NewOIDCConfig(vipr), 19 | APIKey: NewAPIKeyConfig(vipr), 20 | TLS: NewTLSConfig(vipr), 21 | Postgres: NewPostgresConfig(vipr), 22 | }, nil 23 | } 24 | -------------------------------------------------------------------------------- /src/auth/api/http/accesslog.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/gorilla/handlers" 8 | ) 9 | 10 | // TODO: Make accesslog middleware configurable (at least enable/disable) 11 | // TODO: Move to the metrics/monitoring domain when it exists 12 | 13 | type AccessLog struct { 14 | logger io.Writer 15 | } 16 | 17 | func NewAccessLog(accessLogger io.Writer) *AccessLog { 18 | return &AccessLog{ 19 | logger: accessLogger, 20 | } 21 | } 22 | 23 | func (a *AccessLog) Middleware(next http.Handler) http.Handler { 24 | return handlers.LoggingHandler(a.logger, next) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_url.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | ) 7 | 8 | // URL set request URL to the passed URL 9 | func URL(u *url.URL) Preparer { 10 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 11 | newReq := *req 12 | newReq.URL = CopyURL(u) 13 | if req.RequestURI == "" { 14 | newReq.RequestURI = req.URL.RequestURI() 15 | } 16 | return &newReq, nil 17 | }) 18 | } 19 | 20 | func CopyURL(i *url.URL) *url.URL { 21 | out := *i 22 | if i.User != nil { 23 | out.User = new(url.Userinfo) 24 | *out.User = *i.User 25 | } 26 | return &out 27 | } 28 | -------------------------------------------------------------------------------- /tests/acceptance/docker/config/composition.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/tests/acceptance/docker/config/hashicorp" 5 | "github.com/consensys/quorum-key-manager/tests/acceptance/docker/config/postgres" 6 | "github.com/consensys/quorum-key-manager/tests/acceptance/utils" 7 | ) 8 | 9 | type Composition struct { 10 | Containers map[string]*Container 11 | } 12 | 13 | type Container struct { 14 | HashicorpVault *hashicorp.Config 15 | Postgres *postgres.Config 16 | } 17 | 18 | func (c *Container) Field() (interface{}, error) { 19 | return utils.ExtractField(c) 20 | } 21 | -------------------------------------------------------------------------------- /src/infra/log/zap/config.go: -------------------------------------------------------------------------------- 1 | package zap 2 | 3 | type LoggerLevel string 4 | type LoggerFormat string 5 | 6 | const ( 7 | InfoLevel LoggerLevel = "info" 8 | ErrorLevel LoggerLevel = "error" 9 | DebugLevel LoggerLevel = "debug" 10 | WarnLevel LoggerLevel = "warn" 11 | PanicLevel LoggerLevel = "panic" 12 | ) 13 | 14 | const ( 15 | TextFormat LoggerFormat = "text" 16 | JSONFormat LoggerFormat = "json" 17 | ) 18 | 19 | type Config struct { 20 | Level LoggerLevel 21 | Format LoggerFormat 22 | } 23 | 24 | func NewConfig(level LoggerLevel, format LoggerFormat) *Config { 25 | return &Config{ 26 | Level: level, 27 | Format: format, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanUrls": true, 3 | "redirects": [ 4 | { "source": "/en/latest", "destination": "/", "permanent": true }, 5 | { "source": "/en/latest/", "destination": "/", "permanent": true }, 6 | { 7 | "source": "/en/latest/:match(.*)", 8 | "destination": "/:match(.*)", 9 | "permanent": true 10 | }, 11 | { "source": "/en/stable", "destination": "/", "permanent": true }, 12 | { "source": "/en/stable/", "destination": "/", "permanent": true }, 13 | { 14 | "source": "/en/stable/:match(.*)", 15 | "destination": "/:match(.*)", 16 | "permanent": true 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /pkg/json/duration.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // Duration is a duration that can be Marshal and Unmarshal 10 | type Duration struct { 11 | time.Duration 12 | } 13 | 14 | func (d *Duration) UnmarshalJSON(b []byte) (err error) { 15 | if b[0] == '"' { 16 | sd := string(b[1 : len(b)-1]) 17 | d.Duration, err = time.ParseDuration(sd) 18 | return 19 | } 20 | 21 | var id int64 22 | id, err = json.Number(b).Int64() 23 | d.Duration = time.Duration(id) 24 | 25 | return 26 | } 27 | 28 | func (d Duration) MarshalJSON() (b []byte, err error) { 29 | return []byte(fmt.Sprintf(`"%s"`, d.String())), nil 30 | } 31 | -------------------------------------------------------------------------------- /src/entities/manifest.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | const ( 4 | RoleKind string = "Role" 5 | NodeKind string = "Node" 6 | StoreKind string = "Store" 7 | VaultKind string = "Vault" 8 | ) 9 | 10 | type Manifest struct { 11 | Kind string `yaml:"kind" validate:"required,isManifestKind" example:"store"` 12 | Name string `yaml:"name" validate:"required" example:"my-store"` 13 | ResourceType string `yaml:"type,omitempty" example:"ethereum"` 14 | Specs interface{} `yaml:"specs" validate:"required"` 15 | AllowedTenants []string `json:"allowedTenants,omitempty" yaml:"allowed_tenants,omitempty" example:"tenant1,tenant2"` 16 | } 17 | -------------------------------------------------------------------------------- /pkg/http/client/config.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/http/transport" 5 | "github.com/consensys/quorum-key-manager/pkg/json" 6 | ) 7 | 8 | // Config for creating an HTTP Client 9 | type Config struct { 10 | Transport *transport.Config `json:"transport,omitempty"` 11 | Timeout *json.Duration `json:"timeout,omitempty"` 12 | } 13 | 14 | func (cfg *Config) SetDefault() *Config { 15 | if cfg.Transport == nil { 16 | cfg.Transport = new(transport.Config) 17 | } 18 | cfg.Transport.SetDefault() 19 | 20 | if cfg.Timeout == nil { 21 | cfg.Timeout = &json.Duration{Duration: 0} 22 | } 23 | 24 | return cfg 25 | } 26 | -------------------------------------------------------------------------------- /src/infra/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | //go:generate mockgen -source=logger.go -destination=mock/logger.go -package=mocks 4 | 5 | type Logger interface { 6 | WithComponent(component string) Logger 7 | Debug(msg string, keysAndValues ...interface{}) Logger 8 | Warn(msg string, keysAndValues ...interface{}) Logger 9 | Info(msg string, keysAndValues ...interface{}) Logger 10 | Error(msg string, keysAndValues ...interface{}) Logger 11 | Panic(msg string, keysAndValues ...interface{}) Logger 12 | Fatal(msg string, keysAndValues ...interface{}) Logger 13 | WithError(err error) Logger 14 | With(args ...interface{}) Logger 15 | Write(p []byte) (n int, err error) 16 | } 17 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/decrypt.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (c Connector) Decrypt(ctx context.Context, id string, data []byte) ([]byte, error) { 10 | logger := c.logger.With("id", id) 11 | 12 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionEncrypt, Resource: entities.ResourceKey}) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | result, err := c.store.Decrypt(ctx, id, data) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | logger.Debug("data decrypted successfully") 23 | return result, nil 24 | } 25 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/encrypt.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (c Connector) Encrypt(ctx context.Context, id string, data []byte) ([]byte, error) { 10 | logger := c.logger.With("id", id) 11 | 12 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionEncrypt, Resource: entities.ResourceKey}) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | result, err := c.store.Encrypt(ctx, id, data) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | logger.Debug("data encrypted successfully") 23 | return result, nil 24 | } 25 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["stylelint-config-standard", "stylelint-config-prettier"], 3 | rules: { 4 | "selector-pseudo-class-no-unknown": [ 5 | true, 6 | { 7 | // :global is a CSS modules feature to escape from class name hashing 8 | ignorePseudoClasses: ["global"], 9 | }, 10 | ], 11 | "selector-class-pattern": null, 12 | "custom-property-empty-line-before": null, 13 | "selector-id-pattern": null, 14 | "declaration-empty-line-before": null, 15 | "comment-empty-line-before": null, 16 | "value-keyword-case": ["lower", { camelCaseSvgKeywords: true }], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /deps/docker-compose-tools.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | pgadmin: 5 | image: dpage/pgadmin4:4.27 6 | environment: 7 | PGADMIN_DEFAULT_EMAIL: pgadmin4@pgadmin.org 8 | PGADMIN_DEFAULT_PASSWORD: admin 9 | PGADMIN_SERVER_JSON_FILE: /pgadmin4/servers.json 10 | restart: unless-stopped 11 | volumes: 12 | - pgadmin:/root/.pgadmin 13 | - ./pgadmin/servers.json:/pgadmin4/servers.json 14 | - ./pgadmin/password:/pgadmin4/password 15 | ports: 16 | - 9001:80 17 | networks: 18 | - qkm 19 | 20 | volumes: 21 | pgadmin: 22 | driver: local 23 | 24 | networks: 25 | qkm: 26 | external: 27 | name: deps_qkm 28 | -------------------------------------------------------------------------------- /pkg/http/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func New(cfg *Config) *http.Server { 9 | server := &http.Server{ 10 | Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), 11 | IdleTimeout: cfg.IdleConnTimeout, 12 | ReadTimeout: cfg.Timeout, 13 | } 14 | 15 | if cfg.TLSConfig != nil { 16 | server.TLSConfig = cfg.TLSConfig 17 | } 18 | 19 | return server 20 | } 21 | 22 | func NewHealthz(cfg *Config) *http.Server { 23 | server := &http.Server{ 24 | Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.HealthzPort), 25 | IdleTimeout: cfg.IdleConnTimeout, 26 | ReadTimeout: cfg.Timeout, 27 | } 28 | 29 | return server 30 | } 31 | -------------------------------------------------------------------------------- /src/infra/http/types.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | type ErrorResponse struct { 4 | Message string `json:"message" example:"error message"` 5 | Code string `json:"code,omitempty" example:"IR001"` 6 | } 7 | 8 | type PageResponse struct { 9 | Data interface{} `json:"data" example:"{'item1', 'item2', 'item3'}" swaggertype:"array,string"` 10 | Paging PagePagingResponse `json:"paging"` 11 | } 12 | 13 | type PagePagingResponse struct { 14 | Previous string `json:"previous,omitempty" example:"https://quorum-key-manager.com/stores/your-store/secrets?page=1"` 15 | Next string `json:"next,omitempty" example:"https://quorum-key-manager.com/stores/your-store/secrets?page=3"` 16 | } 17 | -------------------------------------------------------------------------------- /src/aliases/service/registries/registries.go: -------------------------------------------------------------------------------- 1 | package registries 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/aliases" 5 | "github.com/consensys/quorum-key-manager/src/aliases/database" 6 | "github.com/consensys/quorum-key-manager/src/auth" 7 | "github.com/consensys/quorum-key-manager/src/infra/log" 8 | ) 9 | 10 | type Registries struct { 11 | db database.Registry 12 | logger log.Logger 13 | roles auth.Roles 14 | } 15 | 16 | var _ aliases.Registries = &Registries{} 17 | 18 | func New(db database.Registry, rolesService auth.Roles, logger log.Logger) *Registries { 19 | return &Registries{ 20 | db: db, 21 | logger: logger, 22 | roles: rolesService, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /deps/besu/config/besu/permissions_config.toml: -------------------------------------------------------------------------------- 1 | nodes-whitelist=[ 2 | "enode://c1979a8a48693db804316b5acebe35e11731e1fb1c9c21ff7268ab25db6f6e03390a429b83cf0ec0865a7205f2669ec1ace652a3def11e2e01571c74939cbe22@172.16.237.11:30303", 3 | "enode://e40129f02c9e29a02049668346d4777bb55809042746882b33b20a8b5a7310eb5f107a53f0aa3da766ee77f401557a79c0c328329ea48bf0996c6c9dff817f76@172.16.237.12:30303", 4 | "enode://a3e4af081a0ab853c959b9acd0596f818b91a9409b9d04c50af055072c929abfa340e14111dcfa76e049fdb16bb9198e722d5e7be3e8ef37562ea0d0ce1eda11@172.16.237.13:30303", 5 | "enode://8f4e444a73034236ab4244c7a572aa2c6198b9e0d483ef17bf4b751cac5c0370bc527a5b0c5d01aa3ef41704af838c74730aeecac0f0c22dc4c17b0a9f03ad76@172.16.237.14:30303", 6 | ] 7 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report. 4 | title: "" 5 | labels: Documentation 6 | assignees: Ezzahhh 7 | --- 8 | 9 | ## Describe the bug 10 | 11 | 13 | 14 | ## System settings 15 | 16 | 17 | 18 | - OS and version: 19 | - Browser and version: 20 | - Plugins activated in your browser: 21 | 22 | ## More context 23 | 24 | 25 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Reference/Rest.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: REST API methods 3 | description: Quorum Key Manager REST API's 4 | sidebar_position: 2 5 | --- 6 | 7 | # REST API 8 | 9 | View the [REST API documentation] for more information about the available APIs. It contains: 10 | 11 | - Exposed HTTP endpoints. 12 | - Examples and expected format of HTTP requests and responses. 13 | - Possible error return codes by endpoint. 14 | 15 | You can use tools such as [Postman] or [cURL] to interact with Quorum Key Manager APIs. 16 | 17 | 18 | 19 | [REST API documentation]: https://consensys.github.io/quorum-key-manager/ 20 | [Postman]: https://www.postman.com/ 21 | [cURL]: https://curl.haxx.se/ 22 | -------------------------------------------------------------------------------- /src/entities/testutils/faker.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/common" 7 | "github.com/consensys/quorum-key-manager/src/entities" 8 | ) 9 | 10 | func FakeAlias(registry, key, kind string, value interface{}) *entities.Alias { 11 | return &entities.Alias{ 12 | Key: key, 13 | RegistryName: registry, 14 | Kind: kind, 15 | Value: value, 16 | } 17 | } 18 | 19 | func FakeAliasRegistry() *entities.AliasRegistry { 20 | return &entities.AliasRegistry{ 21 | Name: common.RandString(10), 22 | AllowedTenants: []string{"tenant_1"}, 23 | CreatedAt: time.Now(), 24 | UpdatedAt: time.Now(), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/service/utils/ec_recover.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/errors" 5 | ethcommon "github.com/ethereum/go-ethereum/common" 6 | "github.com/ethereum/go-ethereum/crypto" 7 | ) 8 | 9 | func (u *Utilities) ECRecover(data, sig []byte) (ethcommon.Address, error) { 10 | pubKey, err := crypto.SigToPub(crypto.Keccak256(data), sig) 11 | if err != nil { 12 | u.logger.WithError(err).Error("failed to recover public key, please verify your signature and payload") 13 | return ethcommon.Address{}, errors.InvalidParameterError(err.Error()) 14 | } 15 | 16 | u.logger.Debug("ethereum account recovered successfully") 17 | return crypto.PubkeyToAddress(*pubKey), nil 18 | } 19 | -------------------------------------------------------------------------------- /deps/besu/config/besu/log-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INFO 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /deps/besu/config/orion/log-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INFO 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/infra/akv/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" 5 | "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault/keyvaultapi" 6 | "github.com/consensys/quorum-key-manager/src/infra/akv" 7 | ) 8 | 9 | type AKVClient struct { 10 | client keyvaultapi.BaseClientAPI 11 | cfg *Config 12 | } 13 | 14 | var _ akv.Client = &AKVClient{} 15 | 16 | func NewClient(cfg *Config) (*AKVClient, error) { 17 | client := keyvault.New() 18 | 19 | authorizer, err := cfg.ToAzureAuthConfig() 20 | if err != nil { 21 | return nil, err 22 | } 23 | client.Authorizer = authorizer 24 | 25 | return &AKVClient{client: client, cfg: cfg}, nil 26 | } 27 | -------------------------------------------------------------------------------- /src/infra/postgres/client/parsers.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/errors" 5 | "github.com/go-pg/pg/v10" 6 | ) 7 | 8 | func parseErrorResponse(err error) error { 9 | if pg.ErrNoRows == err { 10 | return errors.NotFoundError("resource not found") 11 | } 12 | if pg.ErrMultiRows == err { 13 | return errors.StatusConflictError("multiple resources found, only expected one") 14 | } 15 | 16 | pgErr, ok := err.(pg.Error) 17 | if !ok { 18 | return errors.PostgresError(err.Error()) 19 | } 20 | 21 | switch { 22 | case pgErr.IntegrityViolation(): 23 | return errors.StatusConflictError(pgErr.Error()) 24 | default: 25 | return errors.PostgresError(pgErr.Error()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/stores/api/formatters/secrets.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/stores/api/types" 5 | "github.com/consensys/quorum-key-manager/src/stores/entities" 6 | ) 7 | 8 | func FormatSecretResponse(secret *entities.Secret) *types.SecretResponse { 9 | resp := &types.SecretResponse{ 10 | ID: secret.ID, 11 | Version: secret.Metadata.Version, 12 | Value: secret.Value, 13 | Tags: secret.Tags, 14 | Disabled: secret.Metadata.Disabled, 15 | CreatedAt: secret.Metadata.CreatedAt, 16 | UpdatedAt: secret.Metadata.UpdatedAt, 17 | } 18 | 19 | if !secret.Metadata.DeletedAt.IsZero() { 20 | resp.DeletedAt = &secret.Metadata.DeletedAt 21 | } 22 | 23 | return resp 24 | } 25 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/support/static-nodes.json: -------------------------------------------------------------------------------- 1 | [ 2 | "enode://8208a3f344695d44e9cf2c023683cbea7b9343e2f70a5e804bd2c93858e945f8f91439eef96a4ab6c47ff06637d6fbe6472f96de1655a1bee57ea896654f3a22@172.16.238.11:30303?discport=0", 3 | "enode://b9050e002aa42464e6b07c811a1f9dfec01249af03f67b753e8415420649b184447bb2a784863ccbf327ad9e31aaba803464979dfe6a7facc669151a5fa6ad1b@172.16.238.12:30303?discport=0", 4 | "enode://59cf0c623c582fa9b19bdf70fb6bade07f4ae32218dd4d1c7e2c7e65acf87da45cf2ab55d16d27360aafef17622c37c09db60d7680ebcc17b78867f4c05bcaa4@172.16.238.13:30303?discport=0", 5 | "enode://2fd5b5b6ad529f55b71602026d1849d0036f06482368b5812fa793014195d3571b0840dbc4175617de2a12db8f1222c012420d471ae5c0d982118625cae58868@172.16.238.14:30303?discport=0" 6 | ] 7 | -------------------------------------------------------------------------------- /src/auth/entities/operation.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | type OpAction string 4 | type OpResource string 5 | 6 | var ActionRead OpAction = "read" 7 | var ActionWrite OpAction = "write" 8 | var ActionSign OpAction = "sign" 9 | var ActionEncrypt OpAction = "encrypt" 10 | var ActionDelete OpAction = "delete" 11 | var ActionDestroy OpAction = "destroy" 12 | var ActionProxy OpAction = "proxy" 13 | 14 | var ResourceKey OpResource = "keys" 15 | var ResourceSecret OpResource = "secrets" 16 | var ResourceEthAccount OpResource = "ethereum" 17 | var ResourceStore OpResource = "stores" 18 | var ResourceNode OpResource = "nodes" 19 | var ResourceAlias OpResource = "aliases" 20 | 21 | type Operation struct { 22 | Action OpAction 23 | Resource OpResource 24 | } 25 | -------------------------------------------------------------------------------- /deps/go-quorum/config/quorum/support/permissioned-nodes.json: -------------------------------------------------------------------------------- 1 | [ 2 | "enode://8208a3f344695d44e9cf2c023683cbea7b9343e2f70a5e804bd2c93858e945f8f91439eef96a4ab6c47ff06637d6fbe6472f96de1655a1bee57ea896654f3a22@172.16.238.11:30303?discport=0", 3 | "enode://b9050e002aa42464e6b07c811a1f9dfec01249af03f67b753e8415420649b184447bb2a784863ccbf327ad9e31aaba803464979dfe6a7facc669151a5fa6ad1b@172.16.238.12:30303?discport=0", 4 | "enode://59cf0c623c582fa9b19bdf70fb6bade07f4ae32218dd4d1c7e2c7e65acf87da45cf2ab55d16d27360aafef17622c37c09db60d7680ebcc17b78867f4c05bcaa4@172.16.238.13:30303?discport=0", 5 | "enode://2fd5b5b6ad529f55b71602026d1849d0036f06482368b5812fa793014195d3571b0840dbc4175617de2a12db8f1222c012420d471ae5c0d982118625cae58868@172.16.238.14:30303?discport=0" 6 | ] 7 | -------------------------------------------------------------------------------- /pkg/net/dialer/config.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/json" 7 | ) 8 | 9 | type Config struct { 10 | Timeout *json.Duration `json:"timeout,omitempty" description:"Max time to wait for a connection to complete (if zero, no timeout)"` 11 | KeepAlive *json.Duration `json:"keepAlive,omitempty" description:"Interval between keep-alive probes for an active network connection (if zero, if default to 15 seconds)"` 12 | } 13 | 14 | func (cfg *Config) SetDefault() *Config { 15 | if cfg.Timeout == nil { 16 | cfg.Timeout = &json.Duration{Duration: 30 * time.Second} 17 | } 18 | 19 | if cfg.KeepAlive == nil { 20 | cfg.KeepAlive = &json.Duration{Duration: 30 * time.Second} 21 | } 22 | 23 | return cfg 24 | } 25 | -------------------------------------------------------------------------------- /tests/acceptance/utils/reflect.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func ExtractField(cfg interface{}) (interface{}, error) { 9 | v := reflect.ValueOf(cfg).Elem() 10 | nonNilFieldsCount := 0 11 | var rv interface{} 12 | for i := 0; i < v.NumField(); i++ { 13 | field := v.Field(i) 14 | if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct { 15 | if !field.IsNil() { 16 | nonNilFieldsCount++ 17 | rv = field.Interface() 18 | } 19 | } 20 | } 21 | 22 | if nonNilFieldsCount == 0 { 23 | return nil, fmt.Errorf("invalid configuration: empty") 24 | } 25 | 26 | if nonNilFieldsCount > 1 { 27 | return nil, fmt.Errorf("invalid configuration: multiple fields provided") 28 | } 29 | 30 | return rv, nil 31 | } 32 | -------------------------------------------------------------------------------- /src/config.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/http/server" 5 | "github.com/consensys/quorum-key-manager/src/infra/api-key/csv" 6 | "github.com/consensys/quorum-key-manager/src/infra/jwt/jose" 7 | "github.com/consensys/quorum-key-manager/src/infra/log/zap" 8 | manifestreader "github.com/consensys/quorum-key-manager/src/infra/manifests/yaml" 9 | "github.com/consensys/quorum-key-manager/src/infra/postgres/client" 10 | tls "github.com/consensys/quorum-key-manager/src/infra/tls/filesystem" 11 | ) 12 | 13 | type Config struct { 14 | HTTP *server.Config 15 | Logger *zap.Config 16 | Postgres *client.Config 17 | OIDC *jose.Config 18 | APIKey *csv.Config 19 | TLS *tls.Config 20 | Manifest *manifestreader.Config 21 | } 22 | -------------------------------------------------------------------------------- /src/vaults/service/vaults/get.go: -------------------------------------------------------------------------------- 1 | package vaults 2 | 3 | import ( 4 | "context" 5 | 6 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 8 | "github.com/consensys/quorum-key-manager/src/entities" 9 | ) 10 | 11 | func (c *Vaults) Get(ctx context.Context, name string, userInfo *auth.UserInfo) (*entities.Vault, error) { 12 | logger := c.logger.With("name", name) 13 | 14 | permissions := c.roles.UserPermissions(ctx, userInfo) 15 | resolver := authorizator.New(permissions, userInfo.Tenant, c.logger) 16 | 17 | vault, err := c.getVault(ctx, name, resolver) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | logger.Debug("vault found successfully") 23 | return vault, nil 24 | } 25 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_test.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCombinePreparer(t *testing.T) { 12 | ctrl := gomock.NewController(t) 13 | defer ctrl.Finish() 14 | 15 | preparer1 := NewMockPreparer(ctrl) 16 | preparer2 := NewMockPreparer(ctrl) 17 | 18 | preparer := CombinePreparer(preparer1, preparer2) 19 | 20 | req := &http.Request{} 21 | call1 := preparer1.EXPECT().Prepare(req).Return(req, nil) 22 | preparer2.EXPECT().Prepare(req).Return(req, nil).After(call1) 23 | 24 | outReq, err := preparer.Prepare(req) 25 | assert.NoError(t, err, "Prepare should not error") 26 | assert.Equal(t, req, outReq, "Return req should be correct") 27 | } 28 | -------------------------------------------------------------------------------- /src/nodes/interceptor/eth_accounts.go: -------------------------------------------------------------------------------- 1 | package interceptor 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/jsonrpc" 7 | "github.com/consensys/quorum-key-manager/src/auth/api/http" 8 | ethcommon "github.com/ethereum/go-ethereum/common" 9 | ) 10 | 11 | func (i *Interceptor) ethAccounts(ctx context.Context) ([]ethcommon.Address, error) { 12 | i.logger.Debug("listing ETH accounts") 13 | 14 | addresses, err := i.stores.ListAllAccounts(ctx, http.UserInfoFromContext(ctx)) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | i.logger.Debug("ETH accounts fetched successfully") 20 | return addresses, nil 21 | } 22 | 23 | func (i *Interceptor) EthAccounts() jsonrpc.Handler { 24 | h, _ := jsonrpc.MakeHandler(i.ethAccounts) 25 | return h 26 | } 27 | -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | # Sign the CLA 2 | 3 | This page is the step-by-step guide to signing the Consensys Software Inc. 4 | Individual Contributor License Agreement. 5 | 6 | 1. First and foremost, read [the current version of the CLA]. 7 | It is written to be as close to plain English as possible. 8 | 9 | 2. Make an account on [GitHub] if you don't already have one. 10 | 11 | 3. After creating your first pull request, you will see a merge 12 | pre-requisite requiring to you read and sign the CLA. 13 | 14 | If you have any questions, you can reach us at [quorum@consensys.net]. 15 | 16 | [github]: https://github.com/ 17 | [the current version of the cla]: https://gist.github.com/rojotek/978b48a5e8b68836856a8961d6887992 18 | [quorum@consensys.net]: mailto:quorum@consensys.net?subject=Quorum+Key+Manager 19 | -------------------------------------------------------------------------------- /pkg/http/request/config.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type ProxyConfig struct { 4 | Addr string `json:"addr,omitempty"` 5 | PassHostHeader *bool `json:"passHostHeader,omitempty"` 6 | BasicAuth *BasicAuthConfig `json:"basicAuth,omitempty"` 7 | Headers map[string][]string `json:"headers,omitempty"` 8 | } 9 | 10 | func (cfg *ProxyConfig) SetDefault() *ProxyConfig { 11 | if cfg.PassHostHeader != nil { 12 | cfg.PassHostHeader = new(bool) 13 | *cfg.PassHostHeader = true 14 | } 15 | 16 | if cfg.BasicAuth == nil { 17 | cfg.BasicAuth = &BasicAuthConfig{"", ""} 18 | } 19 | 20 | return cfg 21 | } 22 | 23 | type BasicAuthConfig struct { 24 | Username string `json:"username,omitempty"` 25 | Password string `json:"password,omitempty"` 26 | } 27 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=ConsenSys_quorum-key-manager 2 | sonar.organization=consensys 3 | sonar.projectVersion=21.12 4 | 5 | # relative paths to source directories. More details and properties are described 6 | # in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ 7 | sonar.sources=./src,./pkg 8 | sonar.exclusions=**/*_test.go,tests/*,cmd/*,**/mock/*,**/mocks/*,**/testutils/*,**/testdata/*,src/infra/akv/**/*,src/infra/aws/**/*,src/infra/hashicorp/**/* 9 | 10 | sonar.tests=./src,./pkg 11 | sonar.test.inclusions=**/*_test.go 12 | 13 | sonar.go.coverage.reportPaths=./build/coverage/unit.out,./build/coverage/acceptance.out 14 | 15 | # Language 16 | sonar.language=go 17 | 18 | # Encoding of the source files 19 | sonar.sourceEncoding=UTF-8 20 | -------------------------------------------------------------------------------- /.github/actions/update-api-doc/action.yml: -------------------------------------------------------------------------------- 1 | name: 'update-api-doc' 2 | description: 'Update API version' 3 | inputs: 4 | specs-source: 5 | description: 'Generated OpenAPI spec file' 6 | required: true 7 | gh-pages-branch: 8 | description: 'Branch used to publish to GitHub Pages' 9 | required: true 10 | default: gh-pages 11 | versions-file: 12 | description: 'JSON file on ghpages with all the available versions listed' 13 | required: true 14 | default: 'versions.json' 15 | dev-branch: 16 | description: 'development branch' 17 | required: true 18 | default: 'master' 19 | spec-target-prefix: 20 | description: 'target spec file prefix' 21 | required: true 22 | default: 'key-manager-api' 23 | runs: 24 | using: 'node12' 25 | main: 'index.js' 26 | -------------------------------------------------------------------------------- /deps/migrations/000002_create_alias.up.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE IF NOT EXISTS registries ( 4 | pk SERIAL PRIMARY KEY, 5 | name TEXT NOT NULL UNIQUE, 6 | allowed_tenants TEXT [], 7 | created_at TIMESTAMPTZ DEFAULT (now() at time zone 'utc') NOT NULL, 8 | updated_at TIMESTAMPTZ DEFAULT (now() at time zone 'utc') NOT NULL, 9 | UNIQUE(name) 10 | ); 11 | 12 | CREATE TABLE IF NOT EXISTS aliases ( 13 | pk SERIAL PRIMARY KEY, 14 | registry_name TEXT NOT NULL REFERENCES registries(name) ON DELETE CASCADE, 15 | key TEXT NOT NULL, 16 | value JSON NOT NULL, 17 | created_at TIMESTAMPTZ DEFAULT (now() at time zone 'utc') NOT NULL, 18 | updated_at TIMESTAMPTZ DEFAULT (now() at time zone 'utc') NOT NULL, 19 | UNIQUE(registry_name, key) 20 | ); 21 | 22 | COMMIT; 23 | -------------------------------------------------------------------------------- /src/aliases/service/aliases/aliases.go: -------------------------------------------------------------------------------- 1 | package aliases 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/aliases" 5 | "github.com/consensys/quorum-key-manager/src/aliases/database" 6 | "github.com/consensys/quorum-key-manager/src/auth" 7 | "github.com/consensys/quorum-key-manager/src/infra/log" 8 | ) 9 | 10 | type Aliases struct { 11 | aliasDB database.Alias 12 | registryDB database.Registry 13 | logger log.Logger 14 | roles auth.Roles 15 | } 16 | 17 | var _ aliases.Aliases = &Aliases{} 18 | 19 | func New(aliasDB database.Alias, registryDB database.Registry, rolesService auth.Roles, logger log.Logger) *Aliases { 20 | return &Aliases{ 21 | aliasDB: aliasDB, 22 | registryDB: registryDB, 23 | roles: rolesService, 24 | logger: logger, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build 3 | 4 | on: 5 | workflow_call: 6 | # Review gh actions docs if you want to further define triggers, paths, etc 7 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | # the enviroment to deploy to / use secrets from 14 | environment: no-secret 15 | # modify the default permissions of the GITHUB_TOKEN, so as to only allow least priveleges 16 | permissions: 17 | contents: read 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Build 22 | uses: ConsenSys/docs-gha/build@main 23 | with: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /src/nodes/service.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | proxynode "github.com/consensys/quorum-key-manager/src/nodes/node/proxy" 8 | ) 9 | 10 | //go:generate mockgen -source=service.go -destination=mock/service.go -package=mock 11 | 12 | // Nodes Service allows managing nodes 13 | type Nodes interface { 14 | // Create creates a new node 15 | Create(ctx context.Context, name string, config *proxynode.Config, allowedTenants []string, userInfo *entities.UserInfo) error 16 | 17 | // Get returns a node by name 18 | Get(ctx context.Context, name string, userInfo *entities.UserInfo) (*proxynode.Node, error) 19 | 20 | // List returns a list of nodes 21 | List(ctx context.Context, userInfo *entities.UserInfo) ([]string, error) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/ethereum/utils.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ethereum/go-ethereum/signer/core" 7 | ) 8 | 9 | const ( 10 | EIP712DomainLabel = "EIP712Domain" 11 | ) 12 | 13 | func GetEIP712EncodedData(typedData *core.TypedData) ([]byte, error) { 14 | typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | domainSeparatorHash, err := typedData.HashStruct(EIP712DomainLabel, typedData.Domain.Map()) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparatorHash, typedDataHash)), nil 25 | } 26 | 27 | func GetEIP191EncodedData(msg []byte) []byte { 28 | return []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%v", len(msg), string(msg))) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_proxy.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import "net/url" 4 | 5 | // Proxy creates a preparer for proxying request 6 | func Proxy(cfg *ProxyConfig) (Preparer, error) { 7 | var preparers []Preparer 8 | if cfg.Addr != "" { 9 | u, err := url.Parse(cfg.Addr) 10 | if err != nil { 11 | return nil, err 12 | } 13 | preparers = append(preparers, URL(u)) 14 | } 15 | 16 | preparers = append( 17 | preparers, 18 | Headers(cfg.Headers), 19 | ExtractURI(true), 20 | HTTPProtocol(1, 1), 21 | UserAgent(""), 22 | ) 23 | 24 | if cfg.PassHostHeader != nil && !*cfg.PassHostHeader { 25 | preparers = append(preparers, Host(nil)) 26 | } 27 | 28 | preparers = append( 29 | preparers, 30 | BasicAuth(cfg.BasicAuth), 31 | Body(), 32 | ) 33 | 34 | return CombinePreparer(preparers...), nil 35 | } 36 | -------------------------------------------------------------------------------- /src/docs.go: -------------------------------------------------------------------------------- 1 | package src 2 | 3 | // @title Quorum Key Manager API 4 | // @version 0.1 5 | // @description Quorum Key Manager API server documentation. 6 | 7 | // @license.name Business Source License 1.1 8 | // @license.url https://mariadb.com/bsl11/ 9 | 10 | // @host https://consensys.github.io/quorum-key-manager 11 | // @BasePath / 12 | // @query.collection.format multi 13 | 14 | // @securityDefinitions.basic BasicAuth 15 | // @securityDefinitions.apikey ApiKeyAuth 16 | // @in header 17 | // @name Authorization 18 | 19 | // @securitydefinitions.oauth2.application OAuth2Application 20 | // @tokenUrl https://example.com/oauth/token 21 | // @scope.write Grants write access 22 | // @scope.admin Grants read and write access to administrative information 23 | 24 | // @x-extension-openapi {"example": "value on a json format"} 25 | -------------------------------------------------------------------------------- /src/nodes/service/nodes/list.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "context" 5 | "sort" 6 | 7 | "github.com/consensys/quorum-key-manager/src/auth/entities" 8 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 9 | ) 10 | 11 | func (i *Nodes) List(ctx context.Context, userInfo *entities.UserInfo) ([]string, error) { 12 | i.mux.RLock() 13 | defer i.mux.RUnlock() 14 | 15 | var nodeNames []string 16 | for name, nodeInfo := range i.nodes { 17 | permissions := i.roles.UserPermissions(ctx, userInfo) 18 | resolver := authorizator.New(permissions, userInfo.Tenant, i.logger) 19 | 20 | if err := resolver.CheckAccess(nodeInfo.AllowedTenants); err != nil { 21 | continue 22 | } 23 | nodeNames = append(nodeNames, name) 24 | } 25 | 26 | sort.Strings(nodeNames) 27 | 28 | return nodeNames, nil 29 | } 30 | -------------------------------------------------------------------------------- /src/auth/service/roles/create.go: -------------------------------------------------------------------------------- 1 | package roles 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/consensys/quorum-key-manager/pkg/errors" 8 | "github.com/consensys/quorum-key-manager/src/auth/entities" 9 | ) 10 | 11 | func (i *Roles) Create(ctx context.Context, name string, permissions []entities.Permission, _ *entities.UserInfo) error { 12 | logger := i.logger.With("name", name, "permissions", permissions) 13 | logger.Debug("creating role") 14 | 15 | // TODO: Implement {Resource/Role}BAC for roles 16 | 17 | if _, ok := i.roles[name]; ok { 18 | errMessage := fmt.Sprintf("role %s already exist", name) 19 | logger.Error(errMessage) 20 | return errors.AlreadyExistsError(errMessage) 21 | } 22 | 23 | i.createRole(ctx, name, permissions) 24 | 25 | logger.Info("role created successfully") 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /src/stores/connectors/secrets/secrets.go: -------------------------------------------------------------------------------- 1 | package secrets 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/auth" 5 | "github.com/consensys/quorum-key-manager/src/infra/log" 6 | "github.com/consensys/quorum-key-manager/src/stores" 7 | "github.com/consensys/quorum-key-manager/src/stores/database" 8 | ) 9 | 10 | type Connector struct { 11 | store stores.SecretStore 12 | logger log.Logger 13 | db database.Secrets 14 | authorizator auth.Authorizator 15 | } 16 | 17 | var _ stores.SecretStore = &Connector{} 18 | 19 | func NewConnector(store stores.SecretStore, db database.Secrets, authorizator auth.Authorizator, logger log.Logger) *Connector { 20 | return &Connector{ 21 | store: store, 22 | logger: logger, 23 | db: db, 24 | authorizator: authorizator, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_body.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | // Body ensures that GetBody method is set on request 11 | func Body() Preparer { 12 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 13 | if req.GetBody != nil { 14 | return req, nil 15 | } 16 | 17 | // See More https://github.com/golang/net/blob/master/http2/transport.go#L554 18 | if req.Body != nil { 19 | body, _ := ioutil.ReadAll(req.Body) 20 | req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) 21 | req.GetBody = func() (io.ReadCloser, error) { 22 | return ioutil.NopCloser(bytes.NewBuffer(body)), nil 23 | } 24 | } else { 25 | req.GetBody = func() (io.ReadCloser, error) { 26 | return nil, nil 27 | } 28 | } 29 | 30 | return req, nil 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/http/request/json.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | func WriteJSON(req *http.Request, msg interface{}) error { 12 | // Prepare header 13 | if req.Header == nil { 14 | req.Header = make(http.Header) 15 | } 16 | req.Header.Set("Content-Type", "application/json") 17 | req.Header.Set("Accept", "application/json") 18 | 19 | // Write message into buffer 20 | b, err := json.Marshal(msg) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | body := bytes.NewBuffer(b) 26 | 27 | // Set body 28 | req.Body = ioutil.NopCloser(body) 29 | req.ContentLength = int64(body.Len()) 30 | buf := body.Bytes() 31 | req.GetBody = func() (io.ReadCloser, error) { 32 | r := bytes.NewReader(buf) 33 | return ioutil.NopCloser(r), nil 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Reference/Release-Types.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Release types 3 | sidebar_position: 5 4 | --- 5 | 6 | # Release types 7 | 8 | Quorum Key Manager has 2 different release types - long term support (LTS) releases and interim releases. 9 | 10 | LTS releases are reliable, production-ready releases that are updated and supported throughout the lifetime of the release. LTS releases are supported for 12 months and are patched with every bug fix during the course of its support timeline. LTS releases are marked `LTS` in the [release notes](https://github.com/ConsenSys/quorum-key-manager/blob/main/CHANGELOG.md). 11 | 12 | Interim releases provide access to recent features that may still be experimental and are not recommended for production. Use interim releases for testing and development purposes. Interim releases are published between LTS releases. 13 | -------------------------------------------------------------------------------- /src/nodes/app/service.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/aliases" 5 | "github.com/consensys/quorum-key-manager/src/auth" 6 | "github.com/consensys/quorum-key-manager/src/infra/log" 7 | "github.com/consensys/quorum-key-manager/src/nodes/api" 8 | "github.com/consensys/quorum-key-manager/src/nodes/service/nodes" 9 | "github.com/consensys/quorum-key-manager/src/stores" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func RegisterService( 14 | router *mux.Router, 15 | logger log.Logger, 16 | authService auth.Roles, 17 | storesService stores.Stores, 18 | aliasService aliases.Aliases, 19 | ) *nodes.Nodes { 20 | // Business layer 21 | nodesService := nodes.New(storesService, authService, aliasService, logger) 22 | 23 | // Service layer 24 | api.New(nodesService).Register(router) 25 | 26 | return nodesService 27 | } 28 | -------------------------------------------------------------------------------- /src/stores/connectors/ethereum/decrypt.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | ) 10 | 11 | func (c Connector) Decrypt(ctx context.Context, addr common.Address, data []byte) ([]byte, error) { 12 | logger := c.logger.With("address", addr.Hex()) 13 | 14 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionEncrypt, Resource: entities.ResourceEthAccount}) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | acc, err := c.db.Get(ctx, addr.Hex()) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | result, err := c.store.Decrypt(ctx, acc.KeyID, data) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | logger.Debug("data decrypted successfully") 30 | return result, nil 31 | } 32 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.github/workflows/case.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check file name case 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - main 8 | workflow_call: 9 | 10 | jobs: 11 | case: 12 | name: Check for case being inconsistent 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | folder: ["docs"] 17 | # the enviroment to deploy to / use secrets from 18 | environment: no-secret 19 | # modify the default permissions of the GITHUB_TOKEN, so as to only allow least priveleges 20 | permissions: 21 | contents: read 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Case check action 26 | uses: ConsenSys/docs-gha/case@main 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | DOC_DIR: ${{ matrix.folder }} 30 | SKIP_TEST: true 31 | -------------------------------------------------------------------------------- /src/stores/connectors/ethereum/encrypt.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | 8 | ethcommon "github.com/ethereum/go-ethereum/common" 9 | ) 10 | 11 | func (c Connector) Encrypt(ctx context.Context, addr ethcommon.Address, data []byte) ([]byte, error) { 12 | logger := c.logger.With("address", addr.Hex()) 13 | 14 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionEncrypt, Resource: entities.ResourceEthAccount}) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | acc, err := c.db.Get(ctx, addr.Hex()) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | result, err := c.store.Encrypt(ctx, acc.KeyID, data) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | logger.Debug("data encrypted successfully") 30 | return result, nil 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yaml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main, maintenance/* ] 6 | 7 | jobs: 8 | analyze: 9 | name: Analyze 10 | runs-on: ubuntu-latest 11 | permissions: 12 | actions: read 13 | contents: read 14 | security-events: write 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | language: [ 'go' ] 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v2 24 | 25 | # Initializes the CodeQL tools for scanning. 26 | - name: Initialize CodeQL 27 | uses: github/codeql-action/init@v1 28 | with: 29 | languages: ${{ matrix.language }} 30 | 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v1 33 | 34 | - name: Perform CodeQL Analysis 35 | uses: github/codeql-action/analyze@v1 36 | -------------------------------------------------------------------------------- /src/stores/api/types/secrets.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | type SetSecretRequest struct { 6 | Value string `json:"value" validate:"required" example:"my-value"` 7 | Tags map[string]string `json:"tags,omitempty"` 8 | } 9 | 10 | type SecretResponse struct { 11 | ID string `json:"id" example:"my-secret"` 12 | Value string `json:"value" example:"my-value"` 13 | Tags map[string]string `json:"tags,omitempty"` 14 | Version string `json:"version" example:"1"` 15 | Disabled bool `json:"disabled" example:"false"` 16 | CreatedAt time.Time `json:"createdAt" example:"2020-07-09T12:35:42.115395Z"` 17 | UpdatedAt time.Time `json:"updatedAt" example:"2020-07-09T12:35:42.115395Z"` 18 | DeletedAt *time.Time `json:"deletedAt,omitempty" example:"2020-07-09T12:35:42.115395Z"` 19 | } 20 | -------------------------------------------------------------------------------- /pkg/http/request/json_test.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | type TestMsg struct { 13 | Field []string `json:"field"` 14 | } 15 | 16 | func TestWriteJSON(t *testing.T) { 17 | req, _ := http.NewRequest(http.MethodPost, "", nil) 18 | 19 | msg := &TestMsg{ 20 | Field: []string{"Hello", "World"}, 21 | } 22 | err := WriteJSON(req, msg) 23 | require.NoError(t, err, "WriteJSON must not error") 24 | assert.Equal(t, "application/json", req.Header.Get("Content-Type"), "Content-Type Header must be correct") 25 | 26 | b, err := ioutil.ReadAll(req.Body) 27 | require.NoError(t, err, "ReadAll must not error") 28 | expectedBody := []byte(`{"field":["Hello","World"]}`) 29 | assert.Equal(t, expectedBody, b, "Body should be correct") 30 | } 31 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.releaserc.js: -------------------------------------------------------------------------------- 1 | const mainConfig = { 2 | branches: ["main"], 3 | plugins: [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | "@semantic-release/changelog", 7 | [ 8 | "@semantic-release/npm", 9 | { 10 | npmPublish: false, 11 | tarballDir: "dist", 12 | }, 13 | ], 14 | [ 15 | "@semantic-release/git", 16 | { 17 | assets: ["package.json", "package-lock.json", "CHANGELOG.md"], 18 | message: 19 | "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}", 20 | }, 21 | ], 22 | [ 23 | "@semantic-release/github", 24 | { 25 | assets: "dist/*.tgz", 26 | }, 27 | ], 28 | ], 29 | repositoryUrl: "https://github.com/ConsenSys/doc.quorum-key-manager", 30 | }; 31 | 32 | module.exports = mainConfig; 33 | -------------------------------------------------------------------------------- /deps/besu/config/besu/config.toml: -------------------------------------------------------------------------------- 1 | 2 | logging="INFO" 3 | data-path="/opt/besu/data" 4 | host-whitelist=["*"] 5 | 6 | # rpc 7 | rpc-http-enabled=true 8 | rpc-http-host="0.0.0.0" 9 | rpc-http-port=8545 10 | rpc-http-cors-origins=["*"] 11 | 12 | # ws 13 | rpc-ws-enabled=true 14 | rpc-ws-host="0.0.0.0" 15 | rpc-ws-port=8546 16 | 17 | # graphql 18 | graphql-http-enabled=false 19 | graphql-http-host="0.0.0.0" 20 | graphql-http-port=8547 21 | graphql-http-cors-origins=["*"] 22 | 23 | # metrics 24 | metrics-enabled=false 25 | metrics-host="0.0.0.0" 26 | metrics-port=9545 27 | 28 | # permissions 29 | permissions-nodes-config-file-enabled=true 30 | permissions-nodes-config-file="/config/permissions_config.toml" 31 | 32 | # bootnodes 33 | bootnodes=["enode://c1979a8a48693db804316b5acebe35e11731e1fb1c9c21ff7268ab25db6f6e03390a429b83cf0ec0865a7205f2669ec1ace652a3def11e2e01571c74939cbe22@172.16.237.11:30303"] 34 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | @media screen and (max-width: 996px) { 7 | .introductionBlock { 8 | padding: 2rem; 9 | } 10 | } 11 | 12 | .buttons { 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | } 17 | 18 | .introductionBlock { 19 | background: linear-gradient(56.02deg, #2c56dd 49.2%, #30daad 95.07%), #2c56dd; 20 | padding: 6rem; 21 | text-align: center; 22 | width: 100%; 23 | } 24 | 25 | .forceColor { 26 | color: var(--ifm-color-content-inverse); 27 | } 28 | 29 | html[data-theme="dark"] .forceColor { 30 | color: var(--ifm-font-color-secondary); 31 | } 32 | 33 | .title { 34 | font-size: clamp(1.5rem, 5vw, 6rem); 35 | } 36 | 37 | .subtitle { 38 | font-size: clamp(0.8rem, 1vw, 2rem); 39 | } 40 | -------------------------------------------------------------------------------- /src/infra/tls/filesystem/reader.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | 10 | "github.com/consensys/quorum-key-manager/src/infra/tls" 11 | ) 12 | 13 | type Reader struct { 14 | path string 15 | } 16 | 17 | var _ tls.Reader = &Reader{} 18 | 19 | func New(cfg *Config) (*Reader, error) { 20 | _, err := os.Stat(cfg.Path) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | return &Reader{path: cfg.Path}, nil 26 | } 27 | 28 | func (r *Reader) Load(_ context.Context) (*x509.CertPool, error) { 29 | fileContent, err := ioutil.ReadFile(r.path) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | caCertPool := x509.NewCertPool() 35 | ok := caCertPool.AppendCertsFromPEM(fileContent) 36 | if !ok { 37 | return nil, fmt.Errorf("failed to append cert to pool") 38 | } 39 | 40 | return caCertPool, nil 41 | } 42 | -------------------------------------------------------------------------------- /cmd/flags/sync.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/pflag" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | func init() { 11 | viper.SetDefault(storeNameViperKey, storeNameDefault) 12 | _ = viper.BindEnv(storeNameViperKey, storeNameEnv) 13 | } 14 | 15 | const ( 16 | storeNameFlag = "sync-store-name" 17 | storeNameViperKey = "sync.store.name" 18 | storeNameDefault = "" 19 | storeNameEnv = "SYNC_STORE_NAME" 20 | ) 21 | 22 | func SyncFlags(f *pflag.FlagSet) { 23 | storeName(f) 24 | } 25 | 26 | func storeName(f *pflag.FlagSet) { 27 | desc := fmt.Sprintf(`Name of the store to index 28 | Environment variable: %q`, storeNameEnv) 29 | f.String(storeNameFlag, storeNameDefault, desc) 30 | _ = viper.BindPFlag(storeNameViperKey, f.Lookup(storeNameFlag)) 31 | } 32 | 33 | func GetStoreName(vipr *viper.Viper) string { 34 | return vipr.GetString(storeNameViperKey) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Error struct { 8 | Message string 9 | Code string 10 | } 11 | 12 | func (e *Error) GetCode() string { 13 | return e.Code 14 | } 15 | 16 | func (e *Error) Error() string { 17 | return fmt.Sprintf("%s: %s", e.Code, e.Message) 18 | } 19 | 20 | func (e *Error) GetMessage() string { 21 | return e.Message 22 | } 23 | 24 | func (e *Error) SetMessage(format string, args ...interface{}) *Error { 25 | e.Message = fmt.Sprintf(format, args...) 26 | return e 27 | } 28 | 29 | func Errorf(code, format string, a ...interface{}) *Error { 30 | return &Error{fmt.Sprintf(format, a...), code} 31 | } 32 | 33 | func FromError(err interface{}) *Error { 34 | if err == nil { 35 | return nil 36 | } 37 | 38 | ierr, ok := err.(*Error) 39 | if !ok { 40 | return Errorf(Internal, err.(error).Error()) 41 | } 42 | 43 | return ierr 44 | } 45 | -------------------------------------------------------------------------------- /src/stores/api/formatters/keys.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "github.com/consensys/quorum-key-manager/src/stores/api/types" 7 | "github.com/consensys/quorum-key-manager/src/stores/entities" 8 | ) 9 | 10 | func FormatKeyResponse(key *entities.Key) *types.KeyResponse { 11 | resp := &types.KeyResponse{ 12 | ID: key.ID, 13 | PublicKey: base64.StdEncoding.EncodeToString(key.PublicKey), 14 | Curve: string(key.Algo.EllipticCurve), 15 | SigningAlgorithm: string(key.Algo.Type), 16 | Tags: key.Tags, 17 | Annotations: key.Annotations, 18 | Disabled: key.Metadata.Disabled, 19 | CreatedAt: key.Metadata.CreatedAt, 20 | UpdatedAt: key.Metadata.UpdatedAt, 21 | } 22 | 23 | if !key.Metadata.DeletedAt.IsZero() { 24 | resp.DeletedAt = &key.Metadata.DeletedAt 25 | } 26 | 27 | return resp 28 | } 29 | -------------------------------------------------------------------------------- /pkg/websocket/forwarder.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gorilla/websocket" 7 | ) 8 | 9 | var GoingAway = websocket.FormatCloseMessage(websocket.CloseGoingAway, "") 10 | 11 | func Forward(_ context.Context, from, to *websocket.Conn) error { 12 | for { 13 | typ, msg, err := from.ReadMessage() 14 | if err != nil { 15 | return err 16 | } 17 | 18 | _ = to.WriteMessage(typ, msg) 19 | } 20 | } 21 | 22 | func PipeConn(ctx context.Context, clientConn, serverConn *websocket.Conn) (clientErrors, serverErrors <-chan error) { 23 | clientErrs := make(chan error, 1) 24 | serverErrs := make(chan error, 1) 25 | 26 | go func() { 27 | clientErrs <- Forward(ctx, clientConn, serverConn) 28 | close(clientErrs) 29 | }() 30 | 31 | go func() { 32 | serverErrs <- Forward(ctx, serverConn, clientConn) 33 | close(serverErrs) 34 | }() 35 | 36 | return clientErrs, serverErrs 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[] Task short description" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Summary / User Story 11 | 12 | A clear and concise description of what are we solving and how. 13 | 14 | ### Pre-requisites 15 | 16 | Any requirements to be prepared before 17 | 18 | ### Technical Notes 19 | 20 | * Links to architecture, documents 21 | 22 | ## Business Value 23 | A short reasoning about how this feature would improve the value of the product. 24 | 25 | 30 | 31 | ### Acceptance Criteria 32 | 33 | - [ ] Deliverable item that is expected as an outcome of this feature or task. 34 | - [ ] Detail separate any items that should be done 35 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # Run this command to see a complete list of environment variables 2 | # docker run consensys/quorum-key-manager:latest help run 3 | 4 | # Manifest file location on the host machine 5 | #HOST_MANIFEST_PATH=./deps/config/manifests 6 | 7 | # Log format and level 8 | #LOG_LEVEL=info 9 | #LOG_FORMAT=json 10 | 11 | # API key file location 12 | #AUTH_API_KEY_FILE=/apikey/sample.csv 13 | 14 | # TLS root certificate file location 15 | #AUTH_TLS_CA=/ca/ca.crt 16 | 17 | # OpenID Connect 18 | #AUTH_OIDC_ISSUER_URL= 19 | 20 | ## Start HTTPS server 21 | #HTTPS_ENABLED=true 22 | #HTTPS_SERVER_KEY=/certificates/https.key 23 | #HTTPS_SERVER_CERT=/certificates/https.crt 24 | 25 | ## Start Postgres SSL server 26 | #DB_TLS_SSLMODE=verify-ca 27 | #DB_TLS_CERT=/certificates/client.crt 28 | #DB_TLS_KEY=/certificates/client.key 29 | #DB_TLS_CA=/ca/ca.crt 30 | #DB_HOST=postgres-ssl 31 | 32 | # Import tool 33 | #IMPORT_STORE_NAME=hashicorp-secrets 34 | -------------------------------------------------------------------------------- /pkg/http/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "time" 6 | ) 7 | 8 | type Config struct { 9 | Host string 10 | HealthzPort uint32 11 | Port uint32 12 | 13 | TLSConfig *tls.Config 14 | 15 | Timeout time.Duration 16 | KeepAlive time.Duration 17 | IdleConnTimeout time.Duration 18 | TLSHandshakeTimeout time.Duration 19 | ExpectContinueTimeout time.Duration 20 | MaxIdleConnsPerHost int 21 | } 22 | 23 | func NewDefaultConfig() *Config { 24 | return &Config{ 25 | Host: "localhost", 26 | Port: 8080, 27 | HealthzPort: 8081, 28 | MaxIdleConnsPerHost: 200, 29 | IdleConnTimeout: 90 * time.Second, 30 | TLSHandshakeTimeout: 10 * time.Second, 31 | ExpectContinueTimeout: 1 * time.Second, 32 | Timeout: 30 * time.Second, 33 | KeepAlive: 30 * time.Second, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/auth/service/roles/user_permissions.go: -------------------------------------------------------------------------------- 1 | package roles 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (i *Roles) UserPermissions(ctx context.Context, userInfo *entities.UserInfo) []entities.Permission { 10 | if userInfo == nil { 11 | return []entities.Permission{} 12 | } 13 | 14 | permissions := userInfo.Permissions 15 | 16 | for _, roleName := range userInfo.Roles { 17 | role, err := i.Get(ctx, roleName, userInfo) 18 | if err != nil { 19 | continue 20 | } 21 | 22 | permissions = append(permissions, role.Permissions...) 23 | for _, p := range role.Permissions { 24 | permissions = append(permissions, entities.ListWildcardPermission(string(p))...) 25 | } 26 | } 27 | 28 | i.logger.Debug("permissions extracted successfully", "tenant", userInfo.Tenant, "username", userInfo.Username, "permissions", permissions) 29 | return permissions 30 | } 31 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/sign.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | 8 | authentities "github.com/consensys/quorum-key-manager/src/auth/entities" 9 | ) 10 | 11 | func (c Connector) Sign(ctx context.Context, id string, data []byte, algo *entities.Algorithm) ([]byte, error) { 12 | logger := c.logger.With("id", id) 13 | 14 | err := c.authorizator.CheckPermission(&authentities.Operation{Action: authentities.ActionSign, Resource: authentities.ResourceKey}) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | if algo == nil { 20 | key, derr := c.db.Get(ctx, id) 21 | if derr != nil { 22 | return nil, derr 23 | } 24 | 25 | algo = key.Algo 26 | } 27 | 28 | result, err := c.store.Sign(ctx, id, data, algo) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | logger.Debug("payload signed successfully") 34 | return result, nil 35 | } 36 | -------------------------------------------------------------------------------- /src/infra/aws/client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws" 5 | "github.com/aws/aws-sdk-go/aws/credentials" 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | ) 8 | 9 | type Config struct { 10 | Region string 11 | AccessID string 12 | SecretKey string 13 | Debug bool 14 | } 15 | 16 | func NewConfig(cfg *entities.AWSConfig) *Config { 17 | return &Config{ 18 | Region: cfg.Region, 19 | AccessID: cfg.AccessID, 20 | SecretKey: cfg.SecretKey, 21 | Debug: cfg.Debug, 22 | } 23 | } 24 | 25 | func (c *Config) ToAWSConfig() *aws.Config { 26 | awsConfig := &aws.Config{ 27 | Credentials: credentials.NewStaticCredentials(c.AccessID, c.SecretKey, ""), 28 | Region: aws.String(c.Region), 29 | } 30 | 31 | if c.Debug { 32 | awsConfig.WithLogLevel(aws.LogDebug) 33 | awsConfig.CredentialsChainVerboseErrors = aws.Bool(true) 34 | } 35 | 36 | return awsConfig 37 | } 38 | -------------------------------------------------------------------------------- /pkg/errors/data.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | const ( 4 | Data = "DA000" 5 | Encoding = "DA100" 6 | CryptoOperation = "DA200" 7 | ) 8 | 9 | // EncodingError are raised when failing to decode a message 10 | func EncodingError(format string, a ...interface{}) *Error { 11 | return Errorf(Encoding, format, a...) 12 | } 13 | 14 | // IsEncodingError indicate whether an error is a EncodingError error 15 | func IsEncodingError(err error) bool { 16 | return isErrorClass(FromError(err).GetCode(), Encoding) 17 | } 18 | 19 | // CryptoOperationError are raised when failing to perform a crypto operation 20 | func CryptoOperationError(format string, a ...interface{}) *Error { 21 | return Errorf(CryptoOperation, format, a...) 22 | } 23 | 24 | // IsCryptoOperationError indicate whether an error is a CryptoOperationError error 25 | func IsCryptoOperationError(err error) bool { 26 | return isErrorClass(FromError(err).GetCode(), CryptoOperation) 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/service.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/entities" 5 | "github.com/ethereum/go-ethereum/common" 6 | "github.com/ethereum/go-ethereum/signer/core" 7 | ) 8 | 9 | //go:generate mockgen -source=service.go -destination=mock/service.go -package=mock 10 | 11 | type Utilities interface { 12 | // Verify verifies the signature belongs to the corresponding key 13 | Verify(pubKey, data, sig []byte, algo *entities.Algorithm) error 14 | 15 | // ECRecover returns the Ethereum address from a signature and data 16 | ECRecover(data, sig []byte) (common.Address, error) 17 | 18 | // VerifyMessage verifies that a message signature belongs to a given address 19 | VerifyMessage(addr common.Address, data, sig []byte) error 20 | 21 | // VerifyTypedData verifies that a typed data signature belongs to a given address 22 | VerifyTypedData(addr common.Address, typedData *core.TypedData, sig []byte) error 23 | } 24 | -------------------------------------------------------------------------------- /tests/acceptance/utils/service.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "time" 7 | 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func WaitForServiceLive(ctx context.Context, url, name string, timeout time.Duration) { 12 | rctx, cancel := context.WithTimeout(ctx, timeout) 13 | defer cancel() 14 | 15 | for { 16 | req, _ := http.NewRequest("GET", url, nil) 17 | req = req.WithContext(rctx) 18 | 19 | resp, err := http.DefaultClient.Do(req) 20 | if err == nil { 21 | if resp != nil && resp.StatusCode == 200 { 22 | log.Infof("service %s is live", name) 23 | return 24 | } 25 | 26 | log.WithField("status", resp.StatusCode).Warnf("cannot reach %s service", name) 27 | } 28 | 29 | if rctx.Err() != nil { 30 | log.WithError(rctx.Err()).Warnf("cannot reach %s service", name) 31 | return 32 | } 33 | 34 | log.Debugf("waiting for 1 s for service %s to start...", name) 35 | time.Sleep(time.Second) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/auth/entities/user.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | // UserClaims represent raw claims extracted from an authentication method 4 | type UserClaims struct { 5 | Tenant string 6 | Permissions []string 7 | Roles []string 8 | } 9 | 10 | type UserInfo struct { 11 | // AuthMode records the mode that succeeded to Authenticate the request ('tls', 'api-key', 'oidc' or '') 12 | AuthMode string 13 | 14 | // Tenant belonged by the user 15 | Tenant string 16 | 17 | // Tenant identifies the user 18 | Username string 19 | 20 | // Roles indicates the user's membership 21 | Roles []string 22 | 23 | // Permissions specify 24 | Permissions []Permission 25 | } 26 | 27 | func NewWildcardUser() *UserInfo { 28 | return &UserInfo{ 29 | Permissions: ListPermissions(), 30 | } 31 | } 32 | 33 | func NewAnonymousUser() *UserInfo { 34 | return &UserInfo{ 35 | Username: "anonymous", 36 | Roles: []string{AnonymousRole}, 37 | Permissions: []Permission{}, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/vaults/service/vaults/create_aws.go: -------------------------------------------------------------------------------- 1 | package vaults 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/errors" 7 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 8 | "github.com/consensys/quorum-key-manager/src/entities" 9 | "github.com/consensys/quorum-key-manager/src/infra/aws/client" 10 | ) 11 | 12 | func (c *Vaults) CreateAWS(_ context.Context, name string, config *entities.AWSConfig, allowedTenants []string, _ *auth.UserInfo) error { 13 | logger := c.logger.With("name", name) 14 | logger.Debug("creating aws vault client") 15 | 16 | cli, err := client.New(client.NewConfig(config), logger) 17 | if err != nil { 18 | errMessage := "failed to instantiate AWS client" 19 | logger.WithError(err).Error(errMessage) 20 | return errors.InvalidParameterError(errMessage) 21 | } 22 | 23 | c.createVault(name, entities.AWSVaultType, allowedTenants, cli) 24 | 25 | logger.Info("aws vault created successfully") 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /src/vaults/service/vaults/create_azure.go: -------------------------------------------------------------------------------- 1 | package vaults 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/errors" 7 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 8 | "github.com/consensys/quorum-key-manager/src/entities" 9 | "github.com/consensys/quorum-key-manager/src/infra/akv/client" 10 | ) 11 | 12 | func (c *Vaults) CreateAzure(_ context.Context, name string, config *entities.AzureConfig, allowedTenants []string, _ *auth.UserInfo) error { 13 | logger := c.logger.With("name", name) 14 | logger.Debug("creating akv client") 15 | 16 | cli, err := client.NewClient(client.NewConfig(config)) 17 | if err != nil { 18 | errMessage := "failed to instantiate AKV client" 19 | logger.WithError(err).Error(errMessage) 20 | return errors.InvalidFormatError(errMessage) 21 | } 22 | 23 | c.createVault(name, entities.AzureVaultType, allowedTenants, cli) 24 | 25 | logger.Info("azure vault created successfully") 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/http/response/modifier.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "net/http" 4 | 5 | // Modifier is the interface that allows to modify http.http.Response the Respond method. 6 | type Modifier interface { 7 | Modify(*http.Response) error 8 | } 9 | 10 | // ModifierFunc is a method that implements the Modifier interface. 11 | type ModifierFunc func(*http.Response) error 12 | 13 | // Respond implements the Modifier interface on ModifierFunc. 14 | func (rf ModifierFunc) Modify(r *http.Response) error { 15 | return rf(r) 16 | } 17 | 18 | // CombineModifier combines multiple modifiers into a single one 19 | func CombineModifier(modifiers ...Modifier) Modifier { 20 | return ModifierFunc(func(resp *http.Response) error { 21 | var err error 22 | for _, modifier := range modifiers { 23 | err = modifier.Modify(resp) 24 | if err != nil { 25 | return err 26 | } 27 | } 28 | 29 | return nil 30 | }) 31 | } 32 | 33 | var NoopModifier = ModifierFunc(func(*http.Response) error { return nil }) 34 | -------------------------------------------------------------------------------- /pkg/common/interfaces_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type invalidMarshaller struct{} 11 | 12 | func (o *invalidMarshaller) MarshalJSON() ([]byte, error) { 13 | return nil, fmt.Errorf("fake error") 14 | } 15 | 16 | func TestInterfaceToObject(t *testing.T) { 17 | t.Run("parse interface into array of string successfully", func(t *testing.T) { 18 | var res []string 19 | err := InterfaceToObject([]interface{}{"a", "b", "c"}, &res) 20 | assert.NoError(t, err) 21 | assert.Equal(t, res[0], "a") 22 | }) 23 | 24 | t.Run("fail to parse no corresponding interface types", func(t *testing.T) { 25 | var res []int 26 | err := InterfaceToObject([]interface{}{"a", "b", "c"}, &res) 27 | assert.Error(t, err) 28 | }) 29 | 30 | t.Run("fail to parse nil interface types", func(t *testing.T) { 31 | var res int 32 | err := InterfaceToObject(&invalidMarshaller{}, &res) 33 | assert.Error(t, err) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/common/rand.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func seedRand() *rand.Rand { 9 | return rand.New(rand.NewSource(time.Now().UnixNano())) 10 | } 11 | 12 | func RandString(n int) string { 13 | var seededRand = seedRand() 14 | var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 15 | 16 | b := make([]rune, n) 17 | for i := range b { 18 | b[i] = letter[seededRand.Intn(len(letter))] 19 | } 20 | return string(b) 21 | } 22 | 23 | func RandHexString(n int) string { 24 | var seededRand = seedRand() 25 | var letterRunes = []rune("abcdef0123456789") 26 | b := make([]rune, n) 27 | for i := range b { 28 | b[i] = letterRunes[seededRand.Intn(len(letterRunes))] 29 | } 30 | return string(b) 31 | } 32 | 33 | func RandInt(n int) int { 34 | var seededRand = seedRand() 35 | return seededRand.Intn(n) 36 | } 37 | 38 | func RandIntRange(min, max int) int { 39 | var seededRand = seedRand() 40 | return seededRand.Intn(max-min) + min 41 | } 42 | -------------------------------------------------------------------------------- /src/stores/entities/attributes.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | // CryptoOperation type of crypto operation 6 | type CryptoOperation string 7 | 8 | const ( 9 | Signing = "signing" 10 | Encryption = "encryption" 11 | ) 12 | 13 | // RecoveryPolicy policies for recovering a deleted item 14 | type RecoveryPolicy string 15 | 16 | // Attributes are user set configuration and information attached to stored item 17 | type Attributes struct { 18 | // Operations supported by a stored item (e.g. sign, encrypt...) 19 | Operations []CryptoOperation 20 | 21 | // Disabled whether item is disabled 22 | Disabled bool 23 | 24 | // TTL 25 | TTL time.Duration 26 | 27 | // Recovery policy about a key after being deleted before being destroyed 28 | Recovery *Recovery 29 | 30 | // Tags attached to a stored item 31 | Tags map[string]string 32 | } 33 | 34 | type Recovery struct { 35 | // Policy for recovery 36 | Policy RecoveryPolicy 37 | 38 | // Period for recovery 39 | Period time.Time 40 | } 41 | -------------------------------------------------------------------------------- /pkg/http/testutils/request_matcher.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/golang/mock/gomock" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | type requestMatcher struct { 13 | t *testing.T 14 | 15 | urlPath string 16 | body []byte 17 | } 18 | 19 | func RequestMatcher(t *testing.T, urlPath string, body []byte) gomock.Matcher { 20 | return &requestMatcher{ 21 | t: t, 22 | urlPath: urlPath, 23 | body: body, 24 | } 25 | } 26 | 27 | func (m requestMatcher) Matches(x interface{}) bool { 28 | req, ok := x.(*http.Request) 29 | if !ok { 30 | return false 31 | } 32 | 33 | b := make([]byte, req.ContentLength) 34 | _, _ = io.ReadFull(req.Body, b) 35 | 36 | urlMatch := assert.Equal(m.t, m.urlPath, req.URL.Path, "URL path should match") 37 | bodyMatch := assert.Equal(m.t, m.body, b, "Body should match") 38 | 39 | return urlMatch && bodyMatch 40 | } 41 | 42 | func (m requestMatcher) String() string { 43 | return "" 44 | } 45 | -------------------------------------------------------------------------------- /src/stores/app/service.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/auth" 5 | "github.com/consensys/quorum-key-manager/src/infra/log" 6 | "github.com/consensys/quorum-key-manager/src/infra/postgres" 7 | "github.com/consensys/quorum-key-manager/src/stores/api/http" 8 | "github.com/consensys/quorum-key-manager/src/stores/connectors/stores" 9 | db "github.com/consensys/quorum-key-manager/src/stores/database/postgres" 10 | "github.com/consensys/quorum-key-manager/src/vaults" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | func RegisterService(router *mux.Router, logger log.Logger, postgresClient postgres.Client, roles auth.Roles, vaultsService vaults.Vaults) *stores.Connector { 15 | // Data layer 16 | storesDB := db.New(logger, postgresClient) 17 | 18 | // Business layer 19 | storesService := stores.NewConnector(roles, storesDB, vaultsService, logger) 20 | 21 | // Service layer 22 | http.NewStoresHandler(storesService).Register(router) 23 | 24 | return storesService 25 | } 26 | -------------------------------------------------------------------------------- /src/nodes/interceptor/eth_sign.go: -------------------------------------------------------------------------------- 1 | package interceptor 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/jsonrpc" 7 | "github.com/consensys/quorum-key-manager/src/auth/api/http" 8 | ethcommon "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | ) 11 | 12 | func (i *Interceptor) ethSign(ctx context.Context, from ethcommon.Address, data hexutil.Bytes) (*hexutil.Bytes, error) { 13 | logger := i.logger.With("from_account", from.Hex()) 14 | logger.Debug("signing payload") 15 | 16 | store, err := i.stores.EthereumByAddr(ctx, from, http.UserInfoFromContext(ctx)) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | sig, err := store.Sign(ctx, from, data) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | logger.Info("payload signed successfully") 27 | return (*hexutil.Bytes)(&sig), nil 28 | } 29 | 30 | func (i *Interceptor) EthSign() jsonrpc.Handler { 31 | h, _ := jsonrpc.MakeHandler(i.ethSign) 32 | return h 33 | } 34 | -------------------------------------------------------------------------------- /src/stores/connectors/ethereum/ethereum.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/auth" 5 | "github.com/consensys/quorum-key-manager/src/entities" 6 | "github.com/consensys/quorum-key-manager/src/infra/log" 7 | "github.com/consensys/quorum-key-manager/src/stores" 8 | "github.com/consensys/quorum-key-manager/src/stores/database" 9 | ) 10 | 11 | type Connector struct { 12 | store stores.KeyStore 13 | logger log.Logger 14 | db database.ETHAccounts 15 | authorizator auth.Authorizator 16 | } 17 | 18 | var _ stores.EthStore = Connector{} 19 | 20 | var ethAlgo = &entities.Algorithm{ 21 | Type: entities.Ecdsa, 22 | EllipticCurve: entities.Secp256k1, 23 | } 24 | 25 | func NewConnector(store stores.KeyStore, db database.ETHAccounts, authorizator auth.Authorizator, logger log.Logger) *Connector { 26 | return &Connector{ 27 | store: store, 28 | logger: logger, 29 | db: db, 30 | authorizator: authorizator, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/json/duration_test.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestMarshal(t *testing.T) { 13 | tests := []struct { 14 | desc string 15 | 16 | // JSON body of the request 17 | bytes []byte 18 | expectedDuration Duration 19 | }{ 20 | { 21 | desc: "int", 22 | bytes: []byte(`20`), 23 | expectedDuration: Duration{time.Duration(20)}, 24 | }, 25 | { 26 | desc: "string", 27 | bytes: []byte(`"15s30ns"`), 28 | expectedDuration: Duration{time.Duration(15000000030)}, 29 | }, 30 | } 31 | 32 | for _, tt := range tests { 33 | t.Run(tt.desc, func(t *testing.T) { 34 | dur := new(Duration) 35 | err := json.Unmarshal(tt.bytes, dur) 36 | require.NoError(t, err, "Unmarshal should not error") 37 | assert.Equal(t, tt.expectedDuration, *dur, "Duration should be correct") 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/infra/hashicorp/client/parsers.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/consensys/quorum-key-manager/pkg/errors" 8 | "github.com/hashicorp/vault/api" 9 | ) 10 | 11 | func parseErrorResponse(err error) error { 12 | httpError, ok := err.(*api.ResponseError) 13 | if !ok { 14 | return errors.HashicorpVaultError(fmt.Sprintf("failed to connect to Hashicorp store: %s", err.Error())) 15 | } 16 | 17 | switch httpError.StatusCode { 18 | case http.StatusNotFound: 19 | return errors.NotFoundError(httpError.Error()) 20 | case http.StatusBadRequest: 21 | return errors.InvalidFormatError(httpError.Error()) 22 | case http.StatusUnprocessableEntity: 23 | return errors.InvalidParameterError(httpError.Error()) 24 | case http.StatusConflict: 25 | return errors.AlreadyExistsError(httpError.Error()) 26 | case http.StatusTooManyRequests: 27 | return errors.TooManyRequestError(httpError.Error()) 28 | default: 29 | return errors.HashicorpVaultError(httpError.Error()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/vaults/service.go: -------------------------------------------------------------------------------- 1 | package vaults 2 | 3 | import ( 4 | "context" 5 | 6 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | "github.com/consensys/quorum-key-manager/src/entities" 8 | ) 9 | 10 | //go:generate mockgen -source=service.go -destination=mock/service.go -package=mock 11 | 12 | type Vaults interface { 13 | // CreateHashicorp creates a Hashicorp Vault client 14 | CreateHashicorp(ctx context.Context, name string, config *entities.HashicorpConfig, allowedTenants []string, userInfo *auth.UserInfo) error 15 | 16 | // CreateAzure creates an AKV client 17 | CreateAzure(ctx context.Context, name string, config *entities.AzureConfig, allowedTenants []string, userInfo *auth.UserInfo) error 18 | 19 | // CreateAWS creates an AWS KMS client 20 | CreateAWS(ctx context.Context, name string, config *entities.AWSConfig, allowedTenants []string, userInfo *auth.UserInfo) error 21 | 22 | // Get gets a valut by name 23 | Get(ctx context.Context, name string, userInfo *auth.UserInfo) (*entities.Vault, error) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_uri.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "path" 7 | ) 8 | 9 | // ExtractURI parses request URI, updates request URL and optional reset URI 10 | func ExtractURI(reset bool) Preparer { 11 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 12 | if req.RequestURI == "" { 13 | return req, nil 14 | } 15 | 16 | u, err := url.ParseRequestURI(req.RequestURI) 17 | if err != nil { 18 | return req, err 19 | } 20 | 21 | if req.URL.Path != u.Path { 22 | req.URL.RawPath = path.Join(fistNotEmpty(req.URL.RawPath, req.URL.Path), fistNotEmpty(u.RawPath, u.Path)) 23 | req.URL.Path = path.Join(req.URL.Path, u.Path) 24 | 25 | } 26 | 27 | req.URL.RawQuery = u.RawQuery 28 | if reset { 29 | req.RequestURI = "" 30 | req.Host = "" 31 | } 32 | 33 | return req, nil 34 | }) 35 | } 36 | 37 | func fistNotEmpty(values ...string) string { 38 | for _, v := range values { 39 | if v != "" { 40 | return v 41 | } 42 | } 43 | 44 | return "" 45 | } 46 | -------------------------------------------------------------------------------- /src/aliases/service/registries/delete.go: -------------------------------------------------------------------------------- 1 | package registries 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 10 | ) 11 | 12 | func (s *Registries) Delete(ctx context.Context, name string, userInfo *auth.UserInfo) error { 13 | logger := s.logger.With("name", name) 14 | 15 | resolver := authorizator.New(s.roles.UserPermissions(ctx, userInfo), userInfo.Tenant, logger) 16 | err := resolver.CheckPermission(&auth.Operation{Action: auth.ActionDelete, Resource: auth.ResourceAlias}) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | err = s.db.Delete(ctx, name, userInfo.Tenant) 22 | if err != nil { 23 | errMessage := "failed to delete registry" 24 | logger.WithError(err).Error(errMessage) 25 | return errors.FromError(err).SetMessage(errMessage) 26 | } 27 | 28 | logger.Info("alias registry deleted successfully") 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /src/stores/connectors/stores/create_eth.go: -------------------------------------------------------------------------------- 1 | package stores 2 | 3 | import ( 4 | "context" 5 | 6 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 8 | "github.com/consensys/quorum-key-manager/src/stores/entities" 9 | ) 10 | 11 | func (c *Connector) CreateEthereum(ctx context.Context, name, keyStore string, allowedTenants []string, userInfo *auth.UserInfo) error { 12 | logger := c.logger.With("name", name, "key_store", keyStore) 13 | logger.Debug("creating ethereum store") 14 | 15 | // TODO: Uncomment when authManager no longer a runnable 16 | // permissions := c.authManager.UserPermissions(userInfo) 17 | resolver := authorizator.New(userInfo.Permissions, userInfo.Tenant, c.logger) 18 | 19 | store, err := c.getKeyStore(ctx, keyStore, resolver) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | c.createStore(name, entities.EthereumStoreType, store, allowedTenants) 25 | 26 | logger.Info("ethereum store created successfully") 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/http/proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/oxtoacart/bpool" 11 | ) 12 | 13 | type staticTransport struct { 14 | res *http.Response 15 | } 16 | 17 | func (t *staticTransport) RoundTrip(r *http.Request) (*http.Response, error) { 18 | t.res.Request = r 19 | return t.res, nil 20 | } 21 | 22 | func BenchmarkProxy(b *testing.B) { 23 | res := &http.Response{ 24 | StatusCode: 200, 25 | Header: make(http.Header), 26 | Body: ioutil.NopCloser(strings.NewReader("")), 27 | } 28 | 29 | w := httptest.NewRecorder() 30 | req, _ := http.NewRequest(http.MethodGet, "http://foo.bar/", nil) 31 | 32 | proxy, err := New( 33 | testCfg(false), 34 | &staticTransport{res}, 35 | nil, 36 | nil, 37 | nil, 38 | bpool.NewBytePool(32, 1024), 39 | ) 40 | 41 | if err != nil { 42 | b.Errorf("Could not build proxy: %v", err) 43 | } 44 | 45 | b.ReportAllocs() 46 | for i := 0; i < b.N; i++ { 47 | proxy.ServeHTTP(w, req) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Get-Started/Use-Docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Run Quorum Key Manager from Docker image 3 | sidebar_position: 1 4 | --- 5 | 6 | # Run Quorum Key Manager from Docker image 7 | 8 | Use the provided Docker image to run Quorum Key Manager (QKM) in a Docker container without installing QKM. 9 | 10 | ## Prerequisites 11 | 12 | - [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) 13 | - MacOS or Linux 14 | 15 | :::caution 16 | 17 | The Docker image does not run on Windows. 18 | 19 | ::: 20 | 21 | ### Run Quorum Key Manager 22 | 23 | Download the latest QKM [Docker compose file](https://github.com/ConsenSys/quorum-key-manager/blob/main/docker-compose.yml). 24 | 25 | Specify a path to your [manifest file or folder](../HowTo/Use-Manifest-File/Overview.md) in an environment variable: 26 | 27 | ```bash 28 | export HOST_MANIFEST_PATH= 29 | ``` 30 | 31 | Start QKM using Docker Compose: 32 | 33 | ```bash 34 | docker-compose -f docker-compose.yml up key-manager 35 | ``` 36 | -------------------------------------------------------------------------------- /src/infra/postgres/client/queries.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/consensys/quorum-key-manager/src/infra/postgres" 8 | ) 9 | 10 | func QuerySearchIDs(ctx context.Context, client postgres.Client, table, idCol, whereCond string, whereArgs []interface{}, isDeleted bool, limit, offset uint64) ([]string, error) { 11 | var ids []string 12 | var err error 13 | var query string 14 | 15 | switch { 16 | case limit != 0 || offset != 0: 17 | query = fmt.Sprintf("SELECT (array_agg(%s ORDER BY created_at ASC))[%d:%d] FROM %s WHERE %s", idCol, offset+1, offset+limit, table, whereCond) 18 | default: 19 | query = fmt.Sprintf("SELECT array_agg(%s ORDER BY created_at ASC) FROM %s WHERE %s", idCol, table, whereCond) 20 | } 21 | 22 | if isDeleted { 23 | query = fmt.Sprintf("%s AND deleted_at is NOT NULL", query) 24 | } else { 25 | query = fmt.Sprintf("%s AND deleted_at is NULL", query) 26 | } 27 | 28 | err = client.Query(ctx, &ids, query, whereArgs...) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return ids, nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/http/client/client.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/http/transport" 7 | ) 8 | 9 | // Client is an HTTP client 10 | type Client interface { 11 | // Do sends an HTTP request and returns an HTTP response, following 12 | // policy (such as redirects, cookies, auth) 13 | Do(*http.Request) (*http.Response, error) 14 | 15 | // CloseIdleConnections closes any connections on its Transport which 16 | // were previously connected from previous requests but are now 17 | // sitting idle in a "keep-alive" state. It does not interrupt any 18 | // connections currently in use. 19 | CloseIdleConnections() 20 | } 21 | 22 | // New creates a new HTTP client 23 | func New( 24 | cfg *Config, 25 | trnsprt http.RoundTripper, 26 | ) (Client, error) { 27 | var err error 28 | if trnsprt == nil { 29 | trnsprt, err = transport.New(cfg.Transport) 30 | if err != nil { 31 | return nil, err 32 | } 33 | } 34 | 35 | return &http.Client{ 36 | Transport: trnsprt, 37 | Timeout: cfg.Timeout.Duration, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/http/request/preparer_forwarded_for.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | // ForwardedFor populates "X-Forwarded-For" header with the client IP address 10 | // In case, "X-Forwarded-For" was already populated (e.g. if we are not the first proxy) 11 | // then retains prior X-Forwarded-For information as a comma+space 12 | // separated list and fold multiple headers into one. 13 | func ForwardedFor() Preparer { 14 | return PrepareFunc(func(req *http.Request) (*http.Request, error) { 15 | if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { 16 | // If we aren't the first proxy retain prior 17 | // X-Forwarded-For information as a comma+space 18 | // separated list and fold multiple headers into one. 19 | prior, ok := req.Header["X-Forwarded-For"] 20 | omit := ok && prior == nil 21 | if len(prior) > 0 { 22 | clientIP = strings.Join(prior, ", ") + ", " + clientIP 23 | } 24 | if !omit { 25 | req.Header.Set("X-Forwarded-For", clientIP) 26 | } 27 | } 28 | 29 | return req, nil 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/list.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (c Connector) List(ctx context.Context, limit, offset uint64) ([]string, error) { 10 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionRead, Resource: entities.ResourceKey}) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | ids, err := c.db.SearchIDs(ctx, false, limit, offset) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | c.logger.Debug("keys listed successfully") 21 | return ids, nil 22 | } 23 | 24 | func (c Connector) ListDeleted(ctx context.Context, limit, offset uint64) ([]string, error) { 25 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionRead, Resource: entities.ResourceKey}) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | ids, err := c.db.SearchIDs(ctx, true, limit, offset) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | c.logger.Debug("deleted keys listed successfully") 36 | return ids, nil 37 | } 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION=nonroot 2 | 3 | # Build the manager binary 4 | FROM golang:1.16 as builder 5 | 6 | ARG TARGETOS 7 | ARG TARGETARCH 8 | ENV CGO_ENABLED=0 9 | ENV GO111MODULE=on 10 | 11 | RUN apt-get update && \ 12 | apt-get install --no-install-recommends -y \ 13 | upx-ucl 14 | 15 | WORKDIR /app 16 | # Copy the Go Modules manifests 17 | COPY go.mod go.mod 18 | COPY go.sum go.sum 19 | # cache deps before building and copying source so that we don't need to re-download as much 20 | # and so that source changes don't invalidate our downloaded layer 21 | RUN go mod download 22 | 23 | # Copy the go source 24 | COPY . . 25 | 26 | # Build 27 | RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -o /bin/main main.go 28 | RUN upx /bin/main 29 | 30 | # Use distroless as minimal base image to package the manager binary 31 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 32 | FROM gcr.io/distroless/static:$VERSION 33 | WORKDIR / 34 | COPY --from=builder /bin/main . 35 | COPY ./deps/migrations /migrations 36 | COPY LICENSE . 37 | USER 65532:65532 38 | 39 | ENTRYPOINT ["/main"] 40 | -------------------------------------------------------------------------------- /cmd/flags/apikey.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/consensys/quorum-key-manager/src/infra/api-key/csv" 7 | "github.com/spf13/pflag" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func init() { 12 | _ = viper.BindEnv(authAPIKeyFileViperKey, authAPIKeyFileEnv) 13 | } 14 | 15 | const ( 16 | authAPIKeyFileFlag = "auth-api-key-file" 17 | authAPIKeyFileViperKey = "auth.api.key.file" 18 | authAPIKeyDefaultFileFlag = "" 19 | authAPIKeyFileEnv = "AUTH_API_KEY_FILE" 20 | ) 21 | 22 | func APIKeyFlags(f *pflag.FlagSet) { 23 | authAPIKeyFile(f) 24 | } 25 | 26 | func authAPIKeyFile(f *pflag.FlagSet) { 27 | desc := fmt.Sprintf(`API key CSV file location. 28 | Environment variable: %q`, authAPIKeyFileEnv) 29 | f.String(authAPIKeyFileFlag, authAPIKeyDefaultFileFlag, desc) 30 | _ = viper.BindPFlag(authAPIKeyFileViperKey, f.Lookup(authAPIKeyFileFlag)) 31 | } 32 | 33 | func NewAPIKeyConfig(vipr *viper.Viper) *csv.Config { 34 | path := vipr.GetString(authAPIKeyFileViperKey) 35 | 36 | if path != "" { 37 | return csv.NewConfig(path) 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | # Review gh actions docs if you want to further define triggers, paths, etc 9 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 10 | 11 | jobs: 12 | lint: 13 | uses: ./.github/workflows/lint.yml 14 | case: 15 | uses: ./.github/workflows/case.yml 16 | build: 17 | needs: [lint, case] 18 | uses: ./.github/workflows/build.yml 19 | release: 20 | needs: build 21 | name: Create Release 22 | runs-on: ubuntu-latest 23 | # the enviroment to deploy to / use secrets from 24 | environment: no-secret 25 | # modify the default permissions of the GITHUB_TOKEN, so as to only allow least priveleges 26 | permissions: 27 | contents: write 28 | issues: write 29 | pull-requests: write 30 | steps: 31 | - uses: actions/checkout@v3 32 | - name: Release 33 | uses: ConsenSys/docs-gha/release@main 34 | with: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /src/stores/connectors/secrets/list.go: -------------------------------------------------------------------------------- 1 | package secrets 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | ) 8 | 9 | func (c Connector) List(ctx context.Context, limit, offset uint64) ([]string, error) { 10 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionRead, Resource: entities.ResourceSecret}) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | ids, err := c.db.SearchIDs(ctx, false, limit, offset) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | c.logger.Debug("secrets listed successfully") 21 | return ids, nil 22 | } 23 | 24 | func (c Connector) ListDeleted(ctx context.Context, limit, offset uint64) ([]string, error) { 25 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionRead, Resource: entities.ResourceSecret}) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | ids, err := c.db.SearchIDs(ctx, true, limit, offset) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | c.logger.Debug("deleted secrets listed successfully") 36 | return ids, nil 37 | } 38 | -------------------------------------------------------------------------------- /src/stores/secrets.go: -------------------------------------------------------------------------------- 1 | package stores 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/stores/entities" 7 | ) 8 | 9 | //go:generate mockgen -source=secrets.go -destination=mock/secrets.go -package=mock 10 | 11 | type SecretStore interface { 12 | // Set secret 13 | Set(ctx context.Context, id, value string, attr *entities.Attributes) (*entities.Secret, error) 14 | 15 | // Get a secret 16 | Get(ctx context.Context, id string, version string) (*entities.Secret, error) 17 | 18 | // List secrets 19 | List(ctx context.Context, limit, offset uint64) ([]string, error) 20 | 21 | // Delete secret not permanently, it can be restored 22 | Delete(ctx context.Context, id string) error 23 | 24 | // GetDeleted secrets 25 | GetDeleted(ctx context.Context, id string) (*entities.Secret, error) 26 | 27 | // ListDeleted secrets 28 | ListDeleted(ctx context.Context, limit, offset uint64) ([]string, error) 29 | 30 | // Restore a previously deleted secret 31 | Restore(ctx context.Context, id string) error 32 | 33 | // Destroy secret permanently 34 | Destroy(ctx context.Context, id string) error 35 | } 36 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Check for lint/build errors 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - main 8 | workflow_call: 9 | 10 | jobs: 11 | lint: 12 | name: Lint Code Base, Spelling, Link Check 13 | runs-on: ubuntu-latest 14 | # the enviroment to deploy to / use secrets from 15 | environment: no-secret 16 | # modify the default permissions of the GITHUB_TOKEN, so as to only allow least priveleges 17 | permissions: 18 | contents: read 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Lint 23 | uses: ConsenSys/docs-gha/lint@main 24 | with: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | linkCheck: 28 | name: Link Checking 29 | runs-on: ubuntu-latest 30 | environment: no-secret 31 | strategy: 32 | matrix: 33 | file-extensions: [".md", ".mdx"] 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: LinkCheck 37 | uses: ConsenSys/docs-gha/linkcheck@main 38 | with: 39 | FILE_EXTENSION: ${{ matrix.file-extensions }} 40 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/delete.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | 10 | "github.com/consensys/quorum-key-manager/src/stores/database" 11 | ) 12 | 13 | func (c Connector) Delete(ctx context.Context, id string) error { 14 | logger := c.logger.With("id", id) 15 | logger.Debug("deleting key") 16 | 17 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionDelete, Resource: entities.ResourceKey}) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | err = c.db.RunInTransaction(ctx, func(dbtx database.Keys) error { 23 | derr := dbtx.Delete(ctx, id) 24 | if derr != nil { 25 | return derr 26 | } 27 | 28 | derr = c.store.Delete(ctx, id) 29 | if derr != nil && !errors.IsNotSupportedError(derr) { // If the underlying store does not support deleting, we only delete in DB 30 | return derr 31 | } 32 | 33 | return nil 34 | }) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | logger.Info("key deleted successfully") 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /src/stores/connectors/secrets/set.go: -------------------------------------------------------------------------------- 1 | package secrets 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/errors" 7 | 8 | authentities "github.com/consensys/quorum-key-manager/src/auth/entities" 9 | 10 | "github.com/consensys/quorum-key-manager/src/stores/entities" 11 | ) 12 | 13 | func (c Connector) Set(ctx context.Context, id, value string, attr *entities.Attributes) (*entities.Secret, error) { 14 | logger := c.logger.With("id", id) 15 | logger.Debug("creating secret") 16 | 17 | err := c.authorizator.CheckPermission(&authentities.Operation{Action: authentities.ActionWrite, Resource: authentities.ResourceSecret}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | secret, err := c.store.Set(ctx, id, value, attr) 23 | if err != nil && errors.IsAlreadyExistsError(err) { 24 | secret, err = c.store.Get(ctx, id, "") 25 | } 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | _, err = c.db.Add(ctx, secret) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | logger.Info("secret created successfully", "version", secret.Metadata.Version) 36 | return secret, nil 37 | } 38 | -------------------------------------------------------------------------------- /cmd/flags/tls.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | 6 | tls "github.com/consensys/quorum-key-manager/src/infra/tls/filesystem" 7 | "github.com/spf13/pflag" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func init() { 12 | _ = viper.BindEnv(authTLSCertsFileViperKey, authTLSCertsFileEnv) 13 | } 14 | 15 | const ( 16 | authTLSCertsFileFlag = "auth-tls-ca" 17 | authTLSCertsFileViperKey = "auth.tls.ca" 18 | authTLSCertsFileDefault = "" 19 | authTLSCertsFileEnv = "AUTH_TLS_CA" 20 | ) 21 | 22 | func TLSFlags(f *pflag.FlagSet) { 23 | authTLSCertFile(f) 24 | } 25 | 26 | func authTLSCertFile(f *pflag.FlagSet) { 27 | desc := fmt.Sprintf(`TLS Authenticator Cert filepath. 28 | Environment variable: %q`, authTLSCertsFileEnv) 29 | f.String(authTLSCertsFileFlag, authTLSCertsFileDefault, desc) 30 | _ = viper.BindPFlag(authTLSCertsFileViperKey, f.Lookup(authTLSCertsFileFlag)) 31 | } 32 | 33 | func NewTLSConfig(vipr *viper.Viper) *tls.Config { 34 | path := vipr.GetString(authTLSCertsFileViperKey) 35 | 36 | if path != "" { 37 | return tls.NewConfig(vipr.GetString(authTLSCertsFileViperKey)) 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /deps/config/manifests/store.yml.template: -------------------------------------------------------------------------------- 1 | - kind: Store 2 | type: secret 3 | name: hashicorp-secrets 4 | # allowed_tenants: [] 5 | specs: 6 | vault: hashicorp-kv-v2 7 | 8 | - kind: Store 9 | type: key 10 | name: hashicorp-keys 11 | # allowed_tenants: [] 12 | specs: 13 | vault: hashicorp-quorum 14 | 15 | - kind: Store 16 | type: secret 17 | name: akv-secrets 18 | # allowed_tenants: [] 19 | specs: 20 | vault: akv-europe 21 | 22 | - kind: Store 23 | type: key 24 | name: akv-keys 25 | # allowed_tenants: [] 26 | specs: 27 | vault: akv-europe 28 | 29 | - kind: Store 30 | type: secret 31 | name: aws-secrets 32 | # allowed_tenants: [] 33 | specs: 34 | vault: aws-europe 35 | 36 | - kind: Store 37 | type: key 38 | name: aws-keys 39 | # allowed_tenants: [] 40 | specs: 41 | vault: aws-europe 42 | 43 | - kind: Store 44 | type: key 45 | name: local-keys 46 | # allowed_tenants: [] 47 | specs: 48 | secret_store: hashicorp-secrets 49 | 50 | - kind: Store 51 | type: ethereum 52 | name: eth-accounts 53 | # allowed_tenants: [] 54 | specs: 55 | key_store: hashicorp-keys 56 | -------------------------------------------------------------------------------- /src/aliases/service/aliases/delete.go: -------------------------------------------------------------------------------- 1 | package aliases 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 10 | ) 11 | 12 | func (s *Aliases) Delete(ctx context.Context, registry, key string, userInfo *auth.UserInfo) error { 13 | logger := s.logger.With("registry", registry, "key", key) 14 | 15 | resolver := authorizator.New(s.roles.UserPermissions(ctx, userInfo), userInfo.Tenant, logger) 16 | err := resolver.CheckPermission(&auth.Operation{Action: auth.ActionDelete, Resource: auth.ResourceAlias}) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | _, err = s.Get(ctx, registry, key, userInfo) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | err = s.aliasDB.Delete(ctx, registry, key) 27 | if err != nil { 28 | errMessage := "failed to delete alias" 29 | logger.WithError(err).Error(errMessage) 30 | return errors.FromError(err).SetMessage(errMessage) 31 | } 32 | 33 | logger.Info("alias deleted successfully") 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /src/aliases/service/registries/get.go: -------------------------------------------------------------------------------- 1 | package registries 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 10 | "github.com/consensys/quorum-key-manager/src/entities" 11 | ) 12 | 13 | func (s *Registries) Get(ctx context.Context, name string, userInfo *auth.UserInfo) (*entities.AliasRegistry, error) { 14 | logger := s.logger.With("name", name) 15 | 16 | resolver := authorizator.New(s.roles.UserPermissions(ctx, userInfo), userInfo.Tenant, logger) 17 | err := resolver.CheckPermission(&auth.Operation{Action: auth.ActionRead, Resource: auth.ResourceAlias}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | registry, err := s.db.FindOne(ctx, name, userInfo.Tenant) 23 | if err != nil { 24 | errMessage := "failed to get registry" 25 | logger.WithError(err).Error(errMessage) 26 | return nil, errors.FromError(err).SetMessage(errMessage) 27 | } 28 | 29 | logger.Debug("alias registry retrieved successfully") 30 | return registry, nil 31 | } 32 | -------------------------------------------------------------------------------- /src/stores/connectors/secrets/delete.go: -------------------------------------------------------------------------------- 1 | package secrets 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | "github.com/consensys/quorum-key-manager/src/stores/database" 10 | ) 11 | 12 | func (c Connector) Delete(ctx context.Context, id string) error { 13 | logger := c.logger.With("id", id) 14 | logger.Debug("deleting secret") 15 | 16 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionDelete, Resource: entities.ResourceSecret}) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | err = c.db.RunInTransaction(ctx, func(dbtx database.Secrets) error { 22 | derr := dbtx.Delete(ctx, id) 23 | if derr != nil { 24 | return derr 25 | } 26 | 27 | derr = c.store.Delete(ctx, id) 28 | if derr != nil && !errors.IsNotSupportedError(derr) { // If the underlying store does not support deleting, we only delete in DB 29 | return derr 30 | } 31 | 32 | return nil 33 | }) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | logger.Info("secret deleted successfully") 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /tests/e2e/environment.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "context" 5 | "github.com/consensys/quorum-key-manager/pkg/client" 6 | "github.com/consensys/quorum-key-manager/src/infra/log" 7 | "github.com/consensys/quorum-key-manager/src/infra/log/zap" 8 | "github.com/consensys/quorum-key-manager/tests" 9 | "net/http" 10 | ) 11 | 12 | type Environment struct { 13 | ctx context.Context 14 | logger log.Logger 15 | httpClient *http.Client 16 | client client.KeyManagerClient 17 | cfg *tests.Config 18 | } 19 | 20 | func NewEnvironment() (*Environment, error) { 21 | cfg, err := tests.NewConfig() 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | logger, err := zap.NewLogger(zap.NewConfig(zap.InfoLevel, zap.JSONFormat)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | keyManagerClient := client.NewHTTPClient( 32 | &http.Client{Transport: NewTestHttpTransport("", cfg.ApiKey, nil)}, 33 | &client.Config{URL: cfg.KeyManagerURL}, 34 | ) 35 | 36 | return &Environment{ 37 | ctx: context.Background(), 38 | logger: logger, 39 | client: keyManagerClient, 40 | cfg: cfg, 41 | }, nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/flags/manifest.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "fmt" 5 | 6 | manifests "github.com/consensys/quorum-key-manager/src/infra/manifests/yaml" 7 | "github.com/spf13/pflag" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func init() { 12 | viper.SetDefault(manifestPathViperKey, manifestPathDefault) 13 | _ = viper.BindEnv(manifestPathViperKey, manifestPathEnv) 14 | } 15 | 16 | const ( 17 | ManifestPath = "manifest-path" 18 | manifestPathEnv = "MANIFEST_PATH" 19 | manifestPathViperKey = "manifest.path" 20 | manifestPathDefault = "" 21 | ) 22 | 23 | func manifestPath(f *pflag.FlagSet) { 24 | desc := fmt.Sprintf(`Path to manifest file/folder to configure key manager stores and nodes 25 | Environment variable: %q`, manifestPathEnv) 26 | f.String(ManifestPath, manifestPathDefault, desc) 27 | _ = viper.BindPFlag(manifestPathViperKey, f.Lookup(ManifestPath)) 28 | } 29 | 30 | // ManifestFlags register flags for Node 31 | func ManifestFlags(f *pflag.FlagSet) { 32 | manifestPath(f) 33 | } 34 | 35 | func NewManifestConfig(vipr *viper.Viper) *manifests.Config { 36 | return manifests.NewConfig(vipr.GetString(manifestPathViperKey)) 37 | } 38 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Reference/Responsible-Disclosure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Security disclosure 3 | description: QKM responsible disclosure statement. 4 | sidebar_position: 4 5 | --- 6 | 7 | # Responsible disclosure policy 8 | 9 | At ConsenSys, security is a priority. However, regardless of how much effort we put into system security, there may still be vulnerabilities. 10 | 11 | If you discover a vulnerability, please do the following so we can address it as quickly as possible: 12 | 13 | - E-mail your findings to . Provide sufficient information to reproduce the problem, so we can resolve it as quickly as possible. 14 | - Don't take advantage of the vulnerability you've discovered. 15 | - Practice responsible disclosure. That is, don't reveal the problem to others until one of the following happens: 16 | - We release a fix for the disclosure. 17 | - 90 days pass. 18 | - We waive responsible disclosure. 19 | 20 | We will acknowledge receipt of your vulnerability report the next business day and send you regular updates about our progress. 21 | 22 | Thank you for helping us protect our clients and systems. 23 | -------------------------------------------------------------------------------- /pkg/ethereum/caller.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/jsonrpc" 5 | ) 6 | 7 | //go:generate mockgen -source=caller.go -destination=mock/caller.go -package=mock 8 | 9 | type Caller interface { 10 | Eth() EthCaller 11 | EEA() EEACaller 12 | Priv() PrivCaller 13 | } 14 | 15 | // Caller implement methods to interface with the JSON-RPC API of an ethereum caller 16 | // It is some kind of Web3 interface 17 | type caller struct { 18 | eth *ethCaller 19 | eea *eeaCaller 20 | priv *privCaller 21 | } 22 | 23 | // NewCaller creates a caller from a jsonrpc.Client 24 | func NewCaller(client jsonrpc.Client) Caller { 25 | return &caller{ 26 | eth: ðCaller{client}, 27 | eea: &eeaCaller{client}, 28 | priv: &privCaller{client}, 29 | } 30 | } 31 | 32 | // Eth return eth namespace caller 33 | func (c *caller) Eth() EthCaller { // nolint 34 | return c.eth 35 | } 36 | 37 | // EEA return eea namespace caller 38 | func (c *caller) EEA() EEACaller { // nolint 39 | return c.eea 40 | } 41 | 42 | // Priv return priv namespace caller 43 | func (c *caller) Priv() PrivCaller { // nolint 44 | return c.priv 45 | } 46 | -------------------------------------------------------------------------------- /src/vaults/service/vaults/create_hashicorp_test.go: -------------------------------------------------------------------------------- 1 | package vaults 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | entities2 "github.com/consensys/quorum-key-manager/src/auth/entities" 8 | "github.com/consensys/quorum-key-manager/src/auth/mock" 9 | "github.com/consensys/quorum-key-manager/src/entities" 10 | "github.com/consensys/quorum-key-manager/src/infra/log/testutils" 11 | "github.com/golang/mock/gomock" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestCreateHashicorp(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | defer ctrl.Finish() 18 | 19 | logger := testutils.NewMockLogger(ctrl) 20 | roles := mock.NewMockRoles(ctrl) 21 | vault := New(roles, logger) 22 | 23 | ctx := context.Background() 24 | vaultName := "hashicorp-vault" 25 | cfg := &entities.HashicorpConfig{} 26 | allowedTenants := []string{"tenant_id_1"} 27 | 28 | t.Run("should create Hashicorp vault client successfully", func(t *testing.T) { 29 | userInfo := &entities2.UserInfo{ 30 | Tenant: "tenant_id_1", 31 | } 32 | err := vault.CreateHashicorp(ctx, vaultName, cfg, allowedTenants, userInfo) 33 | assert.NoError(t, err) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/client/errors.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ResponseError is the error returned from the client when Key Manager responds with an error or 8 | // non-success HTTP status code. ResponseError gives 9 | // access to the underlying errors and status code. 10 | type ResponseError struct { 11 | // StatusCode is the HTTP status code. 12 | StatusCode int `json:"statusCode,omitempty" example:"404"` 13 | 14 | // ErrorCode is the key manager error code. 15 | ErrorCode string `json:"code,omitempty" example:"IR001"` 16 | 17 | // Errors are the underlying errors returned by Vault. 18 | Message string `json:"message" example:"error message"` 19 | } 20 | 21 | // ErrorResponse is the raw error type returned from the key manager 22 | type ErrorResponse struct { 23 | Message string `json:"message" example:"error message"` 24 | Code string `json:"code,omitempty" example:"IR001"` 25 | } 26 | 27 | // Error returns a human-readable error string for the response error. 28 | func (r *ResponseError) Error() string { 29 | return fmt.Sprintf("Error making API request.\nCode: %s. %s:\nStatus: %d.", r.ErrorCode, r.Message, r.StatusCode) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/jsonrpc/route_matcher.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // matchers try to match a request. 8 | type matcher interface { 9 | Match(*RequestMsg, *RouteMatch) bool 10 | } 11 | 12 | // RouteMatch stores information about a matched route 13 | type RouteMatch struct { 14 | Route *Route 15 | Handler Handler 16 | Err error 17 | } 18 | 19 | // versionMatcher matches request with a given version 20 | type versionMatcher struct { 21 | version string 22 | } 23 | 24 | func (matcher *versionMatcher) Match(msg *RequestMsg, _ *RouteMatch) bool { 25 | return msg.Version == matcher.version 26 | } 27 | 28 | // methodMatcher matches request with a given method 29 | type methodMatcher struct { 30 | method string 31 | } 32 | 33 | func (matcher *methodMatcher) Match(msg *RequestMsg, _ *RouteMatch) bool { 34 | return msg.Method == matcher.method 35 | } 36 | 37 | // methodMatcher matches request with a given prefix 38 | type methodPrefixMatcher struct { 39 | prefix string 40 | } 41 | 42 | func (matcher *methodPrefixMatcher) Match(msg *RequestMsg, _ *RouteMatch) bool { 43 | return strings.HasPrefix(msg.Method, matcher.prefix) 44 | } 45 | -------------------------------------------------------------------------------- /src/aliases/service/aliases/get.go: -------------------------------------------------------------------------------- 1 | package aliases 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 10 | "github.com/consensys/quorum-key-manager/src/entities" 11 | ) 12 | 13 | func (s *Aliases) Get(ctx context.Context, registry, key string, userInfo *auth.UserInfo) (*entities.Alias, error) { 14 | logger := s.logger.With("registry", registry, "key", key) 15 | 16 | resolver := authorizator.New(s.roles.UserPermissions(ctx, userInfo), userInfo.Tenant, logger) 17 | err := resolver.CheckPermission(&auth.Operation{Action: auth.ActionRead, Resource: auth.ResourceAlias}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | alias, err := s.aliasDB.FindOne(ctx, registry, key, userInfo.Tenant) 23 | if err != nil { 24 | errMessage := "failed to get alias" 25 | logger.WithError(err).Error(errMessage) 26 | return nil, errors.FromError(err).SetMessage(errMessage) 27 | } 28 | 29 | logger.Debug("alias registry retrieved successfully") 30 | return alias, nil 31 | } 32 | -------------------------------------------------------------------------------- /src/infra/log/testutils/faker.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "github.com/golang/mock/gomock" 5 | 6 | "github.com/consensys/quorum-key-manager/src/infra/log/mock" 7 | ) 8 | 9 | func NewMockLogger(ctrl *gomock.Controller) *mock.MockLogger { 10 | mockLogger := mock.NewMockLogger(ctrl) 11 | 12 | mockLogger.EXPECT().Debug(gomock.Any(), gomock.Any()).Return(mockLogger).AnyTimes() 13 | mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).Return(mockLogger).AnyTimes() 14 | mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).Return(mockLogger).AnyTimes() 15 | mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).Return(mockLogger).AnyTimes() 16 | mockLogger.EXPECT().Panic(gomock.Any(), gomock.Any()).Return(mockLogger).AnyTimes() 17 | mockLogger.EXPECT().Fatal(gomock.Any(), gomock.Any()).Return(mockLogger).AnyTimes() 18 | mockLogger.EXPECT().With(gomock.Any()).Return(mockLogger).AnyTimes() 19 | mockLogger.EXPECT().WithError(gomock.Any()).Return(mockLogger).AnyTimes() 20 | mockLogger.EXPECT().WithComponent(gomock.Any()).Return(mockLogger).AnyTimes() 21 | mockLogger.EXPECT().Write(gomock.Any()).Return(0, nil).AnyTimes() 22 | 23 | return mockLogger 24 | } 25 | -------------------------------------------------------------------------------- /src/nodes/service/nodes/get.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/errors" 7 | 8 | authtypes "github.com/consensys/quorum-key-manager/src/auth/entities" 9 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 10 | proxynode "github.com/consensys/quorum-key-manager/src/nodes/node/proxy" 11 | ) 12 | 13 | func (i *Nodes) Get(ctx context.Context, name string, userInfo *authtypes.UserInfo) (*proxynode.Node, error) { 14 | permissions := i.roles.UserPermissions(ctx, userInfo) 15 | resolver := authorizator.New(permissions, userInfo.Tenant, i.logger) 16 | 17 | err := resolver.CheckPermission(&authtypes.Operation{Action: authtypes.ActionProxy, Resource: authtypes.ResourceNode}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | node := i.getNode(ctx, name) 23 | if node == nil { 24 | errMessage := "node was not found" 25 | i.logger.Error(errMessage, "name", name) 26 | return nil, errors.NotFoundError(errMessage) 27 | } 28 | 29 | err = resolver.CheckAccess(node.AllowedTenants) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return node.Node, nil 35 | } 36 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Concepts/Authentication.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: User authentication 3 | description: Authentication concept page 4 | sidebar_position: 4 5 | --- 6 | 7 | # User authentication 8 | 9 | You can configure user authentication with Quorum Key Manager (QKM). This is optional but recommended. 10 | 11 | To authenticate to QKM, users must provide credentials in every request through one of the following methods: 12 | 13 | - [OAuth 2.0](../HowTo/Authenticate/OAuth2.md) - OAuth 2.0 standard using JSON Web Tokens 14 | - [TLS](../HowTo/Authenticate/TLS.md) - Client TLS mutual authentication 15 | - [API key](../HowTo/Authenticate/API-Key.md) - Set of static authorization keys defined in a CSV file and loaded at startup 16 | 17 | The authentication process consists of challenging incoming request credentials. If credentials are valid, QKM extracts user information and attaches it to the request context. If credentials are invalid, QKM rejects the request. If no credentials are passed, QKM processes the request as an anonymous request. 18 | 19 | After QKM authenticates a request, it submits the request to the targeted service to [authorize](Authorization.md) it. 20 | -------------------------------------------------------------------------------- /src/vaults/service/vaults/create_aws_test.go: -------------------------------------------------------------------------------- 1 | package vaults 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | entities2 "github.com/consensys/quorum-key-manager/src/auth/entities" 8 | "github.com/consensys/quorum-key-manager/src/auth/mock" 9 | "github.com/consensys/quorum-key-manager/src/entities" 10 | "github.com/consensys/quorum-key-manager/src/infra/log/testutils" 11 | "github.com/golang/mock/gomock" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestCreateAWS(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | defer ctrl.Finish() 18 | 19 | logger := testutils.NewMockLogger(ctrl) 20 | roles := mock.NewMockRoles(ctrl) 21 | vault := New(roles, logger) 22 | 23 | ctx := context.Background() 24 | vaultName := "aws-vault" 25 | allowedTenantID := "allowed_aws_tenant" 26 | cfg := &entities.AWSConfig{} 27 | allowedTenants := []string{allowedTenantID} 28 | 29 | t.Run("should create AWS vault client successfully", func(t *testing.T) { 30 | userInfo := &entities2.UserInfo{ 31 | Tenant: allowedTenantID, 32 | } 33 | err := vault.CreateAWS(ctx, vaultName, cfg, allowedTenants, userInfo) 34 | assert.NoError(t, err) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "gitignoreRoot": ".", 4 | "useGitignore": true, 5 | "dictionaries": [ 6 | "css", 7 | "html", 8 | "fonts", 9 | "typescript", 10 | "softwareTerms", 11 | "companies", 12 | "lorem-ipsum", 13 | "project-words" 14 | ], 15 | "dictionaryDefinitions": [ 16 | { 17 | "name": "project-words", 18 | "path": "./project-words.txt", 19 | "noSuggest": true 20 | } 21 | ], 22 | "ignorePaths": [ 23 | "CHANGELOG.md", 24 | "LICENSE", 25 | "package.json", 26 | "yarn.lock", 27 | "project-words.txt", 28 | "__snapshots__", 29 | "*.min.*", 30 | "jest/vendor", 31 | "docusaurus.config.js", 32 | "src/css/", 33 | "babel.config.js", 34 | "api/", 35 | "docs/test-api/", 36 | "sidebars.js", 37 | "tsconfig.*.json", 38 | ".github/**", 39 | "docs/Tutorials/EddsaBN254Sig.md", 40 | "docs/Tutorials/JsonRPCProxy.md", 41 | "docs/Tutorials/SendMetaTxn.md", 42 | "docs/Reference/CLI/CLI-Syntax.md", 43 | "docs/Tutorials/ConnectInfura.md" 44 | ], 45 | "ignoreRegExpList": ["Email", "Urls", "#[\\w-]*"] 46 | } 47 | -------------------------------------------------------------------------------- /src/infra/aws/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aws/aws-sdk-go/aws/session" 7 | "github.com/aws/aws-sdk-go/service/kms" 8 | "github.com/aws/aws-sdk-go/service/secretsmanager" 9 | "github.com/cenkalti/backoff/v4" 10 | awsinfra "github.com/consensys/quorum-key-manager/src/infra/aws" 11 | "github.com/consensys/quorum-key-manager/src/infra/log" 12 | ) 13 | 14 | type AWSClient struct { 15 | secretsClient *secretsmanager.SecretsManager 16 | kmsClient *kms.KMS 17 | cfg *Config 18 | backOff backoff.BackOff 19 | logger log.Logger 20 | } 21 | 22 | var _ awsinfra.Client = &AWSClient{} 23 | 24 | func New(cfg *Config, logger log.Logger) (*AWSClient, error) { 25 | sess, err := session.NewSession(cfg.ToAWSConfig()) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return &AWSClient{ 31 | kmsClient: kms.New(sess), 32 | secretsClient: secretsmanager.New(sess), 33 | // Max wait of 5 seconds to wait for KMS to transition the state of assets 34 | backOff: backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), 5), 35 | cfg: cfg, 36 | logger: logger, 37 | }, nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/http/proxy/config.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/http/request" 7 | "github.com/consensys/quorum-key-manager/pkg/http/response" 8 | "github.com/consensys/quorum-key-manager/pkg/http/transport" 9 | "github.com/consensys/quorum-key-manager/pkg/json" 10 | ) 11 | 12 | type Config struct { 13 | Transport *transport.Config `json:"transport,omitempty"` 14 | FlushInterval *json.Duration `json:"flushInterval,omitempty"` 15 | Request *request.ProxyConfig `json:"request,omitempty"` 16 | Response *response.ProxyConfig `json:"response,omitempty"` 17 | } 18 | 19 | func (cfg *Config) SetDefault() *Config { 20 | if cfg.Transport == nil { 21 | cfg.Transport = new(transport.Config) 22 | } 23 | 24 | cfg.Transport.SetDefault() 25 | 26 | if cfg.FlushInterval == nil { 27 | cfg.FlushInterval = &json.Duration{Duration: 100 * time.Millisecond} 28 | } 29 | 30 | if cfg.Request == nil { 31 | cfg.Request = new(request.ProxyConfig) 32 | } 33 | 34 | cfg.Request.SetDefault() 35 | 36 | if cfg.Response == nil { 37 | cfg.Response = new(response.ProxyConfig) 38 | } 39 | 40 | return cfg 41 | } 42 | -------------------------------------------------------------------------------- /src/aliases/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | ) 8 | 9 | //go:generate mockgen -source=database.go -destination=mock/database.go -package=mock 10 | 11 | type Registry interface { 12 | // Insert inserts a new alias registry 13 | Insert(ctx context.Context, registry *entities.AliasRegistry) (*entities.AliasRegistry, error) 14 | // FindOne gets an alias registry 15 | FindOne(ctx context.Context, name, tenant string) (*entities.AliasRegistry, error) 16 | // Delete deletes an alias registry 17 | Delete(ctx context.Context, name, tenant string) error 18 | } 19 | 20 | type Alias interface { 21 | // Insert inserts an alias in the registry 22 | Insert(ctx context.Context, alias *entities.Alias) (*entities.Alias, error) 23 | // FindOne gets an alias from the registry 24 | FindOne(ctx context.Context, registry, key, tenant string) (*entities.Alias, error) 25 | // Update updates an alias in the registry 26 | Update(ctx context.Context, alias *entities.Alias) (*entities.Alias, error) 27 | // Delete deletes an alias from the registry 28 | Delete(ctx context.Context, registry, key string) error 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Describe a problem 4 | title: "[] Bug short description" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ### Configuration 15 | 16 | Any requirements to be able to reproduce the issue. 17 | 18 | 28 | 29 | ### Steps to reproduce 30 | 31 | 1. Run '...' 32 | 2. Click on '....' 33 | 3. Open '....' 34 | 35 | **Actual result** 36 | 37 | Description of what actually happens 38 | 39 | **Expected result** 40 | 41 | A clear and concise description of what you expected to happen. 42 | 43 | ### Additional context 44 | 45 | **Machine Setup** 46 | - Binary version 47 | - OS (Linux, Windows...) 48 | 49 | **Logs** 50 | 51 | Obtained logs when the bug occur 52 | 53 | **Screenshots** 54 | 55 | Taken screenshot when the bug occur 56 | 57 | **Other** 58 | Add any other context about the problem here. 59 | -------------------------------------------------------------------------------- /src/nodes/node/proxy/session.go: -------------------------------------------------------------------------------- 1 | package proxynode 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/pkg/ethereum" 5 | "github.com/consensys/quorum-key-manager/pkg/jsonrpc" 6 | "github.com/consensys/quorum-key-manager/pkg/tessera" 7 | ) 8 | 9 | //go:generate mockgen -source=session.go -destination=session_mock.go -package=proxynode 10 | 11 | // Session holds client interface to a downstream node 12 | type Session interface { 13 | // ClientRPC returns a client to downstream JSON-RPC 14 | ClientRPC() jsonrpc.Client 15 | 16 | // EthCaller returns a caller to downstream Ethereum JSON-RPC 17 | EthCaller() ethereum.Caller 18 | 19 | // ClientPrivTxManager returns client to downstream private transaction manager 20 | ClientPrivTxManager() tessera.Client 21 | } 22 | 23 | type session struct { 24 | jsonrpcClient jsonrpc.Client 25 | ethCaller ethereum.Caller 26 | privTxMngrClient tessera.Client 27 | } 28 | 29 | func (s *session) ClientRPC() jsonrpc.Client { 30 | return s.jsonrpcClient 31 | } 32 | 33 | func (s *session) EthCaller() ethereum.Caller { 34 | return s.ethCaller 35 | } 36 | 37 | func (s *session) ClientPrivTxManager() tessera.Client { 38 | return s.privTxMngrClient 39 | } 40 | -------------------------------------------------------------------------------- /src/aliases/api/types/registry.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | ) 8 | 9 | type CreateRegistryRequest struct { 10 | AllowedTenants []string `json:"allowedTenants,omitempty" example:"tenant1,tenant2"` 11 | } 12 | 13 | type RegistryResponse struct { 14 | Name string `json:"name" example:"my-alias-registry"` 15 | Aliases []AliasResponse `json:"aliases"` 16 | AllowedTenants []string `json:"allowedTenants" example:"tenant1,tenant2"` 17 | CreatedAt time.Time `json:"createdAt" example:"2020-07-09T12:35:42.115395Z"` 18 | UpdatedAt time.Time `json:"updatedAt" example:"2020-07-09T12:35:42.115395Z"` 19 | } 20 | 21 | func NewRegistryResponse(registry *entities.AliasRegistry) *RegistryResponse { 22 | aliases := []AliasResponse{} 23 | for _, alias := range registry.Aliases { 24 | aliases = append(aliases, *NewAliasResponse(&alias)) 25 | } 26 | 27 | return &RegistryResponse{ 28 | Name: registry.Name, 29 | Aliases: aliases, 30 | AllowedTenants: registry.AllowedTenants, 31 | CreatedAt: registry.CreatedAt, 32 | UpdatedAt: registry.UpdatedAt, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /doc.quorum-key-manager-main/docs/Concepts/Nodes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nodes 3 | description: Nodes concept page 4 | sidebar_position: 3 5 | --- 6 | 7 | # Nodes 8 | 9 | A node is a Quorum Key Manager (QKM) component that interfaces with underlying node endpoints (such as Ethereum RPC nodes and Tessera nodes). 10 | 11 | You can configure a node using a [node manifest](../HowTo/Use-Manifest-File/Node.md), which includes the configuration to [connect to the JSON-RPC node proxy](../Tutorials/JsonRPCProxy.md). 12 | 13 | When connected to the JSON-RPC node proxy, QKM intercepts the following methods for performing remote transaction signing: 14 | 15 | - [`eea_sendTransaction`](https://entethalliance.github.io/client-spec/spec.html#sec-eea-sendTransaction) 16 | - [`eth_accounts`](https://ethereum.github.io/execution-apis/api-documentation/) 17 | - [`eth_sendTransaction`](https://ethereum.github.io/execution-apis/api-documentation/) ([the GoQuorum version](https://consensys.net/docs/goquorum/en/latest/reference/api-methods/#eth_sendtransaction) is also supported.) 18 | - [`eth_sign`](https://ethereum.github.io/execution-apis/api-documentation/) 19 | - [`eth_signTransaction`](https://ethereum.github.io/execution-apis/api-documentation/) 20 | -------------------------------------------------------------------------------- /src/aliases/service/registries/create.go: -------------------------------------------------------------------------------- 1 | package registries 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/service/authorizator" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | auth "github.com/consensys/quorum-key-manager/src/auth/entities" 10 | "github.com/consensys/quorum-key-manager/src/entities" 11 | ) 12 | 13 | func (s *Registries) Create(ctx context.Context, name string, allowedTenants []string, userInfo *auth.UserInfo) (*entities.AliasRegistry, error) { 14 | logger := s.logger.With("name", name) 15 | 16 | resolver := authorizator.New(s.roles.UserPermissions(ctx, userInfo), userInfo.Tenant, logger) 17 | err := resolver.CheckPermission(&auth.Operation{Action: auth.ActionWrite, Resource: auth.ResourceAlias}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | registry, err := s.db.Insert(ctx, &entities.AliasRegistry{Name: name, AllowedTenants: allowedTenants}) 23 | if err != nil { 24 | errMessage := "failed to create registry" 25 | logger.WithError(err).Error(errMessage) 26 | return nil, errors.FromError(err).SetMessage(errMessage) 27 | } 28 | 29 | logger.Info("alias registry created successfully") 30 | return registry, nil 31 | } 32 | -------------------------------------------------------------------------------- /src/aliases/app/service.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/consensys/quorum-key-manager/src/aliases/api/http" 5 | db "github.com/consensys/quorum-key-manager/src/aliases/database/postgres" 6 | "github.com/consensys/quorum-key-manager/src/aliases/service/aliases" 7 | "github.com/consensys/quorum-key-manager/src/aliases/service/registries" 8 | "github.com/consensys/quorum-key-manager/src/auth" 9 | "github.com/consensys/quorum-key-manager/src/infra/postgres" 10 | "github.com/gorilla/mux" 11 | 12 | "github.com/consensys/quorum-key-manager/src/infra/log" 13 | ) 14 | 15 | func RegisterService(router *mux.Router, logger log.Logger, postgresClient postgres.Client, authService auth.Roles) *aliases.Aliases { 16 | // Data layer 17 | aliasRepository := db.NewAlias(postgresClient) 18 | regisryRepository := db.NewRegistry(postgresClient) 19 | 20 | // Business layer 21 | aliasService := aliases.New(aliasRepository, regisryRepository, authService, logger) 22 | registryService := registries.New(regisryRepository, authService, logger) 23 | 24 | // Service layer 25 | http.NewRegistryHandler(registryService).Register(router) 26 | http.NewAliasHandler(aliasService).Register(router) 27 | 28 | return aliasService 29 | } 30 | -------------------------------------------------------------------------------- /pkg/json/encode.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | ) 7 | 8 | var ( 9 | _ driver.Valuer = jsonArray{} 10 | _ driver.Valuer = jsonMap{} 11 | ) 12 | 13 | func MarshalJSON(src interface{}) ([]byte, error) { 14 | return json.Marshal(recursiveToJSON(src)) 15 | } 16 | 17 | type jsonArray []interface{} 18 | 19 | func (a jsonArray) Value() (driver.Value, error) { return json.Marshal(a) } 20 | 21 | type jsonMap map[string]interface{} 22 | 23 | func (m jsonMap) Value() (driver.Value, error) { return json.Marshal(m) } 24 | 25 | // recursiveToJSON recursively convert all map[interface]interface{} to map[string]interface{} 26 | // as Go refuses to convert map[interface{}]interface{} to JSON because JSON only support string keys 27 | func recursiveToJSON(v interface{}) (r interface{}) { 28 | switch v := v.(type) { 29 | case []interface{}: 30 | for i, e := range v { 31 | v[i] = recursiveToJSON(e) 32 | } 33 | r = jsonArray(v) 34 | case map[interface{}]interface{}: 35 | newMap := make(map[string]interface{}, len(v)) 36 | for k, e := range v { 37 | newMap[k.(string)] = recursiveToJSON(e) 38 | } 39 | r = jsonMap(newMap) 40 | default: 41 | r = v 42 | } 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /src/aliases/database/models/registry.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | ) 8 | 9 | type Registry struct { 10 | tableName struct{} `pg:"registries"` // nolint:unused,structcheck // reason 11 | 12 | Name string `pg:",pk"` 13 | Aliases []Alias `pg:"rel:has-many"` 14 | AllowedTenants []string `pg:",array"` 15 | CreatedAt time.Time `pg:"default:now()"` 16 | UpdatedAt time.Time `pg:"default:now()"` 17 | } 18 | 19 | func NewRegistry(registry *entities.AliasRegistry) *Registry { 20 | return &Registry{ 21 | Name: registry.Name, 22 | AllowedTenants: registry.AllowedTenants, 23 | CreatedAt: registry.CreatedAt, 24 | UpdatedAt: registry.UpdatedAt, 25 | } 26 | } 27 | 28 | func (r *Registry) ToEntity() *entities.AliasRegistry { 29 | aliases := []entities.Alias{} 30 | for _, aliasModel := range r.Aliases { 31 | aliases = append(aliases, *aliasModel.ToEntity()) 32 | } 33 | 34 | return &entities.AliasRegistry{ 35 | Name: r.Name, 36 | AllowedTenants: r.AllowedTenants, 37 | Aliases: aliases, 38 | CreatedAt: r.CreatedAt, 39 | UpdatedAt: r.UpdatedAt, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/destroy.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | 8 | "github.com/consensys/quorum-key-manager/pkg/errors" 9 | "github.com/consensys/quorum-key-manager/src/stores/database" 10 | ) 11 | 12 | func (c Connector) Destroy(ctx context.Context, id string) error { 13 | logger := c.logger.With("id", id) 14 | logger.Debug("destroying key") 15 | 16 | err := c.authorizator.CheckPermission(&entities.Operation{Action: entities.ActionDestroy, Resource: entities.ResourceKey}) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | _, err = c.db.GetDeleted(ctx, id) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | err = c.db.RunInTransaction(ctx, func(dbtx database.Keys) error { 27 | err = dbtx.Purge(ctx, id) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | err = c.store.Destroy(ctx, id) 33 | if err != nil && !errors.IsNotSupportedError(err) { // If the underlying store does not support deleting, we only delete in DB 34 | return err 35 | } 36 | 37 | return nil 38 | }) 39 | 40 | if err != nil { 41 | return err 42 | } 43 | 44 | logger.Info("key was permanently deleted") 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /src/aliases/database/models/alias.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/consensys/quorum-key-manager/src/entities" 7 | ) 8 | 9 | type Alias struct { 10 | tableName struct{} `pg:"aliases"` // nolint:unused,structcheck // reason 11 | 12 | Key string `pg:",pk"` 13 | RegistryName string `pg:"on_delete:CASCADE"` 14 | Registry *Registry `pg:"rel:has-one"` 15 | Value AliasValue 16 | CreatedAt time.Time `pg:"default:now()"` 17 | UpdatedAt time.Time `pg:"default:now()"` 18 | } 19 | 20 | type AliasValue struct { 21 | Kind string 22 | Value interface{} 23 | } 24 | 25 | func NewAlias(alias *entities.Alias) *Alias { 26 | return &Alias{ 27 | RegistryName: alias.RegistryName, 28 | Key: alias.Key, 29 | Value: AliasValue{ 30 | Kind: alias.Kind, 31 | Value: alias.Value, 32 | }, 33 | CreatedAt: alias.CreatedAt, 34 | UpdatedAt: alias.UpdatedAt, 35 | } 36 | } 37 | 38 | func (a *Alias) ToEntity() *entities.Alias { 39 | return &entities.Alias{ 40 | Key: a.Key, 41 | RegistryName: a.RegistryName, 42 | Kind: a.Value.Kind, 43 | Value: a.Value.Value, 44 | CreatedAt: a.CreatedAt, 45 | UpdatedAt: a.UpdatedAt, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/ethereum/caller_eea.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/consensys/quorum-key-manager/pkg/jsonrpc" 7 | ethcommon "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | ) 10 | 11 | func init() { 12 | err := jsonrpc.ProvideCaller( 13 | eeaSrv, 14 | ) 15 | if err != nil { 16 | panic(err) 17 | } 18 | } 19 | 20 | var eeaSrv = new(eeaService) 21 | 22 | // eeaService is a jsonrpc.Caller which methods are meant to be automatically populated using json-rpc.ProvideCaller 23 | type eeaService struct { 24 | SendRawTransaction func(jsonrpc.Client) func(context.Context, hexutil.Bytes) (ethcommon.Hash, error) `namespace:"eea"` 25 | } 26 | 27 | //go:generate mockgen -source=caller_eea.go -destination=mock/caller_eea.go -package=mock 28 | 29 | // EEACaller is a JSON-RPC client to an Ethereum client using eea namespace 30 | type EEACaller interface { 31 | SendRawTransaction(context.Context, []byte) (ethcommon.Hash, error) 32 | } 33 | 34 | type eeaCaller struct { 35 | client jsonrpc.Client 36 | } 37 | 38 | func (c *eeaCaller) SendRawTransaction(ctx context.Context, raw []byte) (ethcommon.Hash, error) { 39 | return eeaSrv.SendRawTransaction(c.client)(ctx, raw) 40 | } 41 | -------------------------------------------------------------------------------- /src/stores/connectors/keys/get.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "context" 5 | 6 | authentities "github.com/consensys/quorum-key-manager/src/auth/entities" 7 | 8 | "github.com/consensys/quorum-key-manager/src/stores/entities" 9 | ) 10 | 11 | func (c Connector) Get(ctx context.Context, id string) (*entities.Key, error) { 12 | logger := c.logger.With("id", id) 13 | 14 | err := c.authorizator.CheckPermission(&authentities.Operation{Action: authentities.ActionRead, Resource: authentities.ResourceKey}) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | key, err := c.db.Get(ctx, id) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | logger.Debug("key retrieved successfully") 25 | return key, nil 26 | } 27 | 28 | func (c Connector) GetDeleted(ctx context.Context, id string) (*entities.Key, error) { 29 | logger := c.logger.With("id", id) 30 | 31 | err := c.authorizator.CheckPermission(&authentities.Operation{Action: authentities.ActionRead, Resource: authentities.ResourceKey}) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | key, err := c.db.GetDeleted(ctx, id) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | logger.Debug("deleted key retrieved successfully") 42 | return key, nil 43 | } 44 | --------------------------------------------------------------------------------