Skip to content

Commit 2533c82

Browse files
authored
golang: make secret manager available to golang http plugin (#38703)
Additional Description: - add generic secrets to golang.proto - add SecretReader in C++ FilterConfig - add SecretManager interface in Go, implemented using the config pointer as secrets are not request scoped - add cAPI to access the secret from C++ Fixes #38702 Signed-off-by: François JACQUES <[email protected]>
1 parent 8fedd32 commit 2533c82

File tree

19 files changed

+435
-2
lines changed

19 files changed

+435
-2
lines changed

api/contrib/envoy/extensions/filters/http/golang/v3alpha/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2
66

77
api_proto_package(
88
deps = [
9+
"//envoy/extensions/transport_sockets/tls/v3:pkg",
910
"@com_github_cncf_xds//udpa/annotations:pkg",
1011
"@com_github_cncf_xds//xds/annotations/v3:pkg",
1112
],

api/contrib/envoy/extensions/filters/http/golang/v3alpha/golang.proto

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ syntax = "proto3";
22

33
package envoy.extensions.filters.http.golang.v3alpha;
44

5+
import "envoy/extensions/transport_sockets/tls/v3/secret.proto";
6+
57
import "google/protobuf/any.proto";
68

79
import "xds/annotations/v3/status.proto";
@@ -21,7 +23,7 @@ option (xds.annotations.v3.file_status).work_in_progress = true;
2123
// For an overview of the Golang HTTP filter please see the :ref:`configuration reference documentation <config_http_filters_golang>`.
2224
// [#extension: envoy.filters.http.golang]
2325

24-
// [#next-free-field: 6]
26+
// [#next-free-field: 7]
2527
message Config {
2628
// The meanings are as follows:
2729
//
@@ -74,6 +76,13 @@ message Config {
7476
//
7577
// [#not-implemented-hide:]
7678
MergePolicy merge_policy = 5 [(validate.rules).enum = {defined_only: true}];
79+
80+
// Generic secret list available to the plugin.
81+
// Looks into SDS or static bootstrap configuration.
82+
//
83+
// See :repo:`StreamFilter API <contrib/golang/common/go/api/filter.go>`
84+
// for more information about how to access secrets from Go.
85+
repeated transport_sockets.tls.v3.SdsSecretConfig generic_secrets = 6;
7786
}
7887

7988
message RouterPlugin {

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ new_features:
332332
change: |
333333
Added an :ref:`io_uring <envoy_v3_api_field_extensions.network.socket_interface.v3.DefaultSocketInterface.io_uring_options>` option in
334334
default socket interface to support io_uring.
335+
- area: golang
336+
change: |
337+
Expose generic secrets to :ref:`lua <envoy_v3_api_msg_extensions.filters.http.golang.v3alpha.Config>`
338+
Add SecretManager interface in the go envoy sdk.
335339
- area: dns
336340
change: |
337341
Update c-ares to version 1.34.4. This upgrade exposes ``ares_reinit()`` which allows the reinitialization of c-ares channels,

contrib/golang/common/go/api/api.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key_data, int ke
118118
uint64_t* value_data, int* value_len);
119119
CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_len,
120120
uint64_t* value_data, int* value_len, int* rc);
121+
CAPIStatus envoyGoFilterHttpGetStringSecret(void* r, void* key_data, int key_len,
122+
uint64_t* value_data, int* value_len);
121123

122124
/* These APIs have nothing to do with request */
123125
void envoyGoFilterLog(uint32_t level, void* message_data, int message_len);

contrib/golang/common/go/api/capi.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type HttpCAPI interface {
5959
HttpGetStringProperty(r unsafe.Pointer, key string) (string, error)
6060

6161
HttpFinalize(r unsafe.Pointer, reason int)
62+
HttpGetStringSecret(c unsafe.Pointer, key string) (string, bool)
6263

6364
/* These APIs are related to config, use the pointer of config. */
6465
HttpDefineMetric(c unsafe.Pointer, metricType MetricType, name string) uint32

contrib/golang/common/go/api/filter.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ type StreamFilterCallbacks interface {
186186
// * ErrValueNotFound
187187
GetProperty(key string) (string, error)
188188
// TODO add more for filter callbacks
189+
190+
// Get secret manager.
191+
// Secrets should be defined in the plugin configuration.
192+
// It is safe to use this secret manager from any goroutine.
193+
SecretManager() SecretManager
189194
}
190195

191196
// FilterProcessCallbacks is the interface for filter to process request/response in decode/encode phase.
@@ -314,6 +319,12 @@ type FilterState interface {
314319
GetString(key string) string
315320
}
316321

322+
type SecretManager interface {
323+
// Get generic secret from secret manager.
324+
// bool is false on missing secret
325+
GetGenericSecret(name string) (string, bool)
326+
}
327+
317328
type MetricType uint32
318329

319330
const (

contrib/golang/filters/http/source/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ envoy_cc_library(
3939
"//source/common/http/http1:codec_lib",
4040
"//source/common/protobuf:utility_lib",
4141
"//source/common/router:string_accessor_lib",
42+
"//source/common/secret:secret_provider_impl_lib",
4243
"//source/extensions/filters/common/expr:cel_state_lib",
4344
"//source/extensions/filters/common/expr:evaluator_lib",
45+
"@com_google_absl//absl/container:flat_hash_map",
46+
"@com_google_absl//absl/types:optional",
4447
"@com_google_cel_cpp//eval/public:activation",
4548
"@com_google_cel_cpp//eval/public:builtin_func_registrar",
4649
"@com_google_cel_cpp//eval/public:cel_expr_builder_factory",
@@ -91,6 +94,7 @@ envoy_cc_library(
9194
"//source/common/http:utility_lib",
9295
"//source/common/http/http1:codec_lib",
9396
"//source/common/protobuf:utility_lib",
97+
"//source/common/secret:secret_provider_impl_lib",
9498
"//source/extensions/filters/common/expr:cel_state_lib",
9599
"//source/extensions/filters/common/expr:evaluator_lib",
96100
"@envoy_api//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg_cc_proto",

contrib/golang/filters/http/source/cgo.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,15 @@ CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_l
361361
});
362362
}
363363

364+
CAPIStatus envoyGoFilterHttpGetStringSecret(void* r, void* key_data, int key_len,
365+
uint64_t* value_data, int* value_len) {
366+
return envoyGoFilterHandlerWrapper(
367+
r, [key_data, key_len, value_data, value_len](std::shared_ptr<Filter>& filter) -> CAPIStatus {
368+
auto key_str = stringViewFromGoPointer(key_data, key_len);
369+
return filter->getSecret(key_str, value_data, value_len);
370+
});
371+
}
372+
364373
CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name_data,
365374
int name_len, uint32_t* metric_id) {
366375
return envoyGoConfigHandlerWrapper(

contrib/golang/filters/http/source/go/pkg/http/capi_impl.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,26 @@ func (c *httpCApiImpl) HttpGetStringProperty(r unsafe.Pointer, key string) (stri
453453
return "", capiStatusToErr(res)
454454
}
455455

456+
func (c *httpCApiImpl) HttpGetStringSecret(r unsafe.Pointer, key string) (string, bool) {
457+
req := (*httpRequest)(r)
458+
var valueData C.uint64_t
459+
var valueLen C.int
460+
req.mutex.Lock()
461+
defer req.mutex.Unlock()
462+
req.markMayWaitingCallback()
463+
res := C.envoyGoFilterHttpGetStringSecret(unsafe.Pointer(req.req), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen)
464+
if res == C.CAPIYield {
465+
req.checkOrWaitCallback()
466+
} else {
467+
req.markNoWaitingCallback()
468+
handleCApiStatus(res)
469+
}
470+
if valueLen == -1 {
471+
return "", false
472+
}
473+
return strings.Clone(unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen))), true
474+
}
475+
456476
func (c *httpCApiImpl) HttpLog(level api.LogType, message string) {
457477
C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message)))
458478
}

