1
#include "cilium/secret_watcher.h"
2

            
3
#include <fmt/format.h>
4

            
5
#include <atomic>
6
#include <string>
7
#include <utility>
8

            
9
#include "envoy/api/api.h"
10
#include "envoy/common/callback.h"
11
#include "envoy/common/exception.h"
12
#include "envoy/config/core/v3/config_source.pb.h"
13
#include "envoy/extensions/transport_sockets/tls/v3/tls.pb.h"
14
#include "envoy/secret/secret_provider.h"
15
#include "envoy/server/transport_socket_config.h"
16

            
17
#include "source/common/common/logger.h"
18
#include "source/common/common/thread.h"
19
#include "source/common/config/datasource.h"
20
#include "source/common/tls/context_config_impl.h"
21
#include "source/common/tls/server_context_config_impl.h"
22

            
23
#include "absl/status/status.h"
24
#include "absl/synchronization/mutex.h"
25
#include "cilium/api/npds.pb.h"
26
#include "cilium/grpc_subscription.h"
27
#include "cilium/network_policy.h"
28

            
29
namespace Envoy {
30
namespace Cilium {
31

            
32
namespace {
33

            
34
// SDS config used in production
35
7
envoy::config::core::v3::ConfigSource getCiliumSDSConfig(const std::string&) {
36
  /* returned config_source has initial_fetch_timeout of 50 milliseconds. */
37
7
  return Cilium::cilium_xds_api_config;
38
7
}
39

            
40
Secret::GenericSecretConfigProviderSharedPtr
41
secretProvider(Server::Configuration::TransportSocketFactoryContext& context,
42
78
               const std::string& sds_name) {
43
78
  envoy::config::core::v3::ConfigSource config_source = getSDSConfig(sds_name);
44
78
  return context.serverFactoryContext().secretManager().findOrCreateGenericSecretProvider(
45
78
      config_source, sds_name, context.serverFactoryContext(), context.initManager());
46
78
}
47

            
48
} // namespace
49

            
50
GetSdsConfigFunc getSDSConfig = &getCiliumSDSConfig;
51
65
void setSDSConfigFunc(GetSdsConfigFunc func) { getSDSConfig = func; }
52
6
void resetSDSConfigFunc() { getSDSConfig = &getCiliumSDSConfig; }
53

            
54
SecretWatcher::SecretWatcher(const NetworkPolicyMapImpl& parent, const std::string& sds_name)
55
78
    : parent_(parent), name_(sds_name),
56
78
      secret_provider_(secretProvider(parent.transportFactoryContext(), sds_name)),
57
78
      update_secret_(readAndWatchSecret()) {}
58

            
59
78
SecretWatcher::~SecretWatcher() {
60
78
  if (!Thread::MainThread::isMainOrTestThread()) {
61
    ENVOY_LOG(error, "SecretWatcher: Destructor executing in a worker thread, while "
62
                     "only main thread should destruct xDS resources");
63
  }
64
78
  delete load();
65
78
}
66

            
67
78
Envoy::Common::CallbackHandlePtr SecretWatcher::readAndWatchSecret() {
68
78
  THROW_IF_NOT_OK(store());
69
78
  return secret_provider_->addUpdateCallback([this]() { return store(); });
70
78
}
71

            
72
153
absl::Status SecretWatcher::store() {
73
153
  const auto* secret = secret_provider_->secret();
74
153
  if (secret != nullptr) {
75
75
    Api::Api& api = parent_.transportFactoryContext().serverFactoryContext().api();
76
75
    auto string_or_error = Config::DataSource::read(secret->secret(), true, api);
77
75
    if (!string_or_error.ok()) {
78
      return string_or_error.status();
79
    }
80
75
    std::string* p = new std::string(string_or_error.value());
81
75
    std::string* old = ptr_.exchange(p, std::memory_order_release);
82
75
    if (old != nullptr) {
83
      // Delete old value after all threads have scheduled
84
      parent_.runAfterAllThreads([old]() { delete old; });
85
    }
86
75
  }
87
153
  return absl::OkStatus();
88
153
}
89

            
90
96
const std::string* SecretWatcher::load() const { return ptr_.load(std::memory_order_acquire); }
91

            
92
TLSContext::TLSContext(const NetworkPolicyMapImpl& parent, const std::string& name)
93
42
    : manager_(parent.transportFactoryContext().serverFactoryContext().sslContextManager()),
94
42
      scope_(parent.transportFactoryContext().serverFactoryContext().serverScope()),
95
42
      init_target_(fmt::format("TLS Context {} secret", name), []() {}) {}
96

            
97
namespace {
98

            
99
void setCommonConfig(const cilium::TLSContext config,
100
42
                     envoy::extensions::transport_sockets::tls::v3::CommonTlsContext* tls_context) {
101
42
  if (!config.validation_context_sds_secret().empty()) {
102
10
    auto sds_secret = tls_context->mutable_validation_context_sds_secret_config();
103
10
    sds_secret->set_name(config.validation_context_sds_secret());
104
10
    auto* config_source = sds_secret->mutable_sds_config();
105
10
    *config_source = getSDSConfig(config.validation_context_sds_secret());
106
32
  } else if (!config.trusted_ca().empty()) {
107
11
    auto validation_context = tls_context->mutable_validation_context();
108
11
    auto trusted_ca = validation_context->mutable_trusted_ca();
109
11
    trusted_ca->set_inline_string(config.trusted_ca());
110
11
  }
111
42
  if (!config.tls_sds_secret().empty()) {
112
10
    auto sds_secret = tls_context->add_tls_certificate_sds_secret_configs();
113
10
    sds_secret->set_name(config.tls_sds_secret());
114
10
    auto* config_source = sds_secret->mutable_sds_config();
115
10
    *config_source = getSDSConfig(config.tls_sds_secret());
116
32
  } else if (!config.certificate_chain().empty()) {
117
11
    auto tls_certificate = tls_context->add_tls_certificates();
118
11
    auto certificate_chain = tls_certificate->mutable_certificate_chain();
119
11
    certificate_chain->set_inline_string(config.certificate_chain());
120
11
    if (!config.private_key().empty()) {
121
11
      auto private_key = tls_certificate->mutable_private_key();
122
11
      private_key->set_inline_string(config.private_key());
123
11
    } else {
124
      throw EnvoyException("TLS Context: missing private key");
125
    }
126
11
  }
127
42
  if (!config.alpn_protocols().empty()) {
128
16
    for (const std::string& protocol : config.alpn_protocols()) {
129
16
      ENVOY_LOG_MISC(trace, "setCommonConfig adding ALPN {}", protocol);
130
16
      tls_context->add_alpn_protocols(protocol);
131
16
    }
132
8
  }
133
42
}
134

            
135
} // namespace
136

            
137
DownstreamTLSContext::DownstreamTLSContext(const NetworkPolicyMapImpl& parent,
138
                                           const cilium::TLSContext config)
139
21
    : TLSContext(parent, "server") {
140
  // Server config always needs the TLS certificate to present to the client
141
21
  if (config.tls_sds_secret().empty() && config.certificate_chain().empty()) {
142
    throw EnvoyException("Downstream TLS Context: missing certificate chain");
143
  }
144

            
145
21
  envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext context_config;
146
21
  auto tls_context = context_config.mutable_common_tls_context();
147

            
148
  // Check if client certificate is required
149
21
  if (!config.validation_context_sds_secret().empty() || !config.trusted_ca().empty()) {
150
    auto require_tls_certificate = context_config.mutable_require_client_certificate();
151
    require_tls_certificate->set_value(true);
152
  }
153
21
  setCommonConfig(config, tls_context);
154

            
155
21
  for (int i = 0; i < config.server_names_size(); i++) {
156
    server_names_.emplace_back(config.server_names(i));
157
  }
158
21
  auto server_config_or_error = Extensions::TransportSockets::Tls::ServerContextConfigImpl::create(
159
21
      context_config, parent.transportFactoryContext(), false);
160
  // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
161
21
  THROW_IF_NOT_OK(server_config_or_error.status());
162
21
  server_config_ = std::move(server_config_or_error.value());
163

            
164
21
  auto create_server_context = [this]() {
165
19
    ENVOY_LOG(debug, "Server secret is updated.");
166
19
    auto ctx_or_error =
167
19
        manager_.createSslServerContext(scope_, *server_config_, server_names_, nullptr);
168
    // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
169
19
    THROW_IF_NOT_OK(ctx_or_error.status());
170
19
    auto ctx = std::move(ctx_or_error.value());
171
19
    {
172
19
      absl::WriterMutexLock l(&ssl_context_mutex_);
173
19
      std::swap(ctx, server_context_);
174
19
    }
175
19
    manager_.removeContext(ctx);
176
19
    init_target_.ready();
177
19
    return absl::OkStatus();
178
19
  };
179
21
  server_config_->setSecretUpdateCallback(create_server_context);
180
21
  if (server_config_->isReady()) {
181
11
    static_cast<void>(create_server_context());
182
21
  } else {
183
10
    parent.transportFactoryContext().initManager().add(init_target_);
184
10
  }
185
21
}
186

            
187
UpstreamTLSContext::UpstreamTLSContext(const NetworkPolicyMapImpl& parent,
188
                                       cilium::TLSContext config)
189
21
    : TLSContext(parent, "client") {
190
  // Client context always needs the trusted CA for server certificate validation
191
  // TODO: Default to system default trusted CAs?
192
21
  if (config.validation_context_sds_secret().empty() && config.trusted_ca().empty()) {
193
    throw EnvoyException("Upstream TLS Context: missing trusted CA");
194
  }
195

            
196
21
  envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext context_config;
197
21
  auto tls_context = context_config.mutable_common_tls_context();
198
21
  setCommonConfig(config, tls_context);
199

            
200
21
  if (config.server_names_size() > 0) {
201
    if (config.server_names_size() > 1) {
202
      throw EnvoyException("Upstream TLS Context: more than one server name");
203
    }
204
    context_config.set_sni(config.server_names(0));
205
  }
206
21
  auto client_config_or_error = Extensions::TransportSockets::Tls::ClientContextConfigImpl::create(
207
21
      context_config, parent.transportFactoryContext());
208
  // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
209
21
  THROW_IF_NOT_OK(client_config_or_error.status());
210

            
211
21
  client_config_ = std::move(client_config_or_error.value());
212
21
  auto create_client_context = [this]() {
213
19
    ENVOY_LOG(debug, "Client secret is updated.");
214
19
    auto ctx_or_error = manager_.createSslClientContext(scope_, *client_config_);
215
    // NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
216
19
    THROW_IF_NOT_OK(ctx_or_error.status());
217
19
    auto ctx = std::move(ctx_or_error.value());
218
19
    {
219
19
      absl::WriterMutexLock l(&ssl_context_mutex_);
220
19
      std::swap(ctx, client_context_);
221
19
    }
222
19
    manager_.removeContext(ctx);
223
19
    init_target_.ready();
224
19
    return absl::OkStatus();
225
19
  };
226
21
  client_config_->setSecretUpdateCallback(create_client_context);
227
21
  if (client_config_->isReady()) {
228
11
    static_cast<void>(create_client_context());
229
21
  } else {
230
10
    parent.transportFactoryContext().initManager().add(init_target_);
231
10
  }
232
21
}
233

            
234
} // namespace Cilium
235
} // namespace Envoy