Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support gcp secret manager #11436

Merged
merged 28 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0e5538c
feat: support gcp secret manager
HuanXin-Chen Jul 25, 2024
8eabd06
docs(secret): integrating gcp usage introduction
HuanXin-Chen Jul 25, 2024
16a815b
style(gcp): fix some style about gcp secret
HuanXin-Chen Jul 25, 2024
95ee16a
style(gcp): fix the success.json style
HuanXin-Chen Jul 25, 2024
1205deb
style(gcp): fix the secret docs
HuanXin-Chen Jul 26, 2024
11acef1
fix(secret): fix some gcp logic
HuanXin-Chen Jul 27, 2024
a213043
fix(secret): gcp code and test
HuanXin-Chen Jul 29, 2024
4ea2590
feat(secret): support ther gcp string value
HuanXin-Chen Aug 3, 2024
6fd6389
feat(secret): return decode err
HuanXin-Chen Aug 4, 2024
3e780e7
cli(common): add the expact
HuanXin-Chen Aug 8, 2024
113a96c
cli(common): remove the expact
HuanXin-Chen Aug 8, 2024
1d017a7
feat(secret): put the oauth into utils
HuanXin-Chen Aug 9, 2024
eeb5712
merge(): remote-tracking branch 'upstream/master' into feat-gcp-secret
HuanXin-Chen Aug 9, 2024
9464092
fix(secret): fix the test1
HuanXin-Chen Aug 13, 2024
c8ced2f
feat(secret): using serverless to test and fix some style
HuanXin-Chen Aug 25, 2024
7174810
fix(secret): resolved the docs conflicts
HuanXin-Chen Sep 1, 2024
626654f
style(secret): _M.get and test case
HuanXin-Chen Sep 1, 2024
749aa95
Merge branch 'master' into feat-gcp-secret
HuanXin-Chen Sep 1, 2024
873bd12
fix(secret): just code style
HuanXin-Chen Sep 5, 2024
23bb722
fix(secret): scope should not be used in the plural
HuanXin-Chen Sep 6, 2024
02a1910
docs(secret): fix the example
HuanXin-Chen Sep 6, 2024
d7e5676
Merge branch 'feat-gcp-secret' of https://github.com/HuanXin-Chen/api…
HuanXin-Chen Sep 6, 2024
5fdadbd
style(secret): fix the lint problem
HuanXin-Chen Sep 6, 2024
0f53faa
fix(utils): remove the default entries
HuanXin-Chen Sep 6, 2024
cdcd661
style(secret): remove some tips
HuanXin-Chen Sep 11, 2024
a4cc432
style(secret): code style
HuanXin-Chen Sep 12, 2024
49c6a6f
Merge remote-tracking branch 'upstream/master' into feat-gcp-secret
HuanXin-Chen Sep 17, 2024
ad3af95
style(gcp): remove sanity
HuanXin-Chen Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions apisix/secret/gcp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
--
-- 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.
--

--- GCP Tools.
local core = require("apisix.core")
local http = require("resty.http")
local google_oauth = require("apisix.utils.google-cloud-oauth")

local sub = core.string.sub
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
local find = core.string.find
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
local decode_base64 = ngx.decode_base64
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved

local lrucache = core.lrucache.new({ttl = 300, count= 8})
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved

local schema = {
type = "object",
properties = {
auth_config = {
type = "object",
properties = {
client_email = { type = "string" },
private_key = { type = "string" },
project_id = { type = "string" },
token_uri = {
type = "string",
default = "https://oauth2.googleapis.com/token"
},
scopes = {
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
type = "array",
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
default = {
"https://www.googleapis.com/auth/cloud-platform"
}
},
entries_uri = {
type = "string",
default = "https://secretmanager.googleapis.com/v1"
},
},
required = { "client_email", "private_key", "project_id"}
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
},
ssl_verify = {
type = "boolean",
default = true
},
auth_file = { type = "string" },
},
oneOf = {
{ required = { "auth_config" } },
{ required = { "auth_file" } },
},
encrypt_fields = {"auth_config.private_key"},
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
}

local _M = {
schema = schema
}

local function fetch_oauth_conf(conf)
if conf.auth_config then
return conf.auth_config
end

local file_content, err = core.io.get_file(conf.auth_file)
if not file_content then
return nil, "failed to read configuration, file: " .. conf.auth_file .. ", err: " .. err
end

local config_tab, err = core.json.decode(file_content)
if not config_tab then
return nil, "config parse failure, data: " .. file_content .. ", err: " .. err
end

local config = {
auth_config = {
client_email = config_tab.client_email,
private_key = config_tab.private_key,
project_id = config_tab.project_id
}
}

local ok, err = core.schema.check(schema, config)
if not ok then
return nil, "config parse failure, file: " .. conf.auth_file .. ", err: " .. err
end

return config_tab
end

local function create_oauth_object(auth_config, ssl_verify)
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
return google_oauth:new(auth_config, ssl_verify)
end

local function get_secret(oauth, secrets_id)
local http_new = http.new()
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved

local access_token = oauth:generate_access_token()
if not access_token then
return nil, "failed to get google oauth token"
end