contrib/golang/filters/http/source/go/pkg/http/filter.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,14 @@ func (r *httpRequest) Finalize(reason int) {
284284
cAPI.HttpFinalize(unsafe.Pointer(r), reason)
285285
}
286286

287+
func (r *httpRequest) SecretManager() api.SecretManager {
288+
return r
289+
}
290+
291+
func (r *httpRequest) GetGenericSecret(name string) (string, bool) {
292+
return cAPI.HttpGetStringSecret(unsafe.Pointer(r), name)
293+
}
294+
287295
type streamInfo struct {
288296
request *httpRequest
289297
}

contrib/golang/filters/http/source/golang_filter.cc

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,47 @@ void Filter::deferredDeleteRequest(HttpRequestInternal* req) {
14571457
}
14581458
}
14591459

1460+
CAPIStatus Filter::getSecret(const absl::string_view name, uint64_t* value_data, int* value_len) {
1461+
// lock until this function return since it may running in a Go thread.
1462+
Thread::LockGuard lock(mutex_);
1463+
if (has_destroyed_) {
1464+
ENVOY_LOG(debug, "golang filter has been destroyed");
1465+
return CAPIStatus::CAPIFilterIsDestroy;
1466+
}
1467+
1468+
auto populateFilter = [](Filter& f, const absl::string_view name, uint64_t* value_data,
1469+
int* value_len) {
1470+
auto maybe_secret = f.config_->getSecretReader().secret(std::string(name));
1471+
if (!maybe_secret.has_value()) {
1472+
*value_len = -1;
1473+
} else {
1474+
f.req_->strValue = maybe_secret.value();
1475+
*value_data = reinterpret_cast<uint64_t>(f.req_->strValue.data());
1476+
*value_len = f.req_->strValue.length();
1477+
}
1478+
};
1479+
ENVOY_LOG(debug, "golang filter getSecret '{}'", name);
1480+
if (isThreadSafe()) {
1481+
ENVOY_LOG(debug, "golang filter getSecret replying directly");
1482+
populateFilter(*this, name, value_data, value_len);
1483+
return CAPIStatus::CAPIOK;
1484+
} else {
1485+
ENVOY_LOG(debug, "golang filter getSecret posting request to dispatcher");
1486+
auto weak_ptr = weak_from_this();
1487+
getDispatcher().post(
1488+
[this, populateFilter, weak_ptr, name = std::string(name), value_data, value_len] {
1489+
ENVOY_LOG(debug, "golang filter getSecret request in worker thread");
1490+
if (!weak_ptr.expired() && !hasDestroyed()) {
1491+
populateFilter(*this, name, value_data, value_len);
1492+
dynamic_lib_->envoyGoRequestSemaDec(req_);
1493+
} else {
1494+
ENVOY_LOG(info, "golang filter has gone or destroyed in getSecret");
1495+
}
1496+
});
1497+
return CAPIStatus::CAPIYield;
1498+
}
1499+
}
1500+
14601501
/* ConfigId */
14611502

