/
l7policy.cc
203 lines (174 loc) · 7.96 KB
/
l7policy.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include "cilium/l7policy.h"
#include <string>
#include "envoy/registry/registry.h"
#include "envoy/singleton/manager.h"
#include "source/common/buffer/buffer_impl.h"
#include "source/common/common/enum_to_int.h"
#include "source/common/config/utility.h"
#include "source/common/http/header_map_impl.h"
#include "source/common/http/utility.h"
#include "source/common/network/upstream_server_name.h"
#include "source/common/network/upstream_subject_alt_names.h"
#include "cilium/api/l7policy.pb.validate.h"
#include "cilium/network_policy.h"
#include "cilium/socket_option.h"
namespace Envoy {
namespace Cilium {
class ConfigFactory : public Server::Configuration::NamedHttpFilterConfigFactory {
public:
Http::FilterFactoryCb
createFilterFactoryFromProto(const Protobuf::Message& proto_config, const std::string&,
Server::Configuration::FactoryContext& context) override {
auto config = std::make_shared<Cilium::Config>(
MessageUtil::downcastAndValidate<const ::cilium::L7Policy&>(
proto_config, context.messageValidationVisitor()),
context);
return [config](Http::FilterChainFactoryCallbacks& callbacks) mutable -> void {
callbacks.addStreamFilter(std::make_shared<Cilium::AccessFilter>(config));
};
}
ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<::cilium::L7Policy>();
}
std::string name() const override { return "cilium.l7policy"; }
};
/**
* Static registration for this filter. @see RegisterFactory.
*/
REGISTER_FACTORY(ConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory);
Config::Config(const std::string& access_log_path, const std::string& denied_403_body,
Server::Configuration::FactoryContext& context)
: time_source_(context.timeSource()), stats_{ALL_CILIUM_STATS(
POOL_COUNTER_PREFIX(context.scope(), "cilium"))},
denied_403_body_(denied_403_body), access_log_(nullptr) {
if (access_log_path.length()) {
access_log_ = AccessLog::Open(access_log_path);
if (!access_log_) {
ENVOY_LOG(warn, "Cilium filter can not open access log socket {}", access_log_path);
}
}
if (denied_403_body_.length() == 0) {
denied_403_body_ = "Access denied";
}
size_t len = denied_403_body_.length();
if (len < 2 || denied_403_body_[len - 2] != '\r' || denied_403_body_[len - 1] != '\n') {
denied_403_body_.append("\r\n");
}
}
Config::Config(const ::cilium::L7Policy& config, Server::Configuration::FactoryContext& context)
: Config(config.access_log_path(), config.denied_403_body(), context) {
if (config.policy_name() != "") {
throw EnvoyException(fmt::format(
"cilium.l7policy: 'policy_name' is no longer supported: \'{}\'", config.DebugString()));
}
if (config.has_is_ingress()) {
ENVOY_LOG(warn,
"cilium.l7policy: 'is_ingress' config option is deprecated and "
"is ignored: \'{}\'",
config.DebugString());
}
}
Config::~Config() {
if (access_log_) {
access_log_->Close();
}
}
void Config::Log(AccessLog::Entry& entry, ::cilium::EntryType type) {
if (access_log_) {
access_log_->Log(entry, type);
}
}
void AccessFilter::onDestroy() {}
Http::FilterHeadersStatus AccessFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) {
const auto& conn = callbacks_->connection();
if (!conn) {
ENVOY_LOG(warn, "cilium.l7policy: No connection");
// Return a 500 response
callbacks_->sendLocalReply(Http::Code::InternalServerError, "", nullptr, absl::nullopt,
absl::string_view());
return Http::FilterHeadersStatus::StopIteration;
}
const Network::Socket::OptionsSharedPtr socketOptions = conn->socketOptions();
const auto option = Cilium::GetSocketOption(socketOptions);
if (!option) {
ENVOY_LOG(warn, "cilium.l7policy: Cilium Socket Option not found");
// Return a 500 response
callbacks_->sendLocalReply(Http::Code::InternalServerError, "", nullptr, absl::nullopt,
absl::string_view());
return Http::FilterHeadersStatus::StopIteration;
}
// Initialize the log entry
log_entry_.InitFromRequest(option->pod_ip_, option->proxy_id_, option->ingress_,
option->identity_,
callbacks_->streamInfo().downstreamAddressProvider().remoteAddress(),
0, callbacks_->streamInfo().downstreamAddressProvider().localAddress(),
callbacks_->streamInfo(), headers);
// This callback is never called if upstream connection fails
callbacks_->addUpstreamCallback([this, option](Http::RequestHeaderMap& headers,
StreamInfo::StreamInfo& stream_info) -> bool {
// Destination may have changed due to upstream routing and load balancing.
// Use original destination address for policy enforcement when not L7 LB, even if the actual
// destination may have chanegd. This can happen with custom Envoy Listeners.
const Network::Address::InstanceConstSharedPtr& dst_address =
option->policyUseUpstreamDestinationAddress()
? stream_info.upstreamInfo()->upstreamHost()->address()
: callbacks_->streamInfo().downstreamAddressProvider().localAddress();
if (nullptr == dst_address) {
ENVOY_LOG(warn, "cilium.l7policy: No destination address");
return false;
}
const auto dip = dst_address->ip();
if (!dip) {
ENVOY_LOG_MISC(warn, "cilium.l7policy: Non-IP destination address: {}",
dst_address->asString());
return false;
}
uint32_t destination_port = dip->port();
uint32_t destination_identity = option->resolvePolicyId(dip);
// Update the log entry with the chosen destination address and current headers, as remaining
// filters and/or upstream may have altered headers.
log_entry_.UpdateFromRequest(destination_identity, dst_address, headers);
const auto& policy = option->getPolicy();
if (policy) {
allowed_ = policy->Allowed(option->ingress_, destination_port,
option->ingress_ ? option->identity_ : destination_identity,
headers, log_entry_);
ENVOY_LOG(debug, "cilium.l7policy: {} ({}->{}) policy lookup for endpoint {} for port {}: {}",
option->ingress_ ? "ingress" : "egress", option->identity_, destination_identity,
option->pod_ip_, destination_port, allowed_ ? "ALLOW" : "DENY");
if (allowed_) {
// Log as a forwarded request
config_->Log(log_entry_, ::cilium::EntryType::Request);
}
} else {
ENVOY_LOG(debug, "cilium.l7policy: No {} policy found for pod {}, defaulting to DENY",
option->ingress_ ? "ingress" : "egress", option->pod_ip_);
}
return allowed_;
});
return Http::FilterHeadersStatus::Continue;
}
Http::FilterHeadersStatus AccessFilter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) {
// Accepted & forwaded requests are logged by the upstream callback. Requests can remain unlogged
// if they are not accepted or any other error happens and upstream callback is never called.
// Logging the (locally generated) response is not enough as we no longer log request headers with
// responses.
if (!allowed_) {
// Request was not yet logged. Log it so that the headers get logged.
// Default logging local errors as "forwarded".
// The response log will contain the locally generated HTTP error code.
auto logType = ::cilium::EntryType::Request;
if (headers.Status()->value() == "403") {
// Log as a denied request.
logType = ::cilium::EntryType::Denied;
config_->stats_.access_denied_.inc();
}
config_->Log(log_entry_, logType);
}
// Log the response
log_entry_.UpdateFromResponse(headers, config_->time_source_);
config_->Log(log_entry_, ::cilium::EntryType::Response);
return Http::FilterHeadersStatus::Continue;
}
} // namespace Cilium
} // namespace Envoy