local entries_uri = oauth.entries_uri .. "/projects/" .. oauth.project_id
.. "/secrets/" .. secrets_id .. "/versions/latest:access"

local res, err = http_new:request_uri(entries_uri, {
ssl_verify = oauth.ssl_verify,
method = "GET",
headers = {
["Content-Type"] = "application/json",
["Authorization"] = (oauth.access_token_type or "Bearer") .. " " .. access_token,
},
})

if not res then
return nil, err
end

if res.status ~= 200 then
return nil, res.body
end

local body, err = core.json.decode(res.body)
if not body then
return nil, "failed to parse response data, " .. err
end

local payload = body.payload
if not payload then
return nil, "invalid payload"
end

return decode_base64(payload.data)
end

membphis marked this conversation as resolved.
Show resolved Hide resolved
local function make_request_to_gcp(conf, secrets_id)
local auth_config, err = fetch_oauth_conf(conf)
if not auth_config then
return nil, err
end

local lru_key = auth_config.client_email .. "#" .. auth_config.project_id

local oauth, err = lrucache(lru_key, "gcp", create_oauth_object, auth_config, conf.ssl_verify)
if not oauth then
return nil, "failed to create oauth object, " .. err
end

local secret, err = get_secret(oauth, secrets_id)
if not secret then
return nil, err
end

return secret
end

function _M.get(conf, key)
core.log.info("fetching data from gcp for key: ", key)

local idx = find(key, '/')

local main_key = idx and sub(key, 1, idx - 1) or key
if main_key == "" then
return nil, "can't find main key, key: " .. key
end

local sub_key = idx and sub(key, idx + 1) or nil

core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or "")

local res, err = make_request_to_gcp(conf, main_key)
if not res then
return nil, "failed to retrtive data from gcp secret manager: " .. err
end
membphis marked this conversation as resolved.
Show resolved Hide resolved

if not sub_key then
return res
end

local data, err = core.json.decode(res)
if not data then
return nil, "failed to decode result, res: " .. res .. ", err: " .. err
HuanXin-Chen marked this conversation as resolved.
Show resolved Hide resolved
end

return data[sub_key]
end


membphis marked this conversation as resolved.
Show resolved Hide resolved

return _M
137 changes: 137 additions & 0 deletions apisix/utils/google-cloud-oauth.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
--
-- 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.
--

local core = require("apisix.core")
local type = type
local setmetatable = setmetatable

local ngx_update_time = ngx.update_time
local ngx_time = ngx.time
local ngx_encode_args = ngx.encode_args

local http = require("resty.http")
local jwt = require("resty.jwt")


local function get_timestamp()
ngx_update_time()
return ngx_time()
end


local _M = {}


function _M:generate_access_token()
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
if not self.access_token or get_timestamp() > self.access_token_expire_time - 60 then
self:refresh_access_token()
end
return self.access_token
end


function _M:refresh_access_token()
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
local http_new = http.new()
local res, err = http_new:request_uri(self.token_uri, {
ssl_verify = self.ssl_verify,
method = "POST",
body = ngx_encode_args({
grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion = self:generate_jwt_token()
}),
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
})

if not res then
core.log.error("failed to refresh google oauth access token, ", err)
return
end

if res.status ~= 200 then
core.log.error("failed to refresh google oauth access token: ", res.body)
return
end

res, err = core.json.decode(res.body)
if not res then
core.log.error("failed to parse google oauth response data: ", err)
return
end

self.access_token = res.access_token
self.access_token_type = res.token_type
self.access_token_expire_time = get_timestamp() + res.expires_in
end


function _M:generate_jwt_token()
local payload = core.json.encode({
iss = self.client_email,
aud = self.token_uri,
scope = self.scope,
iat = get_timestamp(),
exp = get_timestamp() + (60 * 60)
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
})

local jwt_token = jwt:sign(self.private_key, {
header = { alg = "RS256", typ = "JWT" },
payload = payload,
})

return jwt_token
end


function _M:new(config, ssl_verify)
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
local oauth = {
client_email = config.client_email,
private_key = config.private_key,
project_id = config.project_id,
token_uri = config.token_uri or "https://oauth2.googleapis.com/token",
auth_uri = config.auth_uri or "https://accounts.google.com/o/oauth2/auth",
entries_uri = config.entries_uri or "https://logging.googleapis.com/v2/entries:write",
access_token = nil,
access_token_type = nil,
access_token_expire_time = 0,
}

oauth.ssl_verify = ssl_verify

if config.scopes then
if type(config.scopes) == "string" then
oauth.scope = config.scopes
end

if type(config.scopes) == "table" then
oauth.scope = core.table.concat(config.scopes, " ")
end
else
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
-- https://developers.google.com/identity/protocols/oauth2/scopes#logging
oauth.scope = core.table.concat({ "https://www.googleapis.com/auth/logging.read",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/logging.admin",
"https://www.googleapis.com/auth/cloud-platform" }, " ")
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
end

setmetatable(oauth, { __index = self })
return oauth
end


return _M
Loading
Loading