14621503
uint64_t Filter::getMergedConfigId() {
@@ -1484,7 +1525,8 @@ FilterConfig::FilterConfig(
14841525
so_path_(proto_config.library_path()), plugin_config_(proto_config.plugin_config()),
14851526
concurrency_(context.serverFactoryContext().options().concurrency()),
14861527
stats_(GolangFilterStats::generateStats(stats_prefix, context.scope())), dso_lib_(dso_lib),
1487-
metric_store_(std::make_shared<MetricStore>(context.scope().createScope(""))){};
1528+
metric_store_(std::make_shared<MetricStore>(context.scope().createScope(""))),
1529+
secret_reader_(std::make_shared<SecretReader>(proto_config, context)){};
14881530

14891531
void FilterConfig::newGoPluginConfig() {
14901532
ENVOY_LOG(debug, "initializing golang filter config");
@@ -1737,6 +1779,59 @@ uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) {
17371779
return merged_config_id_;
17381780
};
17391781

1782+
/* Secret manager */
1783+
1784+
namespace {
1785+
Secret::GenericSecretConfigProviderSharedPtr
1786+
secretsProvider(const envoy::extensions::transport_sockets::tls::v3::SdsSecretConfig& config,
1787+
Secret::SecretManager& secret_manager,
1788+
Server::Configuration::TransportSocketFactoryContext& transport_socket_factory,
1789+
Init::Manager& init_manager) {
1790+
if (config.has_sds_config()) {
1791+
return secret_manager.findOrCreateGenericSecretProvider(config.sds_config(), config.name(),
1792+
transport_socket_factory, init_manager);
1793+
} else {
1794+
return secret_manager.findStaticGenericSecretProvider(config.name());
1795+
}
1796+
}
1797+
} // namespace
1798+
1799+
SecretReader::SecretReader(
1800+
const envoy::extensions::filters::http::golang::v3alpha::Config& proto_config,
1801+
Server::Configuration::FactoryContext& context) {
1802+
if (proto_config.generic_secrets_size() > 0) {
1803+
auto& secret_manager =
1804+
context.serverFactoryContext().clusterManager().clusterManagerFactory().secretManager();
1805+
auto& transport_socket_factory = context.getTransportSocketFactoryContext();
1806+
auto& init_manager = context.initManager();
1807+
auto& tls = context.serverFactoryContext().threadLocal();
1808+
auto& api = context.serverFactoryContext().api();
1809+
for (auto& secret : proto_config.generic_secrets()) {
1810+
// Check here to avoid creating unecessary sds provider
1811+
if (secrets_.contains(secret.name())) {
1812+
throw EnvoyException(absl::StrCat("duplicate secret ", secret.name()));
1813+
}
1814+
auto secret_provider =
1815+
secretsProvider(secret, secret_manager, transport_socket_factory, init_manager);
1816+
if (secret_provider == nullptr) {
1817+
throw EnvoyException(absl::StrCat("no secret provider found for ", secret.name()));
1818+
}
1819+
auto tlsp = THROW_OR_RETURN_VALUE(
1820+
Secret::ThreadLocalGenericSecretProvider::create(std::move(secret_provider), tls, api),
1821+
std::unique_ptr<Secret::ThreadLocalGenericSecretProvider>);
1822+
secrets_.emplace(secret.name(), std::move(tlsp));
1823+
}
1824+
}
1825+
}
1826+
1827+
absl::optional<const std::string> SecretReader::secret(const std::string& name) const {
1828+
auto secret = secrets_.find(name);
1829+
if (secret != secrets_.end()) {
1830+
return secret->second->secret();
1831+
}
1832+
return absl::nullopt;
1833+
}
1834+
17401835
} // namespace Golang
17411836
} // namespace HttpFilters
17421837
} // namespace Extensions

