diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9031b2dadcf6..0626767ad9ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: test_dir: - t/plugin/[a-k]* - t/plugin/[l-z]* - - t/admin t/cli t/config-center-yaml t/control t/core t/debug t/deployment t/discovery t/error_page t/misc + - t/admin t/cli t/config-center-yaml t/control t/core t/debug t/deployment t/discovery t/error_page t/kms t/misc - t/node t/pubsub t/router t/script t/stream-node t/utils t/wasm t/xds-library t/xrpc runs-on: ${{ matrix.platform }} @@ -86,6 +86,7 @@ jobs: - name: Linux launch common services run: | make ci-env-up project_compose_ci=ci/pod/docker-compose.common.yml + sudo ./ci/init-common-test-service.sh - name: Create tarball if: ${{ startsWith(github.ref, 'refs/heads/release/') }} diff --git a/.github/workflows/centos7-ci.yml b/.github/workflows/centos7-ci.yml index ba76759bfc07..d5b8facc42e5 100644 --- a/.github/workflows/centos7-ci.yml +++ b/.github/workflows/centos7-ci.yml @@ -30,7 +30,7 @@ jobs: test_dir: - t/plugin/[a-k]* - t/plugin/[l-z]* - - t/admin t/cli t/config-center-yaml t/control t/core t/debug t/deployment t/discovery t/error_page t/misc + - t/admin t/cli t/config-center-yaml t/control t/core t/debug t/deployment t/discovery t/error_page t/kms t/misc - t/node t/pubsub t/router t/script t/stream-node t/utils t/wasm t/xds-library steps: @@ -72,6 +72,7 @@ jobs: - name: Linux launch common services run: | make ci-env-up project_compose_ci=ci/pod/docker-compose.common.yml + sudo ./ci/init-common-test-service.sh - name: Build rpm package if: ${{ startsWith(github.ref, 'refs/heads/release/') }} diff --git a/Makefile b/Makefile index 49468dc57e41..f8668c8d4e16 100644 --- a/Makefile +++ b/Makefile @@ -345,6 +345,9 @@ install: runtime $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/pubsub $(ENV_INSTALL) apisix/pubsub/*.lua $(ENV_INST_LUADIR)/apisix/pubsub/ + $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/kms + $(ENV_INSTALL) apisix/kms/*.lua $(ENV_INST_LUADIR)/apisix/kms/ + $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/zipkin $(ENV_INSTALL) apisix/plugins/zipkin/*.lua $(ENV_INST_LUADIR)/apisix/plugins/zipkin/ diff --git a/apisix/kms/vault.lua b/apisix/kms/vault.lua new file mode 100644 index 000000000000..1343002fcb20 --- /dev/null +++ b/apisix/kms/vault.lua @@ -0,0 +1,91 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +--- Vault Tools. +-- Vault is an identity-based secrets and encryption management system. + +local core = require("apisix.core") +local http = require("resty.http") + +local norm_path = require("pl.path").normpath + +local sub = core.string.sub +local rfind_char = core.string.rfind_char + +local _M = {} + + +local function make_request_to_vault(conf, method, key, data) + local httpc = http.new() + -- config timeout or default to 5000 ms + httpc:set_timeout((conf.timeout or 5)*1000) + + local req_addr = conf.uri .. norm_path("/v1/" + .. conf.prefix .. "/" .. key) + + local res, err = httpc:request_uri(req_addr, { + method = method, + headers = { + ["X-Vault-Token"] = conf.token + }, + body = core.json.encode(data or {}, true) + }) + + if not res then + return nil, err + end + + return res.body +end + +-- key is the vault kv engine path +local function get(conf, key) + core.log.info("fetching data from vault for key: ", key) + + local idx = rfind_char(key, '/') + if not idx then + return nil, "error key format, key: " .. key + end + + local main_key = sub(key, 1, idx - 1) + if main_key == "" then + return nil, "can't find main key, key: " .. key + end + local sub_key = sub(key, idx + 1) + if sub_key == "" then + return nil, "can't find sub key, key: " .. key + end + + core.log.info("main: ", main_key, " sub: ", sub_key) + + local res, err = make_request_to_vault(conf, "GET", main_key) + if not res then + return nil, "failed to retrtive data from vault kv engine: " .. err + end + + local ret = core.json.decode(res) + if not ret or not ret.data then + return nil, "failed to decode result, res: " .. res + end + + return ret.data[sub_key] +end + +_M.get = get + + +return _M diff --git a/ci/init-common-test-service.sh b/ci/init-common-test-service.sh new file mode 100755 index 000000000000..7a54cd49a2b6 --- /dev/null +++ b/ci/init-common-test-service.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# prepare vault kv engine +sleep 3s +docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault secrets enable -path=kv -version=1 kv" diff --git a/ci/init-plugin-test-service.sh b/ci/init-plugin-test-service.sh index 1f973ce36f47..fd090441e3fb 100755 --- a/ci/init-plugin-test-service.sh +++ b/ci/init-plugin-test-service.sh @@ -39,9 +39,6 @@ docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic -n rocketmq_namesrv:9876 -t test3 -c DefaultCluster docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic -n rocketmq_namesrv:9876 -t test4 -c DefaultCluster -# prepare vault kv engine -docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault secrets enable -path=kv -version=1 kv" - # wait for keycloak ready bash -c 'while true; do curl -s localhost:8080 &>/dev/null; ret=$?; [[ $ret -eq 0 ]] && break; sleep 3; done' docker cp ci/kcadm_configure_cas.sh apisix_keycloak_new:/tmp/ diff --git a/ci/pod/docker-compose.common.yml b/ci/pod/docker-compose.common.yml index 9e0394a48bd2..222dc1e1eed4 100644 --- a/ci/pod/docker-compose.common.yml +++ b/ci/pod/docker-compose.common.yml @@ -87,3 +87,18 @@ services: - "5004:6382" - "5005:6383" - "5006:6384" + + + ## HashiCorp Vault + vault: + image: vault:1.9.0 + container_name: vault + restart: unless-stopped + ports: + - "8200:8200" + cap_add: + - IPC_LOCK + environment: + VAULT_DEV_ROOT_TOKEN_ID: root + VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 + command: [ "vault", "server", "-dev" ] diff --git a/ci/pod/docker-compose.plugin.yml b/ci/pod/docker-compose.plugin.yml index 6715c7a33433..4b8c2a4c16b4 100644 --- a/ci/pod/docker-compose.plugin.yml +++ b/ci/pod/docker-compose.plugin.yml @@ -130,22 +130,6 @@ services: networks: skywalk_net: - ## HashiCorp Vault - vault: - image: vault:1.9.0 - container_name: vault - restart: unless-stopped - ports: - - "8200:8200" - cap_add: - - IPC_LOCK - environment: - VAULT_DEV_ROOT_TOKEN_ID: root - VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 - command: [ "vault", "server", "-dev" ] - networks: - vault_net: - ## OpenLDAP openldap: @@ -284,5 +268,4 @@ networks: kafka_net: skywalk_net: rocketmq_net: - vault_net: opa_net: diff --git a/t/kms/vault.t b/t/kms/vault.t new file mode 100644 index 000000000000..5e0cdb54aceb --- /dev/null +++ b/t/kms/vault.t @@ -0,0 +1,158 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + +=== TEST 1: check key: error format +--- config + location /t { + content_by_lua_block { + local vault = require("apisix.kms.vault") + local conf = { + prefix = "/kv/prefix", + token = "root", + uri = "http://127.0.0.1:2800" + } + local data, err = vault.get(conf, "apisix") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +error key format, key: apisix + + + +=== TEST 2: check key: no main key +--- config + location /t { + content_by_lua_block { + local vault = require("apisix.kms.vault") + local conf = { + prefix = "/kv/prefix", + token = "root", + uri = "http://127.0.0.1:2800" + } + local data, err = vault.get(conf, "/apisix") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +can't find main key, key: /apisix + + + +=== TEST 3: check key: no sub key +--- config + location /t { + content_by_lua_block { + local vault = require("apisix.kms.vault") + local conf = { + prefix = "/kv/prefix", + token = "root", + uri = "http://127.0.0.1:2800" + } + local data, err = vault.get(conf, "apisix/") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +can't find sub key, key: apisix/ + + + +=== TEST 4: error vault uri +--- config + location /t { + content_by_lua_block { + local vault = require("apisix.kms.vault") + local conf = { + prefix = "/kv/prefix", + token = "root", + uri = "http://127.0.0.2:2800" + } + local data, err = vault.get(conf, "/apisix/sub") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +failed to retrtive data from vault kv engine: connection refused +--- timeout: 6 + + + +=== TEST 5: store secret into vault +--- exec +VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/apisix-key/jack key=value +--- response_body +Success! Data written to: kv/apisix/apisix-key/jack + + + +=== TEST 6: get value from vault +--- config + location /t { + content_by_lua_block { + local vault = require("apisix.kms.vault") + local conf = { + prefix = "kv/apisix", + token = "root", + uri = "http://127.0.0.1:8200" + } + local value, err = vault.get(conf, "/apisix-key/jack/key") + if err then + return ngx.say(err) + end + + ngx.say("value") + } + } +--- request +GET /t +--- response_body +value