contrib/golang/filters/http/source/golang_filter.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
#include "source/common/common/thread.h"
1212
#include "source/common/grpc/context_impl.h"
1313
#include "source/common/http/utility.h"
14+
#include "source/common/secret/secret_provider_impl.h"
1415
#include "source/extensions/filters/common/expr/evaluator.h"
1516

17+
#include "absl/container/flat_hash_map.h"
18+
#include "absl/types/optional.h"
1619
#include "contrib/envoy/extensions/filters/http/golang/v3alpha/golang.pb.h"
1720
#include "contrib/golang/filters/http/source/processor_state.h"
1821
#include "contrib/golang/filters/http/source/stats.h"
@@ -56,6 +59,16 @@ using MetricStoreSharedPtr = std::shared_ptr<MetricStore>;
5659

5760
struct httpConfigInternal;
5861

62+
class SecretReader {
63+
public:
64+
SecretReader(const envoy::extensions::filters::http::golang::v3alpha::Config& proto_config,
65+
Server::Configuration::FactoryContext& context);
66+
absl::optional<const std::string> secret(const std::string& name) const;
67+
68+
private:
69+
absl::flat_hash_map<std::string, std::unique_ptr<Secret::ThreadLocalGenericSecretProvider>>
70+
secrets_;
71+
};
5972
/**
6073
* Configuration for the HTTP golang extension filter.
6174
*/
@@ -72,6 +85,7 @@ class FilterConfig : public std::enable_shared_from_this<FilterConfig>,
7285
const std::string& pluginName() const { return plugin_name_; }
7386
uint64_t getConfigId();
7487
GolangFilterStats& stats() { return stats_; }
88+
const SecretReader& getSecretReader() const { return *secret_reader_; }
7589

7690
void newGoPluginConfig();
7791
CAPIStatus defineMetric(uint32_t metric_type, absl::string_view name, uint32_t* metric_id);
@@ -95,6 +109,8 @@ class FilterConfig : public std::enable_shared_from_this<FilterConfig>,
95109
MetricStoreSharedPtr metric_store_ ABSL_GUARDED_BY(mutex_);
96110
// filter level config is created in C++ side, and freed by Golang GC finalizer.
97111
httpConfigInternal* config_{nullptr};
112+
113+
std::shared_ptr<SecretReader> secret_reader_;
98114
};
99115

100116
using FilterConfigSharedPtr = std::shared_ptr<FilterConfig>;
@@ -296,6 +312,7 @@ class Filter : public Http::StreamFilter,
296312
CAPIStatus getStringFilterState(absl::string_view key, uint64_t* value_data, int* value_len);
297313
CAPIStatus getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len,
298314
GoInt32* rc);
315+
CAPIStatus getSecret(absl::string_view key, uint64_t* value_data, int* value_len);
299316

300317
bool isProcessingInGo() {
301318
return decoding_state_.isProcessingInGo() || encoding_state_.isProcessingInGo();

0 commit comments

Comments
 (0)