From a2b716cf5467ae932df3095f70a50d8c8aeab07d Mon Sep 17 00:00:00 2001 From: shikugawa Date: Mon, 13 Apr 2020 12:03:02 +0000 Subject: [PATCH 01/44] init --- .../http/authn_wasm/authenticator/base.cc | 119 ++++++++++++++++++ .../http/authn_wasm/authenticator/base.h | 65 ++++++++++ .../http/authn_wasm/authenticator/peer.cc | 63 ++++++++++ .../http/authn_wasm/authenticator/peer.h | 50 ++++++++ src/envoy/http/authn_wasm/cert.h | 118 +++++++++++++++++ .../http/authn_wasm/connection_context.h | 58 +++++++++ src/envoy/http/authn_wasm/filter.h | 114 +++++++++++++++++ src/envoy/http/authn_wasm/filter_context.cc | 76 +++++++++++ src/envoy/http/authn_wasm/filter_context.h | 0 9 files changed, 663 insertions(+) create mode 100644 src/envoy/http/authn_wasm/authenticator/base.cc create mode 100644 src/envoy/http/authn_wasm/authenticator/base.h create mode 100644 src/envoy/http/authn_wasm/authenticator/peer.cc create mode 100644 src/envoy/http/authn_wasm/authenticator/peer.h create mode 100644 src/envoy/http/authn_wasm/cert.h create mode 100644 src/envoy/http/authn_wasm/connection_context.h create mode 100644 src/envoy/http/authn_wasm/filter.h create mode 100644 src/envoy/http/authn_wasm/filter_context.cc create mode 100644 src/envoy/http/authn_wasm/filter_context.h diff --git a/src/envoy/http/authn_wasm/authenticator/base.cc b/src/envoy/http/authn_wasm/authenticator/base.cc new file mode 100644 index 00000000000..68b9be5dab9 --- /dev/null +++ b/src/envoy/http/authn_wasm/authenticator/base.cc @@ -0,0 +1,119 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include + +#include "absl/strings/string_view.h" +#include "src/envoy/http/authn_wasm/authenticator/base.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +namespace { +// The default header name for an exchanged token +static constexpr absl::string_view kExchangedTokenHeaderName = "ingress-authorization"; + +// Returns whether the header for an exchanged token is found +bool FindHeaderOfExchangedToken(const istio::authentication::v1alpha1::Jwt& jwt) { + return (jwt.jwt_headers_size() == 1 && + LowerCaseString(kExchangedTokenHeaderName) == + LowerCaseString(jwt.jwt_headers(0))); +} + +} // namespace + +AuthenticatorBase::AuthenticatorBase(FilterContext* filter_context) + : filter_context_(*filter_context) {} + +AuthenticatorBase::~AuthenticatorBase() {} + +bool AuthenticatorBase::validateTrustDomain() const { + std::string peer_trust_domain = filter_context_.peerCertificateInfo()->getTrustDomain(); + if (!peer_trust_domain.has_value()) { + logError("trust domain validation failed: cannot get peer trust domain"); + return false; + } + + std::string local_trust_domain = filter_context_.localCertificateInfo()->getTrustDomain(); + if (!local_trust_domain.has_value()) { + logError("trust domain validation failed: cannot get local trust domain"); + return false; + } + + if (peer_trust_domain.value() != local_trust_domain.value()) { + logError("trust domain validation failed: peer trust domain ", peer_trust_domain.value()); + logError("different from local trust domain ", local_trust_domain.value()); + return false; + } + + logDebug("trust domain validation succeeded"); + return true; +} + +bool AuthenticatorBase::validateX509(const istio::authentication::v1alpha1::MutualTls& mtls, + istio::authn::Payload* payload) const { + bool has_user; + const bool presented = filter_context_.peerCertificateInfo() != nullptr && + filter_context_.peerCertificateInfo()->presented(); + + if (filter_context_.peerCertificateInfo() != nullptr) { + const auto principal = filter_context_.peerCertificateInfo()->getPrincipal(); + if (principal.has_value()) { + *(payload->mutable_x509()->mutable_user()) = principal.value(); + } + has_user = presented && principal.has_value(); + } + + logDebug("validateX509 mode: ", istio::authentication::v1alpha1::MutualTls::Mode_Name(mtls.mode())); + logDebug("validateX509 ssl: ", filter_context_.isTls()); + logDebug("validateX509 has_user: ", has_user); + + if (!has_user) { + // For plaintext connection, return value depend on mode: + // - PERMISSIVE: always true. + // - STRICT: always false. + switch (mtls.mode()) { + case istio::authentication::v1alpha1::MutualTls::PERMISSIVE: + return true; + case istio::authentication::v1alpha1::MutualTls::STRICT: + return false; + default: + logError("should not be reached to this section."); + abort(); + } + } + + if (filter_context_.filterConfig().skip_validate_trust_domain()) { + logDebug("trust domain validation skipped"); + return true; + } + + // For TLS connection with valid certificate, validate trust domain for both + // PERMISSIVE and STRICT mode. + return validateTrustDomain(); +} + +// TODO(shikugawa): implement validateJWT +bool AuthenticatorBase::validateJwt( + const istio::authentication::v1alpha1::Jwt& params, istio::authn::Payload* payload) { + return true; +} + +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/authenticator/base.h b/src/envoy/http/authn_wasm/authenticator/base.h new file mode 100644 index 00000000000..5a81842b247 --- /dev/null +++ b/src/envoy/http/authn_wasm/authenticator/base.h @@ -0,0 +1,65 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include + +#include "src/envoy/http/authn_wasm/filter_context.h" + +#include "authentication/v1alpha1/policy.pb.h" +#include "src/istio/authn/context.pb.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +// AuthenticatorBase is the base class for authenticator. It provides functions +// to perform individual authentication methods, which can be used to construct +// compound authentication flow. +class AuthenticatorBase { +public: + AuthenticatorBase(FilterContext* filter_context); + virtual ~AuthenticatorBase(); + + // Perform authentication. + virtual bool run(istio::authn::Payload*) = 0; + + // Validate TLS/MTLS connection and extract authenticated attributes (just + // source user identity for now). Unlike mTLS, TLS connection does not require + // a client certificate. + virtual bool validateX509( + const istio::authentication::v1alpha1::MutualTls& params, + istio::authn::Payload* payload) const; + + // Validates JWT given the jwt params. If JWT is validated, it will extract + // attributes and claims (JwtPayload), returns status SUCCESS. + // Otherwise, returns status FAILED. + virtual bool validateJwt(const istio::authentication::v1alpha1::Jwt& params, + istio::authn::Payload* payload); + + // Mutable accessor to filter context. + FilterContextPtr& filterContext() { return filter_context_; } + +private: + // Pointer to filter state. Do not own. + FilterContextPtr filter_context_; +}; + +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/authenticator/peer.cc b/src/envoy/http/authn_wasm/authenticator/peer.cc new file mode 100644 index 00000000000..421e38dde59 --- /dev/null +++ b/src/envoy/http/authn_wasm/authenticator/peer.cc @@ -0,0 +1,63 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn_wasm/authenticator/authenticator.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +PeerAuthenticator::PeerAuthenticator(FilterContext* filter_context, + const istio::authentication::v1alpha1::Policy& policy) + : AuthenticatorBase(filter_context), policy_(policy) {} + +bool PeerAuthenticator::run(istio::authn::Payload* payload) { + bool success = false; + if (policy_.peers_size() == 0) { + ENVOY_LOG(debug, "No method defined. Skip source authentication."); + success = true; + return success; + } + for (const auto& method : policy_.peers()) { + switch (method.params_case()) { + case istio::authentication::v1alpha1::PeerAuthenticationMethod::ParamsCase::kMtls: + success = validateX509(method.mtls(), payload); + break; + case istio::authentication::v1alpha1::PeerAuthenticationMethod::ParamsCase::kJwt: + success = validateJwt(method.jwt(), payload); + break; + default: + logError("Unknown peer authentication param ", method.DebugString()); + success = false; + break; + } + + if (success) { + break; + } + } + + if (success) { + filterContext()->setPeerResult(payload); + } + + return success; +} + +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/authenticator/peer.h b/src/envoy/http/authn_wasm/authenticator/peer.h new file mode 100644 index 00000000000..cd880017cbc --- /dev/null +++ b/src/envoy/http/authn_wasm/authenticator/peer.h @@ -0,0 +1,50 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "authentication/v1alpha1/policy.pb.h" +#include "src/envoy/http/authn_wasm/authenticator/base.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +// PeerAuthenticator performs peer authentication for given policy. +class PeerAuthenticator : public AuthenticatorBase { +public: + using PeerAuthenticatorPtr = std::unique_ptr; + + static PeerAuthenticatorPtr create(FilterContext* filter_context) { + return std::make_unique( + filter_context, filter_context->filterConfig().policy()); + } + + bool run(istio::authn::Payload*) override; + +private: + PeerAuthenticator(FilterContext* filter_context, + const istio::authentication::v1alpha1::Policy& policy); + + // Reference to the authentication policy that the authenticator should + // enforce. Typically, the actual object is owned by filter. + const istio::authentication::v1alpha1::Policy& policy_; +}; + +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/cert.h b/src/envoy/http/authn_wasm/cert.h new file mode 100644 index 00000000000..4465125a21f --- /dev/null +++ b/src/envoy/http/authn_wasm/cert.h @@ -0,0 +1,118 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/strings/match.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +constexpr absl::string_view kSPIFFEPrefix = "spiffe://"; + +template +class TlsCertificateInfo { +public: + absl::optional getCertSans() { + const auto& uri_sans = static_cast(*this).uriSans(); + for (const auto& uri_san: uri_sans) { + if (absl::StartsWith(uri_san, kSPIFFEPrefix)) { + return uri_san; + } + } + if (!uri_sans.empty()) { + return uri_sans[0]; + } + return absl::nullopt; + } + + absl::optional getPrincipal() { + const auto cert_sans = getCertSans(); + if (cert_sans.has_value()) { + if (absl::StartsWith(cert_sans, kSPIFFEPrefix)) { + return cert_sans.value().substr(kSPIFFEPrefix.size()); + } + return cert_sans.value(); + } + return absl::nullopt; + } + + absl::optional getTrustDomain() { + const auto cert_san = getCertSans(); + if (!cert_san.has_value() || !absl::StartsWith(cert_san.value(), kSPIFFEPrefix)) { + return absl::nullopt; + } + + // Skip the prefix "spiffe://" before getting trust domain. + size_t slash = cert_san.find('/', kSPIFFEPrefix.size()); + if (slash == std::string::npos) { + return absl::nullopt; + } + + size_t len = slash - kSPIFFEPrefix.size(); + return cert_san.substr(kSPIFFEPrefix.size(), len); + } +}; + +class TlsPeerCertificateInfo : public TlsCertificateInfo { +public: + // getter + std::string& serialNumber() { return serial_number_; } + std::string& issuer() { return issuer_; } + std::string& subject() { return subject_; } + std::string& sha256Digest() { return sha256_digest_; } + bool& validated() { return validated_; } + bool& presented() { return presented_; } + std::string& uriSans() { return uri_sans_; } + std::string& dnsSans() { return dns_sans_; } + +private: + std::string serial_number_; + std::string issuer_; + std::string subject_; + std::string sha256_digest_; + std::string uri_sans_; + std::string dns_sans_; + + bool validated_; + bool presented_; +}; + +class TlsLocalCertificateInfo : public TlsCertificateInfo { +public: + // getter + const std::string& subject() { return subject_; } + const std::vector uriSans() { return uri_sans_; } + const std::vector dnsSans() { return dns_sans_; } + +private: + std::string subject_; + + std::vector uri_sans_; + std::vector dns_sans_; +}; + +using TlsPeerCertificateInfoPtr = std::unique_ptr; +using TlsLocalCertificateInfoPtr = std::unique_ptr; + +} +} +} +} \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/connection_context.h b/src/envoy/http/authn_wasm/connection_context.h new file mode 100644 index 00000000000..ca68eca0f28 --- /dev/null +++ b/src/envoy/http/authn_wasm/connection_context.h @@ -0,0 +1,58 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include +#include + +#include "src/envoy/http/authn_wasm/cert.h" +#include "proxy_wasm_intrinsics.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +constexpr absl::string_view Connection = "connection"; +constexpr absl::string_view TlsVersion = "tls_version"; +constexpr absl::string_view UriSanPeerCertificate = "uri_san_peer_certificate"; +constexpr absl::string_view LocalSanPeerCertificate = "uri_san_local_certificate"; +constexpr absl::string_view Mtls = "mtls"; + +class ConnectionContext { +public: + ConnectionContext() { + if (isTls()) { + peer_cert_info_ = std::make_unique(); + peer_cert_info_->uriSans() = getProperty({Connection, UriSanPeerCertificate}).value_or(""); + local_cert_info_ = std::make_unique(); + local_cert_info_->uriSans() = getProperty({Connection, UriSanLocalCertificate}).value_or(""); + mtls_ = getProperty({Connection, Mtls}).value_or(false); + } + } + bool isMtls() { return mtls_; } + bool isTls() { return getProperty({Connection, TlsVersion}).has_value(); } + +private: + std::unique_ptr peer_cert_info_; + std::unique_ptr local_cert_info_; + bool mtls_; +}; + +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/filter.h b/src/envoy/http/authn_wasm/filter.h new file mode 100644 index 00000000000..69c78eaba2c --- /dev/null +++ b/src/envoy/http/authn_wasm/filter.h @@ -0,0 +1,114 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "proxy_wasm_intrinsics.h" +#include "absl/strings/string_view.h" + +#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" + +#include "src/envoy/http/authn_wasm/authenticator/base.h" + +namespace Envoy { +namespace Extensions { +namespace Wasm { +namespace AuthN { + +using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; +using StringView = absl::string_view; + +// AuthnRootContext is the root context for all streams processed by the +// thread. It has the same lifetime as the worker thread and acts as target for +// interactions that outlives individual stream, e.g. timer, async calls. +class AuthnRootContext : public RootContext { +public: + AuthnRootContext(uint32_t id, absl::string_view root_id) + : RootContext(id, root_id) {} + ~AuthnRootContext() {} + + // RootContext + bool validateConfiguration(size_t) override { return true; } + bool onConfigure(size_t) override; + bool onStart(size_t) override { return true; } + void onTick() override {} + void onQueueReady(uint32_t) override {} + bool onDone() override { return true; } + + // Low level HTTP/gRPC interface. + void onHttpCallResponse(uint32_t token, uint32_t headers, size_t body_size, uint32_t trailers) override {} + void onGrpcReceiveInitialMetadata(uint32_t token, uint32_t headers) override {} + void onGrpcReceiveTrailingMetadata(uint32_t token, uint32_t trailers) override {} + void onGrpcReceive(uint32_t token, size_t body_size) override {} + void onGrpcClose(uint32_t token, GrpcStatus status) override {} + + const FilterConfig& filterConfig() { return filter_config_; }; + + private: + FilterConfig filter_config_; +}; + +// Per-stream context. +class AuthnContext : public Context { +public: + explicit AuthnContext(uint32_t id, RootContext* root) : Context(id, root) {} + ~AuthnContext() = default; + + void onCreate() override {} + + // Context + FilterStatus onNewConnection() override { return FilterStatus::Continue; } + FilterStatus onDownstreamData(size_t, bool) override { return FilterStatus::Continue; } + FilterStatus onUpstreamData(size_t, bool) override { return FilterStatus::Continue; } + void onDownstreamConnectionClose(PeerType) override {} + void onUpstreamConnectionClose(PeerType) override {} + FilterHeadersStatus onRequestHeaders(uint32_t) override; + FilterMetadataStatus onRequestMetadata(uint32_t) override { return FilterMetadataStatus::Continue; } + FilterDataStatus onRequestBody(size_t, bool) override { return FilterDataStatus::Continue; } + FilterTrailersStatus onRequestTrailers(uint32_t) override { return FilterTrailersStatus::Continue; } + FilterHeadersStatus onResponseHeaders(uint32_t) override { return FilterHeadersStatus::Continue; } + FilterMetadataStatus onResponseMetadata(uint32_t) override { return FilterMetadataStatus::Continue; } + FilterDataStatus onResponseBody(size_t, bool) override { return FilterDataStatus::Continue; } + FilterTrailersStatus onResponseTrailers(uint32_t) override { return FilterTrailersStatus::Continue; } + void onDone() override {} + void onLog() override {} + + const FilterConfig& filterConfig() { + return rootContext()->filterConfig(); + }; + +private: + std::unique_ptr createPeerAuthenticator( + Envoy::Http::Istio::AuthN::FilterContext* filter_context); + // TODO(shikugawa): origin authenticator implementation. + // std::unique_ptr createOriginAuthenticator( + // istio::AuthN::FilterContext* filter_context); + + inline AuthnRootContext* rootContext() { + return dynamic_cast(this->root()); + }; + + // Context for authentication process. Created in decodeHeader to start + // authentication process. + std::unique_ptr filter_context_; +}; + +static RegisterContextFactory register_AuthnWasm( + CONTEXT_FACTORY(AuthnContext), ROOT_FACTORY(AuthnRootContext)); + +} // namespace AuthnWasm +} // namespace Wasm +} // namespace Extensions +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/filter_context.cc b/src/envoy/http/authn_wasm/filter_context.cc new file mode 100644 index 00000000000..5bbe0e1dc5a --- /dev/null +++ b/src/envoy/http/authn_wasm/filter_context.cc @@ -0,0 +1,76 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "src/envoy/utils/filter_names.h" +#include "src/envoy/http/authn_wasm/filter_context.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +void FilterContext::setPeerResult(const istio::authn::Payload* payload) { + if (payload != nullptr) { + switch (payload->payload_case()) { + case Payload::kX509: + logDebug("Set peer from X509: ", payload->x509().user()); + result_.set_peer_user(payload->x509().user()); + break; + case Payload::kJwt: + logDebug("Set peer from JWT: ", payload->jwt().user()); + result_.set_peer_user(payload->jwt().user()); + break; + default: + logDebug("Payload has not peer authentication data"); + break; + } + } +} + +void FilterContext::setOriginResult(const istio::authn::Payload* payload) { + // Authentication pass, look at the return payload and store to the context + // output. Set filter to continueDecoding when done. + // At the moment, only JWT can be used for origin authentication, so + // it's ok just to check jwt payload. + if (payload != nullptr && payload->has_jwt()) { + *result_.mutable_origin() = payload->jwt(); + } +} + +void FilterContext::setPrincipal(const istio::authentication::v1alpha1::PrincipalBinding& binding) { + switch (binding) { + case istio::authentication::v1alpha1::PrincipalBinding::USE_PEER: + logDebug("Set principal from peer: ", result_.peer_user()); + result_.set_principal(result_.peer_user()); + return; + case istio::authentication::v1alpha1::PrincipalBinding::USE_ORIGIN: + logDebug("Set principal from origin: ", result_.origin().user()) + result_.set_principal(result_.origin().user()); + return; + default: + // Should never come here. + // TODO(shikugawa): add wasm logger and enable to write logging like under format. + // e.g. logDebug("Invalid binding value", binding) + logDebug("Invalid binding value"); + return; + } +} + +} +} +} +} \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h new file mode 100644 index 00000000000..e69de29bb2d From f4fb7175d5249a6d5a958b8920ca5d257eade173 Mon Sep 17 00:00:00 2001 From: shikugawa Date: Mon, 13 Apr 2020 13:33:56 +0000 Subject: [PATCH 02/44] metadata --- src/envoy/http/authn_wasm/filter.cc | 76 +++++++++++++ src/envoy/http/authn_wasm/filter_context.h | 119 +++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/envoy/http/authn_wasm/filter.cc diff --git a/src/envoy/http/authn_wasm/filter.cc b/src/envoy/http/authn_wasm/filter.cc new file mode 100644 index 00000000000..8ce9f873fa2 --- /dev/null +++ b/src/envoy/http/authn_wasm/filter.cc @@ -0,0 +1,76 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn_wasm/filter.h" +#include "src/envoy/http/authn_wasm/connection_context.h" +#include "src/envoy/http/authn_wasm/authenticator/peer.h" + +#include "google/protobuf/text_format.h" +#include "google/protobuf/util/json_util.h" + +#include "authentication/v1alpha1/policy.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Wasm { +namespace AuthN { + +Istio::AuthN::HeaderMap void unmarshalPairs(const Pairs& pairs) { + Istio::AuthN::HeaderMap header_map; + for (auto&& [key, value]: pairs) { + header_map[key] = value; + } + return header_map; +} + +bool AuthnRootContext::onConfigure(size_t) { + WasmDataPtr configuration = getConfiguration(); + google::protobuf::util::JsonParseOptions json_options; + google::protobuf::util::Status status = JsonStringToMessage(configuration->toString(), + &filter_config_, json_options); + + if (status != google::protobuf::util::Status::OK) { + logError("Cannot parse authentication filter config: " + configuration->toString()); + return false; + } + + logInfo("Istio AuthN filter is started with this configuration: ", configuration->toString()); + return true; +} + +FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { + const auto context = ConnectionContext(); + const auto metadata = getValue({"metadata"}); + filter_context_.reset( + new Istio::AuthN::FilterContext( + unmarshalPairs(getRequestHeader()->pairs()), context, metadata, filter_config_)); + + istio::authn::Payload payload; + + if (PeerAuthenticator::create(filter_context_) && filter_config_.policy().peer_is_optional()) { + logError("Peer authentication failed."); + return FilterHeadersStatus::StopIteration; + } + + // TODO(shikugawa): origin authenticator + // TODO(shikugawa): save authenticate result state as dynamic metadata + + return FilterHeadersStatus::Continue; +} + +} +} +} +} \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h index e69de29bb2d..5a12038a2d7 100644 --- a/src/envoy/http/authn_wasm/filter_context.h +++ b/src/envoy/http/authn_wasm/filter_context.h @@ -0,0 +1,119 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include +#include + +#include + +#include "istio/authn/context.pb.h" +#include "authentication/v1alpha1/policy.pb.h" + +#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" +#include "envoy/config/core/v3/base.pb.h" + +#include "src/envoy/http/authn_wasm/connection_context.h" +#include "src/envoy/http/authn_wasm/cert.h" + +#include "proxy_wasm_intrinsics.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { + +using HeaderMap = std::unordered_map; + +// FilterContext holds inputs, such as request dynamic metadata and connection +// and result data for authentication process. +class FilterContext { +public: + FilterContext( + const HeaderMap& header_map, + const ConnectionContext& connection_context, + const envoy::config::core::v3::Metadata& dynamic_metadata, + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + filter_config) + : header_map_(header_map), + connection_context_(connection_context), + dynamic_metadata_(dynamic_metadata), + filter_config_(filter_config) {} + + // Sets peer result based on authenticated payload. Input payload can be null, + // which basically changes nothing. + void setPeerResult(const istio::authn::Payload* payload); + + // Sets origin result based on authenticated payload. Input payload can be + // null, which basically changes nothing. + void setOriginResult(const istio::authn::Payload* payload); + + // Sets principal based on binding rule, and the existing peer and origin + // result. + void setPrincipal( + const istio::authentication::v1alpha1::PrincipalBinding& binding); + + // Returns the authentication result. + const istio::authn::Result& authenticationResult() { return result_; } + + // Accessor to the filter config + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filterConfig() const { + return filter_config_; + } + + // Gets JWT payload (output from JWT filter) for given issuer. If non-empty + // payload found, returns true and set the output payload string. Otherwise, + // returns false. + bool getJwtPayload(const std::string& issuer, std::string* payload) const { return true; }; + + // Return header map. + const HeaderMap& headerMap() { return header_map_; } + +private: + // TODO(shikugawa): JWT implementation, required metadata retrieval. + // Helper function for getJwtPayload(). It gets the jwt payload from Envoy jwt + // filter metadata and write to |payload|. + bool getJwtPayloadFromEnvoyJwtFilter(const std::string& issuer, + std::string* payload) const { return true; }; + // Helper function for getJwtPayload(). It gets the jwt payload from Istio jwt + // filter metadata and write to |payload|. + bool getJwtPayloadFromIstioJwtFilter(const std::string& issuer, + std::string* payload) const { return true; }; + + // Const reference to request info dynamic metadata. This provides data that + // output from other filters, e.g JWT. + const envoy::config::core::v3::Metadata& dynamic_metadata_; + + // http request header + const HeaderMap& header_map_; + + // context of established connection + const ConnectionContext& connection_context_; + + // Holds authentication attribute outputs. + istio::authn::Result result_; + + // Store the Istio authn filter config. + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + filter_config_; +}; + +using FilterContextPtr = std::unique_ptr; + +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy From 06216c07b4746660a845e2e1f0db9ddd7527fbb9 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Mon, 25 May 2020 12:01:06 +0000 Subject: [PATCH 03/44] introduce wasm build with bazel --- WORKSPACE | 6 + repositories.bzl | 133 +++++++++++++++++ src/envoy/http/authn_wasm/BUILD | 73 +++++++++ src/envoy/http/authn_wasm/authenticator/BUILD | 45 ++++++ .../http/authn_wasm/authenticator/peer.cc | 2 - src/envoy/http/authn_wasm/filter_context.h | 4 +- src/istio/authn/BUILD | 18 +++ wasm.bzl | 140 ++++++++++++++++++ 8 files changed, 417 insertions(+), 4 deletions(-) create mode 100644 src/envoy/http/authn_wasm/BUILD create mode 100644 src/envoy/http/authn_wasm/authenticator/BUILD create mode 100644 wasm.bzl diff --git a/WORKSPACE b/WORKSPACE index cb1f7fe87b8..8cec5b8eb50 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -18,11 +18,13 @@ workspace(name = "io_istio_proxy") # http_archive is not a native function since bazel 0.19 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") load( "//:repositories.bzl", "docker_dependencies", "googletest_repositories", "mixerapi_dependencies", + "wasm_dependencies", ) googletest_repositories() @@ -42,6 +44,10 @@ ENVOY_SHA = "d29f7a659ba736aab97697a7bcfc69a71bc66b66" ENVOY_SHA256 = "ffc2b25af02242a95bf0e65b2f9c4ac0248fb07ade6b5bb3517be934340dfec9" +# wasm dependencies + +wasm_dependencies() + # To override with local envoy, just pass `--override_repository=envoy=/PATH/TO/ENVOY` to Bazel or # persist the option in `user.bazelrc`. http_archive( diff --git a/repositories.bzl b/repositories.bzl index 6e53428b9b6..741bf14f253 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -15,6 +15,7 @@ ################################################################################ # load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") load(":x_tools_imports.bzl", "go_x_tools_imports_repositories") GOOGLETEST = "d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0" @@ -350,6 +351,13 @@ py_proto_library( actual = "@mixerapi_git//:tcp_cluster_rewrite_config_cc_proto", ) +def wasm_dependencies(): + protobuf_dependencies() + zlib_dependencies() + proxy_wasm_cpp_sdk_dependencies() + emscripten_toolchain_dependencies() + abseil_dependencies() + def mixerapi_dependencies(): go_x_tools_imports_repositories() mixerapi_repositories() @@ -361,3 +369,128 @@ def docker_dependencies(): strip_prefix = "rules_docker-0.12.0", urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.12.0/rules_docker-v0.12.0.tar.gz"], ) + +def emscripten_toolchain_dependencies(): + BUILD = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])""" + http_archive( + name = "emscripten_toolchain", + sha256 = "4ac0f1f3de8b3f1373d435cd7e58bd94de4146e751f099732167749a229b443b", + build_file_content = BUILD, + patch_cmds = [ + "./emsdk install 1.39.6-upstream", + "./emsdk activate --embedded 1.39.6-upstream", + ], + strip_prefix = "emsdk-1.39.6", + urls = ["https://github.com/emscripten-core/emsdk/archive/1.39.6.tar.gz"], + ) + +def proxy_wasm_cpp_sdk_dependencies(): + http_archive( + name = "proxy_wasm_cpp_sdk", + sha256 = "3531281b8190ff532b730e92c1f247a2b87995f17a4fd9eaf2ebac6136fbc308", + strip_prefix = "proxy-wasm-cpp-sdk-96927d814b3ec14893b56793e122125e095654c7", + urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/archive/96927d814b3ec14893b56793e122125e095654c7.tar.gz"], + ) + +# This repository is needed to use abseil when wasm build. +# Now we can't use dependencies on external package with wasm. We should implement like `envoy_wasm_cc_binary` to utilize +# dependencies of envoy package. +def abseil_dependencies(): + http_archive( + name = "com_google_abseil", + sha256 = "2693730730247afb0e7cb2d41664ac2af3ad75c79944efd266be40ba944179b9", + strip_prefix = "abseil-cpp-06f0e767d13d4d68071c4fc51e25724e0fc8bc74", + # 2020-03-03 + urls = ["https://github.com/abseil/abseil-cpp/archive/06f0e767d13d4d68071c4fc51e25724e0fc8bc74.tar.gz"], + ) + +# The build strategy to build zlib which is depend by protobuf is not appropriate for emscripten. We should override to +# use pure build strategy for protobuf +def protobuf_dependencies(): + git_repository( + name = "com_google_protobuf", + remote = "https://github.com/protocolbuffers/protobuf", + commit = "655310ca192a6e3a050e0ca0b7084a2968072260", + ) + +# This is a dependency of protobuf. In general, the configuration which is defined in protobuf. But, we can't utilize it because +# bazel will use envoy's definition of zlib build. It will cause confusing behavior of emscripten. Specifically, emcc.py will put out +# the failure message of interpreting `libz.so.1.2.11.1-motley`. It is because of invalid prefix of the name of shared library. emcc can't +# regard it as valid shared library. So we decided to introduce the phase of zlib build directly. +def zlib_dependencies(): + ZLIB_BUILD = """ +load("@rules_cc//cc:defs.bzl", "cc_library") + +licenses(["notice"]) # BSD/MIT-like license (for zlib) + +_ZLIB_HEADERS = [ + "crc32.h", + "deflate.h", + "gzguts.h", + "inffast.h", + "inffixed.h", + "inflate.h", + "inftrees.h", + "trees.h", + "zconf.h", + "zlib.h", + "zutil.h", +] + +_ZLIB_PREFIXED_HEADERS = ["zlib/include/" + hdr for hdr in _ZLIB_HEADERS] + +# In order to limit the damage from the `includes` propagation +# via `:zlib`, copy the public headers to a subdirectory and +# expose those. +genrule( + name = "copy_public_headers", + srcs = _ZLIB_HEADERS, + outs = _ZLIB_PREFIXED_HEADERS, + cmd = "cp $(SRCS) $(@D)/zlib/include/", +) + +cc_library( + name = "zlib", + srcs = [ + "adler32.c", + "compress.c", + "crc32.c", + "deflate.c", + "gzclose.c", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inflate.c", + "inftrees.c", + "trees.c", + "uncompr.c", + "zutil.c", + # Include the un-prefixed headers in srcs to work + # around the fact that zlib isn't consistent in its + # choice of <> or "" delimiter when including itself. + ] + _ZLIB_HEADERS, + hdrs = _ZLIB_PREFIXED_HEADERS, + copts = select({ + "@bazel_tools//src/conditions:windows": [], + "//conditions:default": [ + "-Wno-unused-variable", + "-Wno-implicit-function-declaration", + ], + }), + includes = ["zlib/include/"], + visibility = ["//visibility:public"], +) +""" + + http_archive( + name = "zlib", + build_file_content = ZLIB_BUILD, + sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1", + strip_prefix = "zlib-1.2.11", + urls = [ + "https://mirror.bazel.build/zlib.net/zlib-1.2.11.tar.gz", + "https://zlib.net/zlib-1.2.11.tar.gz", + ], + ) diff --git a/src/envoy/http/authn_wasm/BUILD b/src/envoy/http/authn_wasm/BUILD new file mode 100644 index 00000000000..eb6717544d5 --- /dev/null +++ b/src/envoy/http/authn_wasm/BUILD @@ -0,0 +1,73 @@ +# Copyright 2020 Istio Authors. All Rights Reserved. +# +# Licensed 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. +# +################################################################################ +# + +package(default_visibility = ["//visibility:public"]) + +load( + "//:wasm.bzl", + "wasm_cc_binary", + "wasm_cc_library", +) + +wasm_cc_library( + name = "cert_interface", + hdrs = ["cert.h"], +) + +wasm_cc_library( + name = "connection_context_interface", + hdrs = ["connection_context.h"], + deps = [ + ":cert_interface", + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", + ], +) + +wasm_cc_library( + name = "filter_context_lib", + hdrs = ["filter_context.h"], + deps = [ + ":cert_interface", + ":connection_context_interface", + "//external:authentication_policy_config_cc_proto", + "//src/envoy/utils:filter_names_lib", + "//src/istio/authn:context_proto_cc_wasm", + "@com_google_abseil//absl/strings", + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", + ], +) + +wasm_cc_library( + name = "authn_filter_interface", + hdrs = ["filter.h"], + deps = [ + "//src/envoy/http/authn_wasm/authenticator:base", + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", + ], +) + +wasm_cc_binary( + name = "authn_wasm_binary", + srcs = ["filter.cc"], + deps = [ + ":authn_filter_interface", + ":connection_context_interface", + "//external:authentication_policy_config_cc_proto", + "//src/envoy/http/authn_wasm/authenticator:peer", + "@com_google_protobuf//:protobuf", + ], +) diff --git a/src/envoy/http/authn_wasm/authenticator/BUILD b/src/envoy/http/authn_wasm/authenticator/BUILD new file mode 100644 index 00000000000..f1d92e2780e --- /dev/null +++ b/src/envoy/http/authn_wasm/authenticator/BUILD @@ -0,0 +1,45 @@ +# Copyright 2020 Istio Authors. All Rights Reserved. +# +# Licensed 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. +# +################################################################################ +# + +package(default_visibility = ["//visibility:public"]) + +load( + "//:wasm.bzl", + "wasm_cc_library", +) + +wasm_cc_library( + name = "base", + srcs = ["base.cc"], + hdrs = ["base.h"], + deps = [ + "//external:authentication_policy_config_cc_proto", + "//src/envoy/http/authn_wasm:filter_context_lib", + "//src/istio/authn:context_proto_cc_wasm", + "@com_google_abseil//absl/strings", + ], +) + +wasm_cc_library( + name = "peer", + srcs = ["peer.cc"], + hdrs = ["peer.h"], + deps = [ + ":base", + "//external:authentication_policy_config_cc_proto", + ], +) diff --git a/src/envoy/http/authn_wasm/authenticator/peer.cc b/src/envoy/http/authn_wasm/authenticator/peer.cc index 421e38dde59..c7720f5b3fc 100644 --- a/src/envoy/http/authn_wasm/authenticator/peer.cc +++ b/src/envoy/http/authn_wasm/authenticator/peer.cc @@ -13,8 +13,6 @@ * limitations under the License. */ -#include "src/envoy/http/authn_wasm/authenticator/authenticator.h" - namespace Envoy { namespace Http { namespace Istio { diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h index 5a12038a2d7..5cce6fa188c 100644 --- a/src/envoy/http/authn_wasm/filter_context.h +++ b/src/envoy/http/authn_wasm/filter_context.h @@ -18,9 +18,9 @@ #include #include -#include +#include "absl/strings/string_view.h" -#include "istio/authn/context.pb.h" +#include "src/istio/authn/context.pb.h" #include "authentication/v1alpha1/policy.pb.h" #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" diff --git a/src/istio/authn/BUILD b/src/istio/authn/BUILD index 79f3c18011c..1b353858cd1 100644 --- a/src/istio/authn/BUILD +++ b/src/istio/authn/BUILD @@ -22,8 +22,26 @@ load( "envoy_proto_library", ) +load( + "//:wasm.bzl", + "wasm_cc_proto_library" +) + envoy_proto_library( name = "context_proto", srcs = ["context.proto"], external_deps = ["well_known_protos"], ) + +proto_library( + name = "context_proto_wasm", + srcs = ["context.proto"], + deps = [ + "@com_google_protobuf//:struct_proto", + ], +) + +wasm_cc_proto_library( + name = "context_proto_cc_wasm", + deps = ":context_proto_wasm" +) diff --git a/wasm.bzl b/wasm.bzl new file mode 100644 index 00000000000..73807c81595 --- /dev/null +++ b/wasm.bzl @@ -0,0 +1,140 @@ +# Copyright 2020 Istio Authors. All Rights Reserved. +# +# Licensed 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. +# +################################################################################ +# + +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") + +def _wasm_transition_impl(settings, attr): + return { + "//command_line_option:cpu": "wasm", + "//command_line_option:crosstool_top": "@proxy_wasm_cpp_sdk//toolchain:emscripten", + + # Overriding copt/cxxopt/linkopt to prevent sanitizers/coverage options leak + # into WASM build configuration + "//command_line_option:copt": [], + "//command_line_option:cxxopt": [], + "//command_line_option:linkopt": [], + } + +def _cc_library_wasm_transition_impl(settings, attr): + return { + # Overriding copt/cxxopt/linkopt to prevent sanitizers/coverage options leak + # into WASM build configuration + "//command_line_option:copt": [], + "//command_line_option:cxxopt": ["-std=c++17"], + "//command_line_option:linkopt": [], + } + +wasm_transition = transition( + implementation = _wasm_transition_impl, + inputs = [], + outputs = [ + "//command_line_option:cpu", + "//command_line_option:crosstool_top", + "//command_line_option:copt", + "//command_line_option:cxxopt", + "//command_line_option:linkopt", + ], +) + +cc_library_wasm_transition = transition( + implementation = _cc_library_wasm_transition_impl, + inputs = [], + outputs = [ + "//command_line_option:copt", + "//command_line_option:cxxopt", + "//command_line_option:linkopt", + ] +) + +def _wasm_binary_impl(ctx): + out = ctx.actions.declare_file(ctx.label.name) + ctx.actions.run_shell( + command = 'cp "{}" "{}"'.format(ctx.files.binary[0].path, out.path), + outputs = [out], + inputs = ctx.files.binary, + ) + + return [DefaultInfo(runfiles = ctx.runfiles([out]))] + +# WASM binary rule implementation. +# This copies the binary specified in binary attribute in WASM configuration to +# target configuration, so a binary in non-WASM configuration can depend on them. +wasm_binary = rule( + implementation = _wasm_binary_impl, + attrs = { + "binary": attr.label(mandatory = True, cfg = wasm_transition), + "_whitelist_function_transition": attr.label(default = "@bazel_tools//tools/whitelists/function_transition_whitelist"), + }, +) + +def _wasm_library_impl(ctx): + return [deps[CcInfo] for deps in ctx.attr.deps] + +wasm_library = rule( + implementation = _wasm_library_impl, + attrs = { + "deps": attr.label(mandatory = True, providers = [CcInfo, DefaultInfo], cfg = cc_library_wasm_transition), + "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), + "_whitelist_function_transition": attr.label(default = "@bazel_tools//tools/whitelists/function_transition_whitelist"), + }, + toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], +) + +def wasm_cc_binary(name, **kwargs): + wasm_name = "_wasm_" + name + kwargs.setdefault("additional_linker_inputs", ["@proxy_wasm_cpp_sdk//:jslib"]) + kwargs.setdefault("linkopts", ["--js-library external/proxy_wasm_cpp_sdk/proxy_wasm_intrinsics.js"]) + kwargs.setdefault("visibility", ["//visibility:public"]) + native.cc_binary( + name = wasm_name, + # Adding manual tag it won't be built in non-WASM (e.g. x86_64 config) + # when an wildcard is specified, but it will be built in WASM configuration + # when the wasm_binary below is built. + tags = ["manual"], + **kwargs + ) + + wasm_binary( + name = name, + binary = ":" + wasm_name, + ) + +def wasm_cc_library(name, **kwargs): + lib_name = "_lib_" + name + native.cc_library( + name = lib_name, + **kwargs + ) + + wasm_library( + name = name, + deps = ":" + lib_name + ) + +def wasm_cc_proto_library(name, deps): + proto_name = "_proto_" + name + + cc_proto_library( + name = proto_name, + deps = [deps], + ) + + wasm_library( + name = name, + deps = ":" + proto_name + ) \ No newline at end of file From fe44df0bc9f71c23622d7997576900d39173d221 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 27 May 2020 09:35:33 +0000 Subject: [PATCH 04/44] fix compile error --- src/envoy/http/authn_wasm/BUILD | 19 ++- src/envoy/http/authn_wasm/authenticator/BUILD | 1 + .../http/authn_wasm/authenticator/base.cc | 78 ++++++---- .../http/authn_wasm/authenticator/base.h | 17 +-- .../http/authn_wasm/authenticator/peer.cc | 24 ++-- .../http/authn_wasm/authenticator/peer.h | 22 +-- src/envoy/http/authn_wasm/cert.cc | 70 +++++++++ src/envoy/http/authn_wasm/cert.h | 136 ++++++++---------- .../http/authn_wasm/connection_context.cc | 35 +++++ .../http/authn_wasm/connection_context.h | 45 +++--- src/envoy/http/authn_wasm/filter.cc | 57 +++----- src/envoy/http/authn_wasm/filter.h | 75 ++++++---- src/envoy/http/authn_wasm/filter_context.cc | 35 ++--- src/envoy/http/authn_wasm/filter_context.h | 61 ++++---- src/istio/authn/BUILD | 5 +- wasm.bzl | 14 +- 16 files changed, 423 insertions(+), 271 deletions(-) create mode 100644 src/envoy/http/authn_wasm/cert.cc create mode 100644 src/envoy/http/authn_wasm/connection_context.cc diff --git a/src/envoy/http/authn_wasm/BUILD b/src/envoy/http/authn_wasm/BUILD index eb6717544d5..89344515f6a 100644 --- a/src/envoy/http/authn_wasm/BUILD +++ b/src/envoy/http/authn_wasm/BUILD @@ -24,25 +24,32 @@ load( ) wasm_cc_library( - name = "cert_interface", + name = "cert_lib", + srcs = ["cert.cc"], hdrs = ["cert.h"], + deps = [ + "@com_google_abseil//absl/strings", + "@com_google_abseil//absl/types:optional", + ], ) wasm_cc_library( - name = "connection_context_interface", + name = "connection_context_lib", + srcs = ["connection_context.cc"], hdrs = ["connection_context.h"], deps = [ - ":cert_interface", + ":cert_lib", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) wasm_cc_library( name = "filter_context_lib", + srcs = ["filter_context.cc"], hdrs = ["filter_context.h"], deps = [ - ":cert_interface", - ":connection_context_interface", + ":cert_lib", + ":connection_context_lib", "//external:authentication_policy_config_cc_proto", "//src/envoy/utils:filter_names_lib", "//src/istio/authn:context_proto_cc_wasm", @@ -65,7 +72,7 @@ wasm_cc_binary( srcs = ["filter.cc"], deps = [ ":authn_filter_interface", - ":connection_context_interface", + ":connection_context_lib", "//external:authentication_policy_config_cc_proto", "//src/envoy/http/authn_wasm/authenticator:peer", "@com_google_protobuf//:protobuf", diff --git a/src/envoy/http/authn_wasm/authenticator/BUILD b/src/envoy/http/authn_wasm/authenticator/BUILD index f1d92e2780e..57314819e42 100644 --- a/src/envoy/http/authn_wasm/authenticator/BUILD +++ b/src/envoy/http/authn_wasm/authenticator/BUILD @@ -31,6 +31,7 @@ wasm_cc_library( "//src/envoy/http/authn_wasm:filter_context_lib", "//src/istio/authn:context_proto_cc_wasm", "@com_google_abseil//absl/strings", + "@envoy//include/envoy/http:header_map_interface", ], ) diff --git a/src/envoy/http/authn_wasm/authenticator/base.cc b/src/envoy/http/authn_wasm/authenticator/base.cc index 68b9be5dab9..8ca317d2cd8 100644 --- a/src/envoy/http/authn_wasm/authenticator/base.cc +++ b/src/envoy/http/authn_wasm/authenticator/base.cc @@ -15,48 +15,71 @@ #include +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "envoy/http/header_map.h" #include "src/envoy/http/authn_wasm/authenticator/base.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { namespace { // The default header name for an exchanged token -static constexpr absl::string_view kExchangedTokenHeaderName = "ingress-authorization"; +static constexpr absl::string_view kExchangedTokenHeaderName = + "ingress-authorization"; // Returns whether the header for an exchanged token is found -bool FindHeaderOfExchangedToken(const istio::authentication::v1alpha1::Jwt& jwt) { +bool FindHeaderOfExchangedToken( + const istio::authentication::v1alpha1::Jwt& jwt) { return (jwt.jwt_headers_size() == 1 && - LowerCaseString(kExchangedTokenHeaderName) == - LowerCaseString(jwt.jwt_headers(0))); + Envoy::Http::LowerCaseString(kExchangedTokenHeaderName.data()) == + Envoy::Http::LowerCaseString(jwt.jwt_headers(0))); } } // namespace -AuthenticatorBase::AuthenticatorBase(FilterContext* filter_context) - : filter_context_(*filter_context) {} +AuthenticatorBase::AuthenticatorBase(FilterContextPtr filter_context) + : filter_context_(filter_context) {} AuthenticatorBase::~AuthenticatorBase() {} bool AuthenticatorBase::validateTrustDomain() const { - std::string peer_trust_domain = filter_context_.peerCertificateInfo()->getTrustDomain(); + const auto& conn_context = filter_context_->connectionContext(); + if (conn_context.peerCertificateInfo() == nullptr) { + logError(absl::StrCat( + "trust domain validation failed: failed to extract peer certificate ", + "info")); + return false; + } + + const auto& peer_trust_domain = + conn_context.peerCertificateInfo()->getTrustDomain(); if (!peer_trust_domain.has_value()) { logError("trust domain validation failed: cannot get peer trust domain"); return false; } - std::string local_trust_domain = filter_context_.localCertificateInfo()->getTrustDomain(); + if (conn_context.localCertificateInfo() == nullptr) { + logError( + absl::StrCat("trust domain validation failed: failed to extract local ", + "certificate info")); + return false; + } + + const auto& local_trust_domain = + conn_context.localCertificateInfo()->getTrustDomain(); if (!local_trust_domain.has_value()) { logError("trust domain validation failed: cannot get local trust domain"); return false; } if (peer_trust_domain.value() != local_trust_domain.value()) { - logError("trust domain validation failed: peer trust domain ", peer_trust_domain.value()); - logError("different from local trust domain ", local_trust_domain.value()); + logError(absl::StrCat("trust domain validation failed: peer trust domain ", + peer_trust_domain.value())); + logError(absl::StrCat("different from local trust domain ", + local_trust_domain.value())); return false; } @@ -64,23 +87,27 @@ bool AuthenticatorBase::validateTrustDomain() const { return true; } -bool AuthenticatorBase::validateX509(const istio::authentication::v1alpha1::MutualTls& mtls, - istio::authn::Payload* payload) const { +bool AuthenticatorBase::validateX509( + const istio::authentication::v1alpha1::MutualTls& mtls, + istio::authn::Payload* payload) const { bool has_user; - const bool presented = filter_context_.peerCertificateInfo() != nullptr && - filter_context_.peerCertificateInfo()->presented(); - - if (filter_context_.peerCertificateInfo() != nullptr) { - const auto principal = filter_context_.peerCertificateInfo()->getPrincipal(); + const auto& conn_context = filter_context_->connectionContext(); + const bool presented = conn_context.peerCertificateInfo() != nullptr && + conn_context.peerCertificateInfo()->presented(); + + if (conn_context.peerCertificateInfo() != nullptr) { + const auto principal = conn_context.peerCertificateInfo()->getPrincipal(); if (principal.has_value()) { - *(payload->mutable_x509()->mutable_user()) = principal.value(); + *(payload->mutable_x509()->mutable_user()) = principal.value(); } has_user = presented && principal.has_value(); } - logDebug("validateX509 mode: ", istio::authentication::v1alpha1::MutualTls::Mode_Name(mtls.mode())); - logDebug("validateX509 ssl: ", filter_context_.isTls()); - logDebug("validateX509 has_user: ", has_user); + logDebug(absl::StrCat( + "validateX509 mode: ", + istio::authentication::v1alpha1::MutualTls::Mode_Name(mtls.mode()))); + logDebug(absl::StrCat("validateX509 ssl: ", conn_context.isTls())); + logDebug(absl::StrCat("validateX509 has_user: ", has_user)); if (!has_user) { // For plaintext connection, return value depend on mode: @@ -97,7 +124,7 @@ bool AuthenticatorBase::validateX509(const istio::authentication::v1alpha1::Mutu } } - if (filter_context_.filterConfig().skip_validate_trust_domain()) { + if (filter_context_->filterConfig().skip_validate_trust_domain()) { logDebug("trust domain validation skipped"); return true; } @@ -109,11 +136,12 @@ bool AuthenticatorBase::validateX509(const istio::authentication::v1alpha1::Mutu // TODO(shikugawa): implement validateJWT bool AuthenticatorBase::validateJwt( - const istio::authentication::v1alpha1::Jwt& params, istio::authn::Payload* payload) { + const istio::authentication::v1alpha1::Jwt& params, + istio::authn::Payload* payload) { return true; } } // namespace AuthN -} // namespace Istio } // namespace Http +} // namespace Wasm } // namespace Envoy diff --git a/src/envoy/http/authn_wasm/authenticator/base.h b/src/envoy/http/authn_wasm/authenticator/base.h index 5a81842b247..8afe8f0b846 100644 --- a/src/envoy/http/authn_wasm/authenticator/base.h +++ b/src/envoy/http/authn_wasm/authenticator/base.h @@ -17,22 +17,21 @@ #include -#include "src/envoy/http/authn_wasm/filter_context.h" - #include "authentication/v1alpha1/policy.pb.h" +#include "src/envoy/http/authn_wasm/filter_context.h" #include "src/istio/authn/context.pb.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { // AuthenticatorBase is the base class for authenticator. It provides functions // to perform individual authentication methods, which can be used to construct // compound authentication flow. class AuthenticatorBase { -public: - AuthenticatorBase(FilterContext* filter_context); + public: + AuthenticatorBase(FilterContextPtr filter_context); virtual ~AuthenticatorBase(); // Perform authentication. @@ -52,14 +51,16 @@ class AuthenticatorBase { istio::authn::Payload* payload); // Mutable accessor to filter context. - FilterContextPtr& filterContext() { return filter_context_; } + FilterContextPtr filterContext() { return filter_context_; } + + private: + bool validateTrustDomain() const; -private: // Pointer to filter state. Do not own. FilterContextPtr filter_context_; }; } // namespace AuthN -} // namespace Istio } // namespace Http +} // namespace Wasm } // namespace Envoy diff --git a/src/envoy/http/authn_wasm/authenticator/peer.cc b/src/envoy/http/authn_wasm/authenticator/peer.cc index c7720f5b3fc..e3e53ba8a5b 100644 --- a/src/envoy/http/authn_wasm/authenticator/peer.cc +++ b/src/envoy/http/authn_wasm/authenticator/peer.cc @@ -13,32 +13,40 @@ * limitations under the License. */ +#include "absl/strings/str_cat.h" +#include "proxy_wasm_intrinsics.h" +#include "src/envoy/http/authn_wasm/authenticator/peer.h" + namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { -PeerAuthenticator::PeerAuthenticator(FilterContext* filter_context, - const istio::authentication::v1alpha1::Policy& policy) +PeerAuthenticator::PeerAuthenticator( + FilterContextPtr filter_context, + const istio::authentication::v1alpha1::Policy& policy) : AuthenticatorBase(filter_context), policy_(policy) {} bool PeerAuthenticator::run(istio::authn::Payload* payload) { bool success = false; if (policy_.peers_size() == 0) { - ENVOY_LOG(debug, "No method defined. Skip source authentication."); + logDebug("No method defined. Skip source authentication."); success = true; return success; } for (const auto& method : policy_.peers()) { switch (method.params_case()) { - case istio::authentication::v1alpha1::PeerAuthenticationMethod::ParamsCase::kMtls: + case istio::authentication::v1alpha1::PeerAuthenticationMethod:: + ParamsCase::kMtls: success = validateX509(method.mtls(), payload); break; - case istio::authentication::v1alpha1::PeerAuthenticationMethod::ParamsCase::kJwt: + case istio::authentication::v1alpha1::PeerAuthenticationMethod:: + ParamsCase::kJwt: success = validateJwt(method.jwt(), payload); break; default: - logError("Unknown peer authentication param ", method.DebugString()); + logError(absl::StrCat("Unknown peer authentication param ", + method.DebugString())); success = false; break; } @@ -56,6 +64,6 @@ bool PeerAuthenticator::run(istio::authn::Payload* payload) { } } // namespace AuthN -} // namespace Istio } // namespace Http +} // namespace Wasm } // namespace Envoy diff --git a/src/envoy/http/authn_wasm/authenticator/peer.h b/src/envoy/http/authn_wasm/authenticator/peer.h index cd880017cbc..3b7d2a44931 100644 --- a/src/envoy/http/authn_wasm/authenticator/peer.h +++ b/src/envoy/http/authn_wasm/authenticator/peer.h @@ -15,36 +15,38 @@ #pragma once -#include "authentication/v1alpha1/policy.pb.h" +#include + #include "src/envoy/http/authn_wasm/authenticator/base.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { // PeerAuthenticator performs peer authentication for given policy. class PeerAuthenticator : public AuthenticatorBase { -public: - using PeerAuthenticatorPtr = std::unique_ptr; - - static PeerAuthenticatorPtr create(FilterContext* filter_context) { + public: + static std::unique_ptr create( + FilterContextPtr filter_context) { return std::make_unique( - filter_context, filter_context->filterConfig().policy()); + filter_context, filter_context->filterConfig().policy()); } bool run(istio::authn::Payload*) override; -private: - PeerAuthenticator(FilterContext* filter_context, + PeerAuthenticator(FilterContextPtr filter_context, const istio::authentication::v1alpha1::Policy& policy); + private: // Reference to the authentication policy that the authenticator should // enforce. Typically, the actual object is owned by filter. const istio::authentication::v1alpha1::Policy& policy_; }; +using PeerAuthenticatorPtr = std::unique_ptr; + } // namespace AuthN -} // namespace Istio } // namespace Http +} // namespace Wasm } // namespace Envoy diff --git a/src/envoy/http/authn_wasm/cert.cc b/src/envoy/http/authn_wasm/cert.cc new file mode 100644 index 00000000000..7ec17613bbe --- /dev/null +++ b/src/envoy/http/authn_wasm/cert.cc @@ -0,0 +1,70 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn_wasm/cert.h" + +namespace Envoy { +namespace Wasm { +namespace Http { +namespace AuthN { + +template +absl::optional TlsCertificateInfo::getCertSans() { + const auto& uri_sans = static_cast(*this).uriSans(); + for (const auto& uri_san : uri_sans) { + if (absl::StartsWith(uri_san, kSPIFFEPrefix)) { + return uri_san; + } + } + if (!uri_sans.empty()) { + return uri_sans[0]; + } + return absl::nullopt; +} + +template +absl::optional TlsCertificateInfo::getPrincipal() { + const auto cert_sans = getCertSans(); + if (cert_sans.has_value()) { + if (absl::StartsWith(cert_sans, kSPIFFEPrefix)) { + return cert_sans.value().substr(kSPIFFEPrefix.size()); + } + return cert_sans.value(); + } + return absl::nullopt; +} + +template +absl::optional TlsCertificateInfo::getTrustDomain() { + const auto cert_san = getCertSans(); + if (!cert_san.has_value() || + !absl::StartsWith(cert_san.value(), kSPIFFEPrefix)) { + return absl::nullopt; + } + + // Skip the prefix "spiffe://" before getting trust domain. + size_t slash = cert_san.find('/', kSPIFFEPrefix.size()); + if (slash == std::string::npos) { + return absl::nullopt; + } + + size_t len = slash - kSPIFFEPrefix.size(); + return cert_san.substr(kSPIFFEPrefix.size(), len); +} + +} // namespace AuthN +} // namespace Http +} // namespace Wasm +} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/cert.h b/src/envoy/http/authn_wasm/cert.h index 4465125a21f..b3681de9e0f 100644 --- a/src/envoy/http/authn_wasm/cert.h +++ b/src/envoy/http/authn_wasm/cert.h @@ -16,103 +16,87 @@ #include #include +#include "absl/strings/match.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" -#include "absl/strings/match.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { constexpr absl::string_view kSPIFFEPrefix = "spiffe://"; template class TlsCertificateInfo { -public: - absl::optional getCertSans() { - const auto& uri_sans = static_cast(*this).uriSans(); - for (const auto& uri_san: uri_sans) { - if (absl::StartsWith(uri_san, kSPIFFEPrefix)) { - return uri_san; - } - } - if (!uri_sans.empty()) { - return uri_sans[0]; - } - return absl::nullopt; - } - - absl::optional getPrincipal() { - const auto cert_sans = getCertSans(); - if (cert_sans.has_value()) { - if (absl::StartsWith(cert_sans, kSPIFFEPrefix)) { - return cert_sans.value().substr(kSPIFFEPrefix.size()); - } - return cert_sans.value(); - } - return absl::nullopt; - } - - absl::optional getTrustDomain() { - const auto cert_san = getCertSans(); - if (!cert_san.has_value() || !absl::StartsWith(cert_san.value(), kSPIFFEPrefix)) { - return absl::nullopt; - } + public: + absl::optional getCertSans(); - // Skip the prefix "spiffe://" before getting trust domain. - size_t slash = cert_san.find('/', kSPIFFEPrefix.size()); - if (slash == std::string::npos) { - return absl::nullopt; - } + absl::optional getPrincipal(); - size_t len = slash - kSPIFFEPrefix.size(); - return cert_san.substr(kSPIFFEPrefix.size(), len); - } + absl::optional getTrustDomain(); }; -class TlsPeerCertificateInfo : public TlsCertificateInfo { -public: - // getter - std::string& serialNumber() { return serial_number_; } - std::string& issuer() { return issuer_; } - std::string& subject() { return subject_; } - std::string& sha256Digest() { return sha256_digest_; } - bool& validated() { return validated_; } - bool& presented() { return presented_; } - std::string& uriSans() { return uri_sans_; } - std::string& dnsSans() { return dns_sans_; } - -private: - std::string serial_number_; - std::string issuer_; - std::string subject_; - std::string sha256_digest_; - std::string uri_sans_; - std::string dns_sans_; - - bool validated_; - bool presented_; +class TlsPeerCertificateInfo + : public TlsCertificateInfo { + public: + explicit TlsPeerCertificateInfo(std::string&& serial_number, + std::string&& issuer, std::string&& subject, + std::string&& sha256_digest, + std::vector&& uri_sans, + std::vector&& dns_sans, + bool validated, bool presented) + : serial_number_(std::move(serial_number)), + issuer_(std::move(issuer)), + subject_(std::move(subject)), + sha256_digest_(std::move(sha256_digest)), + uri_sans_(std::move(uri_sans)), + dns_sans_(std::move(dns_sans)), + validated_(validated), + presented_(presented) {} + const std::string& serialNumber() const { return serial_number_; } + const std::string& issuer() const { return issuer_; } + const std::string& subject() const { return subject_; } + const std::string& sha256Digest() const { return sha256_digest_; } + const std::vector& uriSans() const { return uri_sans_; } + const std::vector& dnsSans() const { return dns_sans_; } + const bool& validated() const { return validated_; } + const bool& presented() const { return presented_; } + + private: + const std::string serial_number_; + const std::string issuer_; + const std::string subject_; + const std::string sha256_digest_; + const std::vector uri_sans_; + const std::vector dns_sans_; + const bool validated_; + const bool presented_; }; -class TlsLocalCertificateInfo : public TlsCertificateInfo { -public: - // getter - const std::string& subject() { return subject_; } - const std::vector uriSans() { return uri_sans_; } - const std::vector dnsSans() { return dns_sans_; } - -private: +class TlsLocalCertificateInfo + : public TlsCertificateInfo { + public: + explicit TlsLocalCertificateInfo(std::string&& subject, + std::vector&& uri_sans, + std::vector&& dns_sans) + : subject_(std::move(subject)), + uri_sans_(std::move(uri_sans)), + dns_sans_(std::move(dns_sans)) {} + const std::string& subject() const { return subject_; } + const std::vector& uriSans() const { return uri_sans_; } + const std::vector& dnsSans() const { return dns_sans_; } + + private: std::string subject_; - std::vector uri_sans_; - std::vector dns_sans_; + std::vector dns_sans_; }; using TlsPeerCertificateInfoPtr = std::unique_ptr; using TlsLocalCertificateInfoPtr = std::unique_ptr; -} -} -} -} \ No newline at end of file +} // namespace AuthN +} // namespace Http +} // namespace Wasm +} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/connection_context.cc b/src/envoy/http/authn_wasm/connection_context.cc new file mode 100644 index 00000000000..997e9e48d22 --- /dev/null +++ b/src/envoy/http/authn_wasm/connection_context.cc @@ -0,0 +1,35 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +namespace Envoy { +namespace Wasm { +namespace Http { +namespace AuthN { + +// ConnectionContext::ConnectionContext() { +// if (isTls()) { +// peer_cert_info_ = std::make_unique(); +// peer_cert_info_->uriSans() = getProperty({Connection, +// UriSanPeerCertificate}); local_cert_info_ = +// std::make_unique(); local_cert_info_->uriSans() +// = getProperty({Connection, UriSanLocalCertificate}); mtls_ = +// getProperty({Connection, Mtls}).value_or(false); +// } +// } + +} // namespace AuthN +} // namespace Http +} // namespace Wasm +} // namespace Envoy diff --git a/src/envoy/http/authn_wasm/connection_context.h b/src/envoy/http/authn_wasm/connection_context.h index ca68eca0f28..991e02953d1 100644 --- a/src/envoy/http/authn_wasm/connection_context.h +++ b/src/envoy/http/authn_wasm/connection_context.h @@ -16,43 +16,50 @@ #pragma once #include -#include -#include "src/envoy/http/authn_wasm/cert.h" +#include "absl/strings/string_view.h" #include "proxy_wasm_intrinsics.h" +#include "src/envoy/http/authn_wasm/cert.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { constexpr absl::string_view Connection = "connection"; constexpr absl::string_view TlsVersion = "tls_version"; constexpr absl::string_view UriSanPeerCertificate = "uri_san_peer_certificate"; -constexpr absl::string_view LocalSanPeerCertificate = "uri_san_local_certificate"; +constexpr absl::string_view LocalSanPeerCertificate = + "uri_san_local_certificate"; constexpr absl::string_view Mtls = "mtls"; class ConnectionContext { -public: - ConnectionContext() { - if (isTls()) { - peer_cert_info_ = std::make_unique(); - peer_cert_info_->uriSans() = getProperty({Connection, UriSanPeerCertificate}).value_or(""); - local_cert_info_ = std::make_unique(); - local_cert_info_->uriSans() = getProperty({Connection, UriSanLocalCertificate}).value_or(""); - mtls_ = getProperty({Connection, Mtls}).value_or(false); - } + public: + ConnectionContext(); + + bool isMtls() const { return mtls_; } + + // Regard this connection as TLS when we can extract tls version. + bool isTls() const { + return getProperty({Connection, TlsVersion}).has_value(); } - bool isMtls() { return mtls_; } - bool isTls() { return getProperty({Connection, TlsVersion}).has_value(); } -private: - std::unique_ptr peer_cert_info_; - std::unique_ptr local_cert_info_; + const TlsPeerCertificateInfoPtr& peerCertificateInfo() const { + return peer_cert_info_; + } + + const TlsLocalCertificateInfoPtr& localCertificateInfo() const { + return local_cert_info_; + } + + private: + TlsPeerCertificateInfoPtr peer_cert_info_; + TlsLocalCertificateInfoPtr local_cert_info_; + bool mtls_; }; } // namespace AuthN -} // namespace Istio } // namespace Http +} // namespace Wasm } // namespace Envoy diff --git a/src/envoy/http/authn_wasm/filter.cc b/src/envoy/http/authn_wasm/filter.cc index 8ce9f873fa2..199185a9947 100644 --- a/src/envoy/http/authn_wasm/filter.cc +++ b/src/envoy/http/authn_wasm/filter.cc @@ -13,64 +13,47 @@ * limitations under the License. */ -#include "src/envoy/http/authn_wasm/filter.h" -#include "src/envoy/http/authn_wasm/connection_context.h" -#include "src/envoy/http/authn_wasm/authenticator/peer.h" - +#include "absl/strings/str_cat.h" +#include "authentication/v1alpha1/policy.pb.h" #include "google/protobuf/text_format.h" #include "google/protobuf/util/json_util.h" - -#include "authentication/v1alpha1/policy.pb.h" +#include "src/envoy/http/authn_wasm/authenticator/peer.h" +#include "src/envoy/http/authn_wasm/connection_context.h" +#include "src/envoy/http/authn_wasm/filter.h" namespace Envoy { -namespace Extensions { namespace Wasm { +namespace Http { namespace AuthN { -Istio::AuthN::HeaderMap void unmarshalPairs(const Pairs& pairs) { - Istio::AuthN::HeaderMap header_map; - for (auto&& [key, value]: pairs) { - header_map[key] = value; - } - return header_map; -} - -bool AuthnRootContext::onConfigure(size_t) { - WasmDataPtr configuration = getConfiguration(); - google::protobuf::util::JsonParseOptions json_options; - google::protobuf::util::Status status = JsonStringToMessage(configuration->toString(), - &filter_config_, json_options); +FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { + const auto context = ConnectionContext(); + std::string metadata; - if (status != google::protobuf::util::Status::OK) { - logError("Cannot parse authentication filter config: " + configuration->toString()); - return false; + if (!getValue({"metadata"}, &metadata)) { + logError("Failed to read metadata"); + return FilterHeadersStatus::StopIteration; } - logInfo("Istio AuthN filter is started with this configuration: ", configuration->toString()); - return true; -} + const auto request_headers = getRequestHeaderPairs()->pairs(); -FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { - const auto context = ConnectionContext(); - const auto metadata = getValue({"metadata"}); filter_context_.reset( - new Istio::AuthN::FilterContext( - unmarshalPairs(getRequestHeader()->pairs()), context, metadata, filter_config_)); + new FilterContext(request_headers, context, filterConfig())); istio::authn::Payload payload; - if (PeerAuthenticator::create(filter_context_) && filter_config_.policy().peer_is_optional()) { + if (PeerAuthenticator::create(filter_context_) && + filterConfig().policy().peer_is_optional()) { logError("Peer authentication failed."); return FilterHeadersStatus::StopIteration; } // TODO(shikugawa): origin authenticator // TODO(shikugawa): save authenticate result state as dynamic metadata - return FilterHeadersStatus::Continue; } -} -} -} -} \ No newline at end of file +} // namespace AuthN +} // namespace Http +} // namespace Wasm +} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter.h b/src/envoy/http/authn_wasm/filter.h index 69c78eaba2c..621df9de9cf 100644 --- a/src/envoy/http/authn_wasm/filter.h +++ b/src/envoy/http/authn_wasm/filter.h @@ -15,16 +15,14 @@ #pragma once -#include "proxy_wasm_intrinsics.h" #include "absl/strings/string_view.h" - #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" - +#include "proxy_wasm_intrinsics.h" #include "src/envoy/http/authn_wasm/authenticator/base.h" namespace Envoy { -namespace Extensions { namespace Wasm { +namespace Http { namespace AuthN { using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; @@ -34,23 +32,26 @@ using StringView = absl::string_view; // thread. It has the same lifetime as the worker thread and acts as target for // interactions that outlives individual stream, e.g. timer, async calls. class AuthnRootContext : public RootContext { -public: + public: AuthnRootContext(uint32_t id, absl::string_view root_id) : RootContext(id, root_id) {} ~AuthnRootContext() {} // RootContext bool validateConfiguration(size_t) override { return true; } - bool onConfigure(size_t) override; + bool onConfigure(size_t) override { return true; }; bool onStart(size_t) override { return true; } void onTick() override {} void onQueueReady(uint32_t) override {} bool onDone() override { return true; } // Low level HTTP/gRPC interface. - void onHttpCallResponse(uint32_t token, uint32_t headers, size_t body_size, uint32_t trailers) override {} - void onGrpcReceiveInitialMetadata(uint32_t token, uint32_t headers) override {} - void onGrpcReceiveTrailingMetadata(uint32_t token, uint32_t trailers) override {} + void onHttpCallResponse(uint32_t token, uint32_t headers, size_t body_size, + uint32_t trailers) override {} + void onGrpcReceiveInitialMetadata(uint32_t token, uint32_t headers) override { + } + void onGrpcReceiveTrailingMetadata(uint32_t token, + uint32_t trailers) override {} void onGrpcReceive(uint32_t token, size_t body_size) override {} void onGrpcClose(uint32_t token, GrpcStatus status) override {} @@ -62,36 +63,52 @@ class AuthnRootContext : public RootContext { // Per-stream context. class AuthnContext : public Context { -public: + public: explicit AuthnContext(uint32_t id, RootContext* root) : Context(id, root) {} ~AuthnContext() = default; void onCreate() override {} - + // Context FilterStatus onNewConnection() override { return FilterStatus::Continue; } - FilterStatus onDownstreamData(size_t, bool) override { return FilterStatus::Continue; } - FilterStatus onUpstreamData(size_t, bool) override { return FilterStatus::Continue; } + FilterStatus onDownstreamData(size_t, bool) override { + return FilterStatus::Continue; + } + FilterStatus onUpstreamData(size_t, bool) override { + return FilterStatus::Continue; + } void onDownstreamConnectionClose(PeerType) override {} void onUpstreamConnectionClose(PeerType) override {} FilterHeadersStatus onRequestHeaders(uint32_t) override; - FilterMetadataStatus onRequestMetadata(uint32_t) override { return FilterMetadataStatus::Continue; } - FilterDataStatus onRequestBody(size_t, bool) override { return FilterDataStatus::Continue; } - FilterTrailersStatus onRequestTrailers(uint32_t) override { return FilterTrailersStatus::Continue; } - FilterHeadersStatus onResponseHeaders(uint32_t) override { return FilterHeadersStatus::Continue; } - FilterMetadataStatus onResponseMetadata(uint32_t) override { return FilterMetadataStatus::Continue; } - FilterDataStatus onResponseBody(size_t, bool) override { return FilterDataStatus::Continue; } - FilterTrailersStatus onResponseTrailers(uint32_t) override { return FilterTrailersStatus::Continue; } + FilterMetadataStatus onRequestMetadata(uint32_t) override { + return FilterMetadataStatus::Continue; + } + FilterDataStatus onRequestBody(size_t, bool) override { + return FilterDataStatus::Continue; + } + FilterTrailersStatus onRequestTrailers(uint32_t) override { + return FilterTrailersStatus::Continue; + } + FilterHeadersStatus onResponseHeaders(uint32_t) override { + return FilterHeadersStatus::Continue; + } + FilterMetadataStatus onResponseMetadata(uint32_t) override { + return FilterMetadataStatus::Continue; + } + FilterDataStatus onResponseBody(size_t, bool) override { + return FilterDataStatus::Continue; + } + FilterTrailersStatus onResponseTrailers(uint32_t) override { + return FilterTrailersStatus::Continue; + } void onDone() override {} void onLog() override {} - const FilterConfig& filterConfig() { - return rootContext()->filterConfig(); - }; + const FilterConfig& filterConfig() { return rootContext()->filterConfig(); }; -private: - std::unique_ptr createPeerAuthenticator( - Envoy::Http::Istio::AuthN::FilterContext* filter_context); + private: + std::unique_ptr createPeerAuthenticator( + FilterContextPtr filter_context); // TODO(shikugawa): origin authenticator implementation. // std::unique_ptr createOriginAuthenticator( // istio::AuthN::FilterContext* filter_context); @@ -102,13 +119,13 @@ class AuthnContext : public Context { // Context for authentication process. Created in decodeHeader to start // authentication process. - std::unique_ptr filter_context_; + FilterContextPtr filter_context_; }; static RegisterContextFactory register_AuthnWasm( CONTEXT_FACTORY(AuthnContext), ROOT_FACTORY(AuthnRootContext)); -} // namespace AuthnWasm +} // namespace AuthN +} // namespace Http } // namespace Wasm -} // namespace Extensions } // namespace Envoy diff --git a/src/envoy/http/authn_wasm/filter_context.cc b/src/envoy/http/authn_wasm/filter_context.cc index 5bbe0e1dc5a..b918c21c153 100644 --- a/src/envoy/http/authn_wasm/filter_context.cc +++ b/src/envoy/http/authn_wasm/filter_context.cc @@ -13,25 +13,24 @@ * limitations under the License. */ -#pragma once - -#include "src/envoy/utils/filter_names.h" +#include "absl/strings/str_cat.h" #include "src/envoy/http/authn_wasm/filter_context.h" +#include "src/envoy/utils/filter_names.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { void FilterContext::setPeerResult(const istio::authn::Payload* payload) { if (payload != nullptr) { switch (payload->payload_case()) { - case Payload::kX509: - logDebug("Set peer from X509: ", payload->x509().user()); + case istio::authn::Payload::kX509: + logDebug(absl::StrCat("Set peer from X509: ", payload->x509().user())); result_.set_peer_user(payload->x509().user()); break; - case Payload::kJwt: - logDebug("Set peer from JWT: ", payload->jwt().user()); + case istio::authn::Payload::kJwt: + logDebug(absl::StrCat("Set peer from JWT: ", payload->jwt().user())); result_.set_peer_user(payload->jwt().user()); break; default: @@ -51,26 +50,28 @@ void FilterContext::setOriginResult(const istio::authn::Payload* payload) { } } -void FilterContext::setPrincipal(const istio::authentication::v1alpha1::PrincipalBinding& binding) { +void FilterContext::setPrincipal( + const istio::authentication::v1alpha1::PrincipalBinding& binding) { switch (binding) { case istio::authentication::v1alpha1::PrincipalBinding::USE_PEER: - logDebug("Set principal from peer: ", result_.peer_user()); + logDebug(absl::StrCat("Set principal from peer: ", result_.peer_user())); result_.set_principal(result_.peer_user()); return; case istio::authentication::v1alpha1::PrincipalBinding::USE_ORIGIN: - logDebug("Set principal from origin: ", result_.origin().user()) + logDebug( + absl::StrCat("Set principal from origin: ", result_.origin().user())); result_.set_principal(result_.origin().user()); return; default: // Should never come here. - // TODO(shikugawa): add wasm logger and enable to write logging like under format. - // e.g. logDebug("Invalid binding value", binding) + // TODO(shikugawa): add wasm logger and enable to write logging like under + // format. e.g. logDebug("Invalid binding value", binding) logDebug("Invalid binding value"); return; } } -} -} -} -} \ No newline at end of file +} // namespace AuthN +} // namespace Http +} // namespace Wasm +} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h index 5cce6fa188c..8dbba860b47 100644 --- a/src/envoy/http/authn_wasm/filter_context.h +++ b/src/envoy/http/authn_wasm/filter_context.h @@ -15,42 +15,36 @@ #pragma once -#include #include +#include +#include #include "absl/strings/string_view.h" - -#include "src/istio/authn/context.pb.h" #include "authentication/v1alpha1/policy.pb.h" - #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "envoy/config/core/v3/base.pb.h" - -#include "src/envoy/http/authn_wasm/connection_context.h" -#include "src/envoy/http/authn_wasm/cert.h" - #include "proxy_wasm_intrinsics.h" +#include "src/envoy/http/authn_wasm/connection_context.h" +#include "src/istio/authn/context.pb.h" namespace Envoy { +namespace Wasm { namespace Http { -namespace Istio { namespace AuthN { -using HeaderMap = std::unordered_map; +using HeaderMap = std::vector>; // FilterContext holds inputs, such as request dynamic metadata and connection // and result data for authentication process. class FilterContext { -public: + public: FilterContext( - const HeaderMap& header_map, - const ConnectionContext& connection_context, - const envoy::config::core::v3::Metadata& dynamic_metadata, - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + const HeaderMap& header_map, const ConnectionContext& connection_context, + // const envoy::config::core::v3::Metadata& dynamic_metadata, + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filter_config) : header_map_(header_map), connection_context_(connection_context), - dynamic_metadata_(dynamic_metadata), + // dynamic_metadata_(dynamic_metadata), filter_config_(filter_config) {} // Sets peer result based on authenticated payload. Input payload can be null, @@ -70,32 +64,47 @@ class FilterContext { const istio::authn::Result& authenticationResult() { return result_; } // Accessor to the filter config - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filterConfig() const { + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + filterConfig() const { return filter_config_; } // Gets JWT payload (output from JWT filter) for given issuer. If non-empty // payload found, returns true and set the output payload string. Otherwise, // returns false. - bool getJwtPayload(const std::string& issuer, std::string* payload) const { return true; }; + bool getJwtPayload(const std::string& issuer, std::string* payload) const { + return true; + }; // Return header map. const HeaderMap& headerMap() { return header_map_; } -private: + const ConnectionContext& connectionContext() const { + return connection_context_; + } + + private: // TODO(shikugawa): JWT implementation, required metadata retrieval. // Helper function for getJwtPayload(). It gets the jwt payload from Envoy jwt // filter metadata and write to |payload|. bool getJwtPayloadFromEnvoyJwtFilter(const std::string& issuer, - std::string* payload) const { return true; }; + std::string* payload) const { + return true; + }; // Helper function for getJwtPayload(). It gets the jwt payload from Istio jwt // filter metadata and write to |payload|. bool getJwtPayloadFromIstioJwtFilter(const std::string& issuer, - std::string* payload) const { return true; }; + std::string* payload) const { + return true; + }; // Const reference to request info dynamic metadata. This provides data that // output from other filters, e.g JWT. - const envoy::config::core::v3::Metadata& dynamic_metadata_; + // TODO(shikugawa): Now we can't build this type of message into wasm. Because + // emscripten standalone mode haven't support pthread yet. Maybe this problem + // is caused by google/re2 which is used to build envoy_api. So we should + // define metadata for wasm. const envoy::config::core::v3::Metadata& + // dynamic_metadata_; // http request header const HeaderMap& header_map_; @@ -111,9 +120,9 @@ class FilterContext { filter_config_; }; -using FilterContextPtr = std::unique_ptr; +using FilterContextPtr = std::shared_ptr; } // namespace AuthN -} // namespace Istio } // namespace Http -} // namespace Envoy +} // namespace Wasm +} // namespace Envoy \ No newline at end of file diff --git a/src/istio/authn/BUILD b/src/istio/authn/BUILD index 1b353858cd1..d77a397aff0 100644 --- a/src/istio/authn/BUILD +++ b/src/istio/authn/BUILD @@ -21,10 +21,9 @@ load( "@envoy//bazel:envoy_build_system.bzl", "envoy_proto_library", ) - load( "//:wasm.bzl", - "wasm_cc_proto_library" + "wasm_cc_proto_library", ) envoy_proto_library( @@ -43,5 +42,5 @@ proto_library( wasm_cc_proto_library( name = "context_proto_cc_wasm", - deps = ":context_proto_wasm" + deps = ":context_proto_wasm", ) diff --git a/wasm.bzl b/wasm.bzl index 73807c81595..fab91310f4e 100644 --- a/wasm.bzl +++ b/wasm.bzl @@ -36,7 +36,7 @@ def _cc_library_wasm_transition_impl(settings, attr): # into WASM build configuration "//command_line_option:copt": [], "//command_line_option:cxxopt": ["-std=c++17"], - "//command_line_option:linkopt": [], + "//command_line_option:linkopt": [], } wasm_transition = transition( @@ -57,8 +57,8 @@ cc_library_wasm_transition = transition( outputs = [ "//command_line_option:copt", "//command_line_option:cxxopt", - "//command_line_option:linkopt", - ] + "//command_line_option:linkopt", + ], ) def _wasm_binary_impl(ctx): @@ -123,12 +123,12 @@ def wasm_cc_library(name, **kwargs): wasm_library( name = name, - deps = ":" + lib_name + deps = ":" + lib_name, ) def wasm_cc_proto_library(name, deps): proto_name = "_proto_" + name - + cc_proto_library( name = proto_name, deps = [deps], @@ -136,5 +136,5 @@ def wasm_cc_proto_library(name, deps): wasm_library( name = name, - deps = ":" + proto_name - ) \ No newline at end of file + deps = ":" + proto_name, + ) From 372427628c69dda6510a1cb5383c2c055b8c72c6 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Fri, 29 May 2020 13:08:11 +0000 Subject: [PATCH 05/44] integrate with new build strategy --- WORKSPACE | 6 - repositories.bzl | 133 ------------------ src/envoy/http/authn_wasm/BUILD | 73 ++++------ src/envoy/http/authn_wasm/authenticator/BUILD | 46 ------ .../authn_wasm/{authenticator => }/base.cc | 59 +++++--- .../authn_wasm/{authenticator => }/base.h | 16 ++- src/envoy/http/authn_wasm/cert.cc | 70 --------- src/envoy/http/authn_wasm/cert.h | 57 +++++++- .../http/authn_wasm/connection_context.cc | 15 +- .../http/authn_wasm/connection_context.h | 25 +++- src/envoy/http/authn_wasm/filter.cc | 26 ++-- src/envoy/http/authn_wasm/filter.h | 24 +++- src/envoy/http/authn_wasm/filter_context.cc | 37 +++-- src/envoy/http/authn_wasm/filter_context.h | 51 ++++--- .../authn_wasm/{authenticator => }/peer.cc | 26 +++- .../authn_wasm/{authenticator => }/peer.h | 23 ++- .../authn_wasm/{authenticator => }/request.cc | 21 ++- .../authn_wasm/{authenticator => }/request.h | 18 ++- wasm.bzl | 115 +-------------- 19 files changed, 306 insertions(+), 535 deletions(-) delete mode 100644 src/envoy/http/authn_wasm/authenticator/BUILD rename src/envoy/http/authn_wasm/{authenticator => }/base.cc (79%) rename src/envoy/http/authn_wasm/{authenticator => }/base.h (93%) delete mode 100644 src/envoy/http/authn_wasm/cert.cc rename src/envoy/http/authn_wasm/{authenticator => }/peer.cc (83%) rename src/envoy/http/authn_wasm/{authenticator => }/peer.h (81%) rename src/envoy/http/authn_wasm/{authenticator => }/request.cc (92%) rename src/envoy/http/authn_wasm/{authenticator => }/request.h (88%) diff --git a/WORKSPACE b/WORKSPACE index 6c478871a96..3385bed225b 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -19,13 +19,11 @@ workspace(name = "io_istio_proxy") # http_archive is not a native function since bazel 0.19 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") - load( "//:repositories.bzl", "docker_dependencies", "googletest_repositories", "mixerapi_dependencies", - "wasm_dependencies", ) googletest_repositories() @@ -49,10 +47,6 @@ ENVOY_ORG = "envoyproxy" ENVOY_REPO = "envoy-wasm" -# wasm dependencies - -wasm_dependencies() - # To override with local envoy, just pass `--override_repository=envoy=/PATH/TO/ENVOY` to Bazel or # persist the option in `user.bazelrc`. http_archive( diff --git a/repositories.bzl b/repositories.bzl index 741bf14f253..6e53428b9b6 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -15,7 +15,6 @@ ################################################################################ # load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") load(":x_tools_imports.bzl", "go_x_tools_imports_repositories") GOOGLETEST = "d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0" @@ -351,13 +350,6 @@ py_proto_library( actual = "@mixerapi_git//:tcp_cluster_rewrite_config_cc_proto", ) -def wasm_dependencies(): - protobuf_dependencies() - zlib_dependencies() - proxy_wasm_cpp_sdk_dependencies() - emscripten_toolchain_dependencies() - abseil_dependencies() - def mixerapi_dependencies(): go_x_tools_imports_repositories() mixerapi_repositories() @@ -369,128 +361,3 @@ def docker_dependencies(): strip_prefix = "rules_docker-0.12.0", urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.12.0/rules_docker-v0.12.0.tar.gz"], ) - -def emscripten_toolchain_dependencies(): - BUILD = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])""" - http_archive( - name = "emscripten_toolchain", - sha256 = "4ac0f1f3de8b3f1373d435cd7e58bd94de4146e751f099732167749a229b443b", - build_file_content = BUILD, - patch_cmds = [ - "./emsdk install 1.39.6-upstream", - "./emsdk activate --embedded 1.39.6-upstream", - ], - strip_prefix = "emsdk-1.39.6", - urls = ["https://github.com/emscripten-core/emsdk/archive/1.39.6.tar.gz"], - ) - -def proxy_wasm_cpp_sdk_dependencies(): - http_archive( - name = "proxy_wasm_cpp_sdk", - sha256 = "3531281b8190ff532b730e92c1f247a2b87995f17a4fd9eaf2ebac6136fbc308", - strip_prefix = "proxy-wasm-cpp-sdk-96927d814b3ec14893b56793e122125e095654c7", - urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/archive/96927d814b3ec14893b56793e122125e095654c7.tar.gz"], - ) - -# This repository is needed to use abseil when wasm build. -# Now we can't use dependencies on external package with wasm. We should implement like `envoy_wasm_cc_binary` to utilize -# dependencies of envoy package. -def abseil_dependencies(): - http_archive( - name = "com_google_abseil", - sha256 = "2693730730247afb0e7cb2d41664ac2af3ad75c79944efd266be40ba944179b9", - strip_prefix = "abseil-cpp-06f0e767d13d4d68071c4fc51e25724e0fc8bc74", - # 2020-03-03 - urls = ["https://github.com/abseil/abseil-cpp/archive/06f0e767d13d4d68071c4fc51e25724e0fc8bc74.tar.gz"], - ) - -# The build strategy to build zlib which is depend by protobuf is not appropriate for emscripten. We should override to -# use pure build strategy for protobuf -def protobuf_dependencies(): - git_repository( - name = "com_google_protobuf", - remote = "https://github.com/protocolbuffers/protobuf", - commit = "655310ca192a6e3a050e0ca0b7084a2968072260", - ) - -# This is a dependency of protobuf. In general, the configuration which is defined in protobuf. But, we can't utilize it because -# bazel will use envoy's definition of zlib build. It will cause confusing behavior of emscripten. Specifically, emcc.py will put out -# the failure message of interpreting `libz.so.1.2.11.1-motley`. It is because of invalid prefix of the name of shared library. emcc can't -# regard it as valid shared library. So we decided to introduce the phase of zlib build directly. -def zlib_dependencies(): - ZLIB_BUILD = """ -load("@rules_cc//cc:defs.bzl", "cc_library") - -licenses(["notice"]) # BSD/MIT-like license (for zlib) - -_ZLIB_HEADERS = [ - "crc32.h", - "deflate.h", - "gzguts.h", - "inffast.h", - "inffixed.h", - "inflate.h", - "inftrees.h", - "trees.h", - "zconf.h", - "zlib.h", - "zutil.h", -] - -_ZLIB_PREFIXED_HEADERS = ["zlib/include/" + hdr for hdr in _ZLIB_HEADERS] - -# In order to limit the damage from the `includes` propagation -# via `:zlib`, copy the public headers to a subdirectory and -# expose those. -genrule( - name = "copy_public_headers", - srcs = _ZLIB_HEADERS, - outs = _ZLIB_PREFIXED_HEADERS, - cmd = "cp $(SRCS) $(@D)/zlib/include/", -) - -cc_library( - name = "zlib", - srcs = [ - "adler32.c", - "compress.c", - "crc32.c", - "deflate.c", - "gzclose.c", - "gzlib.c", - "gzread.c", - "gzwrite.c", - "infback.c", - "inffast.c", - "inflate.c", - "inftrees.c", - "trees.c", - "uncompr.c", - "zutil.c", - # Include the un-prefixed headers in srcs to work - # around the fact that zlib isn't consistent in its - # choice of <> or "" delimiter when including itself. - ] + _ZLIB_HEADERS, - hdrs = _ZLIB_PREFIXED_HEADERS, - copts = select({ - "@bazel_tools//src/conditions:windows": [], - "//conditions:default": [ - "-Wno-unused-variable", - "-Wno-implicit-function-declaration", - ], - }), - includes = ["zlib/include/"], - visibility = ["//visibility:public"], -) -""" - - http_archive( - name = "zlib", - build_file_content = ZLIB_BUILD, - sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1", - strip_prefix = "zlib-1.2.11", - urls = [ - "https://mirror.bazel.build/zlib.net/zlib-1.2.11.tar.gz", - "https://zlib.net/zlib-1.2.11.tar.gz", - ], - ) diff --git a/src/envoy/http/authn_wasm/BUILD b/src/envoy/http/authn_wasm/BUILD index 89344515f6a..bb856d3300e 100644 --- a/src/envoy/http/authn_wasm/BUILD +++ b/src/envoy/http/authn_wasm/BUILD @@ -18,63 +18,38 @@ package(default_visibility = ["//visibility:public"]) load( - "//:wasm.bzl", - "wasm_cc_binary", - "wasm_cc_library", + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", ) - -wasm_cc_library( - name = "cert_lib", - srcs = ["cert.cc"], - hdrs = ["cert.h"], - deps = [ - "@com_google_abseil//absl/strings", - "@com_google_abseil//absl/types:optional", - ], +load( + "@envoy//bazel/wasm:wasm.bzl", + "wasm_cc_binary", ) -wasm_cc_library( - name = "connection_context_lib", - srcs = ["connection_context.cc"], - hdrs = ["connection_context.h"], - deps = [ - ":cert_lib", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", +wasm_cc_binary( + name = "authn_filter.wasm", + srcs = [ + "base.cc", + "base.h", + "cert.h", + "connection_context.cc", + "connection_context.h", + "filter.cc", + "filter.h", + "filter_context.cc", + "filter_context.h", + "peer.cc", + "peer.h", ], -) - -wasm_cc_library( - name = "filter_context_lib", - srcs = ["filter_context.cc"], - hdrs = ["filter_context.h"], + copts = ["-UNULL_PLUGIN"], deps = [ - ":cert_lib", - ":connection_context_lib", "//external:authentication_policy_config_cc_proto", "//src/envoy/utils:filter_names_lib", "//src/istio/authn:context_proto_cc_wasm", - "@com_google_abseil//absl/strings", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", - ], -) - -wasm_cc_library( - name = "authn_filter_interface", - hdrs = ["filter.h"], - deps = [ - "//src/envoy/http/authn_wasm/authenticator:base", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", - ], -) - -wasm_cc_binary( - name = "authn_wasm_binary", - srcs = ["filter.cc"], - deps = [ - ":authn_filter_interface", - ":connection_context_lib", - "//external:authentication_policy_config_cc_proto", - "//src/envoy/http/authn_wasm/authenticator:peer", + "@com_google_absl_wasm//absl/strings", + "@com_google_absl_wasm//absl/types:optional", "@com_google_protobuf//:protobuf", + # "@proxy_wasm_cpp_host//:lib", + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", ], ) diff --git a/src/envoy/http/authn_wasm/authenticator/BUILD b/src/envoy/http/authn_wasm/authenticator/BUILD deleted file mode 100644 index 57314819e42..00000000000 --- a/src/envoy/http/authn_wasm/authenticator/BUILD +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2020 Istio Authors. All Rights Reserved. -# -# Licensed 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. -# -################################################################################ -# - -package(default_visibility = ["//visibility:public"]) - -load( - "//:wasm.bzl", - "wasm_cc_library", -) - -wasm_cc_library( - name = "base", - srcs = ["base.cc"], - hdrs = ["base.h"], - deps = [ - "//external:authentication_policy_config_cc_proto", - "//src/envoy/http/authn_wasm:filter_context_lib", - "//src/istio/authn:context_proto_cc_wasm", - "@com_google_abseil//absl/strings", - "@envoy//include/envoy/http:header_map_interface", - ], -) - -wasm_cc_library( - name = "peer", - srcs = ["peer.cc"], - hdrs = ["peer.h"], - deps = [ - ":base", - "//external:authentication_policy_config_cc_proto", - ], -) diff --git a/src/envoy/http/authn_wasm/authenticator/base.cc b/src/envoy/http/authn_wasm/base.cc similarity index 79% rename from src/envoy/http/authn_wasm/authenticator/base.cc rename to src/envoy/http/authn_wasm/base.cc index 8ca317d2cd8..ae3dd704309 100644 --- a/src/envoy/http/authn_wasm/authenticator/base.cc +++ b/src/envoy/http/authn_wasm/base.cc @@ -13,30 +13,40 @@ * limitations under the License. */ +#include "src/envoy/http/authn_wasm/base.h" + #include #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "envoy/http/header_map.h" -#include "src/envoy/http/authn_wasm/authenticator/base.h" -namespace Envoy { -namespace Wasm { +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + namespace { // The default header name for an exchanged token -static constexpr absl::string_view kExchangedTokenHeaderName = - "ingress-authorization"; +// static constexpr absl::string_view kExchangedTokenHeaderName = +// "ingress-authorization"; // Returns whether the header for an exchanged token is found -bool FindHeaderOfExchangedToken( - const istio::authentication::v1alpha1::Jwt& jwt) { - return (jwt.jwt_headers_size() == 1 && - Envoy::Http::LowerCaseString(kExchangedTokenHeaderName.data()) == - Envoy::Http::LowerCaseString(jwt.jwt_headers(0))); -} +// bool FindHeaderOfExchangedToken( +// const istio::authentication::v1alpha1::Jwt& jwt) { +// return (jwt.jwt_headers_size() == 1 && +// Envoy::Http::LowerCaseString(kExchangedTokenHeaderName.data()) == +// Envoy::Http::LowerCaseString(jwt.jwt_headers(0))); +// } } // namespace @@ -48,9 +58,9 @@ AuthenticatorBase::~AuthenticatorBase() {} bool AuthenticatorBase::validateTrustDomain() const { const auto& conn_context = filter_context_->connectionContext(); if (conn_context.peerCertificateInfo() == nullptr) { - logError(absl::StrCat( - "trust domain validation failed: failed to extract peer certificate ", - "info")); + logError( + "trust domain validation failed: failed to extract peer certificate " + "info"); return false; } @@ -63,8 +73,8 @@ bool AuthenticatorBase::validateTrustDomain() const { if (conn_context.localCertificateInfo() == nullptr) { logError( - absl::StrCat("trust domain validation failed: failed to extract local ", - "certificate info")); + "trust domain validation failed: failed to extract local certificate " + "info"); return false; } @@ -76,7 +86,7 @@ bool AuthenticatorBase::validateTrustDomain() const { } if (peer_trust_domain.value() != local_trust_domain.value()) { - logError(absl::StrCat("trust domain validation failed: peer trust domain ", + logError(absl::StrCat("trust domain validation failed: peer trust domain", peer_trust_domain.value())); logError(absl::StrCat("different from local trust domain ", local_trust_domain.value())); @@ -135,13 +145,16 @@ bool AuthenticatorBase::validateX509( } // TODO(shikugawa): implement validateJWT -bool AuthenticatorBase::validateJwt( - const istio::authentication::v1alpha1::Jwt& params, - istio::authn::Payload* payload) { +bool AuthenticatorBase::validateJwt(const istio::authentication::v1alpha1::Jwt&, + istio::authn::Payload*) { return true; } +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/authenticator/base.h b/src/envoy/http/authn_wasm/base.h similarity index 93% rename from src/envoy/http/authn_wasm/authenticator/base.h rename to src/envoy/http/authn_wasm/base.h index 8afe8f0b846..3949600fda6 100644 --- a/src/envoy/http/authn_wasm/authenticator/base.h +++ b/src/envoy/http/authn_wasm/base.h @@ -21,11 +21,15 @@ #include "src/envoy/http/authn_wasm/filter_context.h" #include "src/istio/authn/context.pb.h" -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + // AuthenticatorBase is the base class for authenticator. It provides functions // to perform individual authentication methods, which can be used to construct // compound authentication flow. @@ -60,7 +64,11 @@ class AuthenticatorBase { FilterContextPtr filter_context_; }; +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/cert.cc b/src/envoy/http/authn_wasm/cert.cc deleted file mode 100644 index 7ec17613bbe..00000000000 --- a/src/envoy/http/authn_wasm/cert.cc +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "src/envoy/http/authn_wasm/cert.h" - -namespace Envoy { -namespace Wasm { -namespace Http { -namespace AuthN { - -template -absl::optional TlsCertificateInfo::getCertSans() { - const auto& uri_sans = static_cast(*this).uriSans(); - for (const auto& uri_san : uri_sans) { - if (absl::StartsWith(uri_san, kSPIFFEPrefix)) { - return uri_san; - } - } - if (!uri_sans.empty()) { - return uri_sans[0]; - } - return absl::nullopt; -} - -template -absl::optional TlsCertificateInfo::getPrincipal() { - const auto cert_sans = getCertSans(); - if (cert_sans.has_value()) { - if (absl::StartsWith(cert_sans, kSPIFFEPrefix)) { - return cert_sans.value().substr(kSPIFFEPrefix.size()); - } - return cert_sans.value(); - } - return absl::nullopt; -} - -template -absl::optional TlsCertificateInfo::getTrustDomain() { - const auto cert_san = getCertSans(); - if (!cert_san.has_value() || - !absl::StartsWith(cert_san.value(), kSPIFFEPrefix)) { - return absl::nullopt; - } - - // Skip the prefix "spiffe://" before getting trust domain. - size_t slash = cert_san.find('/', kSPIFFEPrefix.size()); - if (slash == std::string::npos) { - return absl::nullopt; - } - - size_t len = slash - kSPIFFEPrefix.size(); - return cert_san.substr(kSPIFFEPrefix.size(), len); -} - -} // namespace AuthN -} // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/cert.h b/src/envoy/http/authn_wasm/cert.h index b3681de9e0f..8519576018d 100644 --- a/src/envoy/http/authn_wasm/cert.h +++ b/src/envoy/http/authn_wasm/cert.h @@ -20,21 +20,60 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + constexpr absl::string_view kSPIFFEPrefix = "spiffe://"; template class TlsCertificateInfo { public: - absl::optional getCertSans(); + absl::optional getCertSans() { + const auto& uri_sans = static_cast(*this).uriSans(); + for (const auto& uri_san : uri_sans) { + if (absl::StartsWith(uri_san, kSPIFFEPrefix)) { + return uri_san; + } + } + if (!uri_sans.empty()) { + return uri_sans[0]; + } + return absl::nullopt; + } + + absl::optional getPrincipal() { + const auto cert_sans = getCertSans(); + if (cert_sans.has_value()) { + if (absl::StartsWith(cert_sans.value(), kSPIFFEPrefix)) { + return cert_sans.value().substr(kSPIFFEPrefix.size()); + } + return cert_sans.value(); + } + return absl::nullopt; + } - absl::optional getPrincipal(); + absl::optional getTrustDomain() { + const auto cert_san = getCertSans(); + if (!cert_san.has_value() || + !absl::StartsWith(cert_san.value(), kSPIFFEPrefix)) { + return absl::nullopt; + } - absl::optional getTrustDomain(); + // Skip the prefix "spiffe://" before getting trust domain. + size_t slash = cert_san.value().find('/', kSPIFFEPrefix.size()); + if (slash == std::string::npos) { + return absl::nullopt; + } + + size_t len = slash - kSPIFFEPrefix.size(); + return cert_san.value().substr(kSPIFFEPrefix.size(), len); + } }; class TlsPeerCertificateInfo @@ -96,7 +135,11 @@ class TlsLocalCertificateInfo using TlsPeerCertificateInfoPtr = std::unique_ptr; using TlsLocalCertificateInfoPtr = std::unique_ptr; +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/connection_context.cc b/src/envoy/http/authn_wasm/connection_context.cc index 997e9e48d22..04f87fe7540 100644 --- a/src/envoy/http/authn_wasm/connection_context.cc +++ b/src/envoy/http/authn_wasm/connection_context.cc @@ -13,11 +13,14 @@ * limitations under the License. */ -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif // ConnectionContext::ConnectionContext() { // if (isTls()) { // peer_cert_info_ = std::make_unique(); @@ -29,7 +32,11 @@ namespace AuthN { // } // } +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/connection_context.h b/src/envoy/http/authn_wasm/connection_context.h index 991e02953d1..c7e13782540 100644 --- a/src/envoy/http/authn_wasm/connection_context.h +++ b/src/envoy/http/authn_wasm/connection_context.h @@ -18,14 +18,23 @@ #include #include "absl/strings/string_view.h" -#include "proxy_wasm_intrinsics.h" #include "src/envoy/http/authn_wasm/cert.h" -namespace Envoy { -namespace Wasm { +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::getProperty; +using proxy_wasm::null_plugin::logDebug; + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + constexpr absl::string_view Connection = "connection"; constexpr absl::string_view TlsVersion = "tls_version"; constexpr absl::string_view UriSanPeerCertificate = "uri_san_peer_certificate"; @@ -35,7 +44,7 @@ constexpr absl::string_view Mtls = "mtls"; class ConnectionContext { public: - ConnectionContext(); + ConnectionContext() = default; bool isMtls() const { return mtls_; } @@ -59,7 +68,11 @@ class ConnectionContext { bool mtls_; }; +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter.cc b/src/envoy/http/authn_wasm/filter.cc index ace8bf0a8ae..0b72dd87aa4 100644 --- a/src/envoy/http/authn_wasm/filter.cc +++ b/src/envoy/http/authn_wasm/filter.cc @@ -13,21 +13,25 @@ * limitations under the License. */ +#include "src/envoy/http/authn_wasm/filter.h" + #include "absl/strings/str_cat.h" #include "authentication/v1alpha1/policy.pb.h" #include "google/protobuf/text_format.h" #include "google/protobuf/util/json_util.h" -#include "src/envoy/http/authn_wasm/authenticator/peer.h" #include "src/envoy/http/authn_wasm/connection_context.h" -#include "src/envoy/http/authn_wasm/filter.h" +#include "src/envoy/http/authn_wasm/peer.h" + +#ifdef NULL_PLUGIN -namespace Envoy { -namespace Wasm { +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { - const auto context = ConnectionContext(); std::string metadata_bytes; if (!getValue({"metadata"}, &metadata_bytes)) { @@ -39,8 +43,8 @@ FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { metadata.ParseFromString(metadata_bytes); const auto request_headers = getRequestHeaderPairs()->pairs(); - filter_context_.reset( - new FilterContext(request_headers, metadata, filterConfig())); + filter_context_.reset(new FilterContext(ConnectionContext(), request_headers, + metadata, filterConfig())); istio::authn::Payload payload; @@ -55,7 +59,11 @@ FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { return FilterHeadersStatus::Continue; } +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter.h b/src/envoy/http/authn_wasm/filter.h index 621df9de9cf..98d2c68127c 100644 --- a/src/envoy/http/authn_wasm/filter.h +++ b/src/envoy/http/authn_wasm/filter.h @@ -18,13 +18,23 @@ #include "absl/strings/string_view.h" #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" #include "proxy_wasm_intrinsics.h" -#include "src/envoy/http/authn_wasm/authenticator/base.h" +#include "src/envoy/http/authn_wasm/base.h" -namespace Envoy { -namespace Wasm { +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; using StringView = absl::string_view; @@ -125,7 +135,11 @@ class AuthnContext : public Context { static RegisterContextFactory register_AuthnWasm( CONTEXT_FACTORY(AuthnContext), ROOT_FACTORY(AuthnRootContext)); +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.cc b/src/envoy/http/authn_wasm/filter_context.cc index f426ff90f02..d7bc0cc3302 100644 --- a/src/envoy/http/authn_wasm/filter_context.cc +++ b/src/envoy/http/authn_wasm/filter_context.cc @@ -13,26 +13,29 @@ * limitations under the License. */ -#include "absl/strings/str_cat.h" #include "src/envoy/http/authn_wasm/filter_context.h" + +#include "absl/strings/str_cat.h" #include "src/envoy/utils/filter_names.h" -// #include "" -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + FilterContext::FilterContext( - const RawHeaderMap& raw_header_map, - const ConnectionContext& connection_context, + ConnectionContext&& connection_context, const RawHeaderMap& raw_header_map, const istio::authn::Metadata& dynamic_metadata, const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filter_config) - : connection_context_(connection_context), - dynamic_metadata_(dynamic_metadata), - filter_config_(filter_config) { - createHeaderMap(raw_header_map); + : connection_context_(std::move(connection_context)), + filter_config_(filter_config), + dynamic_metadata_(dynamic_metadata) { + createHeaderMap(std::move(raw_header_map)); } void FilterContext::setPeerResult(const istio::authn::Payload* payload) { @@ -84,13 +87,17 @@ void FilterContext::setPrincipal( } } -void FilterContext::createHeaderMap(RawHeaderMap& raw_header_map) { - for (const auto& header : header_map) { - header_map_[header.first] = header.second; +void FilterContext::createHeaderMap(const RawHeaderMap& raw_header_map) { + for (const auto& header : raw_header_map) { + header_map_.emplace(header.first.data(), header.second.data()); } } +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h index cc1e81e9fc7..91bff850039 100644 --- a/src/envoy/http/authn_wasm/filter_context.h +++ b/src/envoy/http/authn_wasm/filter_context.h @@ -23,28 +23,37 @@ #include "absl/strings/string_view.h" #include "authentication/v1alpha1/policy.pb.h" #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "proxy_wasm_intrinsics.h" #include "src/envoy/http/authn_wasm/connection_context.h" #include "src/istio/authn/context.pb.h" -namespace Envoy { -namespace Wasm { +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::getProperty; +using proxy_wasm::null_plugin::logDebug; + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + using RawHeaderMap = std::vector>; // TODO(shikugawa): use Envoy::Http::HeaderMapImpl. Because which is optimized // to process headers. -using HeaderMap = std::unordered_map; +using HeaderMap = std::unordered_map; // FilterContext holds inputs, such as request dynamic metadata and // connection and result data for authentication process. class FilterContext { public: FilterContext( + ConnectionContext&& connection_context, const RawHeaderMap& raw_header_map, - const ConnectionContext& connection_context, const istio::authn::Metadata& dynamic_metadata, const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filter_config); @@ -74,40 +83,32 @@ class FilterContext { // Gets JWT payload (output from JWT filter) for given issuer. If non-empty // payload found, returns true and set the output payload string. Otherwise, // returns false. - bool getJwtPayload(const std::string& issuer, std::string* payload) const { - return true; - }; + bool getJwtPayload(const std::string&, std::string*) const { return true; }; // Return header map. - const HeaderMap& HeaderMap() { return header_map_; } + const HeaderMap& requestHeader() { return header_map_; } const ConnectionContext& connectionContext() const { return connection_context_; } private: - void createHeaderMap(RawHeaderMap& raw_header_map); + void createHeaderMap(const RawHeaderMap& raw_header_map); // TODO(shikugawa): JWT implementation, required metadata retrieval. // Helper function for getJwtPayload(). It gets the jwt payload from Envoy jwt // filter metadata and write to |payload|. - bool getJwtPayloadFromEnvoyJwtFilter(const std::string& issuer, - std::string* payload) const { + bool getJwtPayloadFromEnvoyJwtFilter(const std::string&, std::string*) const { return true; }; // Helper function for getJwtPayload(). It gets the jwt payload from Istio jwt // filter metadata and write to |payload|. - bool getJwtPayloadFromIstioJwtFilter(const std::string& issuer, - std::string* payload) const { + bool getJwtPayloadFromIstioJwtFilter(const std::string&, std::string*) const { return true; }; - // Const reference to request info dynamic metadata. This provides data that - // output from other filters, e.g JWT. - const istio::authn::Metadata& dynamic_metadata_; - // http request header - HeaderMap& header_map_; + HeaderMap header_map_; // context of established connection const ConnectionContext& connection_context_; @@ -118,11 +119,19 @@ class FilterContext { // Store the Istio authn filter config. const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filter_config_; + + // Const reference to request info dynamic metadata. This provides data that + // output from other filters, e.g JWT. + const istio::authn::Metadata& dynamic_metadata_; }; using FilterContextPtr = std::shared_ptr; +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/authenticator/peer.cc b/src/envoy/http/authn_wasm/peer.cc similarity index 83% rename from src/envoy/http/authn_wasm/authenticator/peer.cc rename to src/envoy/http/authn_wasm/peer.cc index 2f08e93b5cf..89589164c65 100644 --- a/src/envoy/http/authn_wasm/authenticator/peer.cc +++ b/src/envoy/http/authn_wasm/peer.cc @@ -13,15 +13,25 @@ * limitations under the License. */ +#include "src/envoy/http/authn_wasm/peer.h" + #include "absl/strings/str_cat.h" + +#ifndef NULL_PLUGIN #include "proxy_wasm_intrinsics.h" -#include "src/envoy/http/authn_wasm/authenticator/peer.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; -namespace Envoy { -namespace Wasm { +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + PeerAuthenticator::PeerAuthenticator( FilterContextPtr filter_context, const istio::authentication::v1alpha1::Policy& policy) @@ -41,7 +51,7 @@ bool PeerAuthenticator::run(istio::authn::Payload* payload) { success = validateX509(method.mtls(), payload); break; case istio::authentication::v1alpha1::PeerAuthenticationMethod:: - ParamsCase::kJwt: // This is deprecated. + ParamsCase::kJwt: // This is deprecated. success = validateJwt(method.jwt(), payload); break; default: @@ -63,7 +73,11 @@ bool PeerAuthenticator::run(istio::authn::Payload* payload) { return success; } +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/authenticator/peer.h b/src/envoy/http/authn_wasm/peer.h similarity index 81% rename from src/envoy/http/authn_wasm/authenticator/peer.h rename to src/envoy/http/authn_wasm/peer.h index 0dfc8978f9f..3d20466e328 100644 --- a/src/envoy/http/authn_wasm/authenticator/peer.h +++ b/src/envoy/http/authn_wasm/peer.h @@ -17,13 +17,17 @@ #include -#include "src/envoy/http/authn_wasm/authenticator/base.h" +#include "src/envoy/http/authn_wasm/base.h" -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + // PeerAuthenticator performs peer authentication for given policy. // This is only to use connection level authentication. class PeerAuthenticator : public AuthenticatorBase { @@ -36,8 +40,9 @@ class PeerAuthenticator : public AuthenticatorBase { bool run(istio::authn::Payload*) override; - explicit PeerAuthenticator(FilterContextPtr filter_context, - const istio::authentication::v1alpha1::Policy& policy); + explicit PeerAuthenticator( + FilterContextPtr filter_context, + const istio::authentication::v1alpha1::Policy& policy); private: // Reference to the authentication policy that the authenticator should @@ -47,7 +52,11 @@ class PeerAuthenticator : public AuthenticatorBase { using PeerAuthenticatorPtr = std::unique_ptr; +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/authenticator/request.cc b/src/envoy/http/authn_wasm/request.cc similarity index 92% rename from src/envoy/http/authn_wasm/authenticator/request.cc rename to src/envoy/http/authn_wasm/request.cc index a0855800bcd..6efc2b7405f 100644 --- a/src/envoy/http/authn_wasm/authenticator/request.cc +++ b/src/envoy/http/authn_wasm/request.cc @@ -13,16 +13,21 @@ * limitations under the License. */ +#include "src/envoy/http/authn_wasm/request.h" + #include "absl/strings/str_cat.h" #include "authentication/v1alpha1/policy.pb.h" #include "common/http/headers.h" -#include "src/envoy/http/authn_wasm/authenticator/origin.h" -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + RequestAuthenticator::RequestAuthenticator( FilterContextPtr filter_context, const istio::authentication::v1alpha1::Policy& policy) @@ -59,11 +64,13 @@ RequestAuthenticator::run(istio::authn::Payload* payload) { logDebug("CORS preflight request allowed regardless of JWT policy"); return true; } - - } +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/authenticator/request.h b/src/envoy/http/authn_wasm/request.h similarity index 88% rename from src/envoy/http/authn_wasm/authenticator/request.h rename to src/envoy/http/authn_wasm/request.h index 3739a6a9316..27c5e5d9855 100644 --- a/src/envoy/http/authn_wasm/authenticator/request.h +++ b/src/envoy/http/authn_wasm/request.h @@ -16,13 +16,17 @@ #pragma once #include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn_wasm/authenticator/base.h" +#include "src/envoy/http/authn_wasm/base.h" -namespace Envoy { -namespace Wasm { +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { namespace Http { namespace AuthN { +#endif + // This authenticator performs to authenticate on request level. class RequestAuthenticator : public AuthenticatorBase { public: @@ -46,7 +50,11 @@ class RequestAuthenticator : public AuthenticatorBase { using RequestAuthenticatorPtr = std::unique_ptr; +#ifdef NULL_PLUGIN + } // namespace AuthN } // namespace Http -} // namespace Wasm -} // namespace Envoy \ No newline at end of file +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/wasm.bzl b/wasm.bzl index fab91310f4e..fe4e2f892dd 100644 --- a/wasm.bzl +++ b/wasm.bzl @@ -15,116 +15,7 @@ ################################################################################ # -load("@rules_proto//proto:defs.bzl", "proto_library") -load("@rules_cc//cc:defs.bzl", "cc_proto_library") - -def _wasm_transition_impl(settings, attr): - return { - "//command_line_option:cpu": "wasm", - "//command_line_option:crosstool_top": "@proxy_wasm_cpp_sdk//toolchain:emscripten", - - # Overriding copt/cxxopt/linkopt to prevent sanitizers/coverage options leak - # into WASM build configuration - "//command_line_option:copt": [], - "//command_line_option:cxxopt": [], - "//command_line_option:linkopt": [], - } - -def _cc_library_wasm_transition_impl(settings, attr): - return { - # Overriding copt/cxxopt/linkopt to prevent sanitizers/coverage options leak - # into WASM build configuration - "//command_line_option:copt": [], - "//command_line_option:cxxopt": ["-std=c++17"], - "//command_line_option:linkopt": [], - } - -wasm_transition = transition( - implementation = _wasm_transition_impl, - inputs = [], - outputs = [ - "//command_line_option:cpu", - "//command_line_option:crosstool_top", - "//command_line_option:copt", - "//command_line_option:cxxopt", - "//command_line_option:linkopt", - ], -) - -cc_library_wasm_transition = transition( - implementation = _cc_library_wasm_transition_impl, - inputs = [], - outputs = [ - "//command_line_option:copt", - "//command_line_option:cxxopt", - "//command_line_option:linkopt", - ], -) - -def _wasm_binary_impl(ctx): - out = ctx.actions.declare_file(ctx.label.name) - ctx.actions.run_shell( - command = 'cp "{}" "{}"'.format(ctx.files.binary[0].path, out.path), - outputs = [out], - inputs = ctx.files.binary, - ) - - return [DefaultInfo(runfiles = ctx.runfiles([out]))] - -# WASM binary rule implementation. -# This copies the binary specified in binary attribute in WASM configuration to -# target configuration, so a binary in non-WASM configuration can depend on them. -wasm_binary = rule( - implementation = _wasm_binary_impl, - attrs = { - "binary": attr.label(mandatory = True, cfg = wasm_transition), - "_whitelist_function_transition": attr.label(default = "@bazel_tools//tools/whitelists/function_transition_whitelist"), - }, -) - -def _wasm_library_impl(ctx): - return [deps[CcInfo] for deps in ctx.attr.deps] - -wasm_library = rule( - implementation = _wasm_library_impl, - attrs = { - "deps": attr.label(mandatory = True, providers = [CcInfo, DefaultInfo], cfg = cc_library_wasm_transition), - "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), - "_whitelist_function_transition": attr.label(default = "@bazel_tools//tools/whitelists/function_transition_whitelist"), - }, - toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], -) - -def wasm_cc_binary(name, **kwargs): - wasm_name = "_wasm_" + name - kwargs.setdefault("additional_linker_inputs", ["@proxy_wasm_cpp_sdk//:jslib"]) - kwargs.setdefault("linkopts", ["--js-library external/proxy_wasm_cpp_sdk/proxy_wasm_intrinsics.js"]) - kwargs.setdefault("visibility", ["//visibility:public"]) - native.cc_binary( - name = wasm_name, - # Adding manual tag it won't be built in non-WASM (e.g. x86_64 config) - # when an wildcard is specified, but it will be built in WASM configuration - # when the wasm_binary below is built. - tags = ["manual"], - **kwargs - ) - - wasm_binary( - name = name, - binary = ":" + wasm_name, - ) - -def wasm_cc_library(name, **kwargs): - lib_name = "_lib_" + name - native.cc_library( - name = lib_name, - **kwargs - ) - - wasm_library( - name = name, - deps = ":" + lib_name, - ) +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") def wasm_cc_proto_library(name, deps): proto_name = "_proto_" + name @@ -134,7 +25,7 @@ def wasm_cc_proto_library(name, deps): deps = [deps], ) - wasm_library( + native.cc_library( name = name, - deps = ":" + proto_name, + deps = [":" + proto_name], ) From fb933a372a889d0342f1a9eeed911e1ae468dcad Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 3 Jun 2020 09:30:05 +0000 Subject: [PATCH 06/44] tmp --- WORKSPACE | 22 +++ src/envoy/http/authn/http_filter.cc | 1 - src/envoy/http/authn_wasm/BUILD | 3 +- src/envoy/http/authn_wasm/base.cc | 162 ++++++++++++++++++-- src/envoy/http/authn_wasm/base.h | 2 +- src/envoy/http/authn_wasm/filter_context.cc | 76 ++++++++- src/envoy/http/authn_wasm/filter_context.h | 14 +- src/envoy/http/authn_wasm/json.cc | 0 src/envoy/http/authn_wasm/json.h | 43 ++++++ src/envoy/http/authn_wasm/request.cc | 41 +++++ 10 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 src/envoy/http/authn_wasm/json.cc create mode 100644 src/envoy/http/authn_wasm/json.h diff --git a/WORKSPACE b/WORKSPACE index 3385bed225b..eb4fa576955 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -144,3 +144,25 @@ http_archive( strip_prefix = "abseil-cpp-" + COM_GOOGLE_ABSL_WASM_SHA, url = "https://github.com/abseil/abseil-cpp/archive/" + COM_GOOGLE_ABSL_WASM_SHA + ".tar.gz", ) + +COM_GITHUB_TENCENT_RAPIDJSON = "dfbe1db9da455552f7a9ad5d2aea17dd9d832ac1" + +RAPIDJSON_BUILD = """ +licenses(["notice"]) # Apache 2 + +cc_library( + name = "rapidjson", + hdrs = glob(["include/rapidjson/**/*.h"]), + defines = ["RAPIDJSON_HAS_STDSTRING=1"], + includes = ["include"], + visibility = ["//visibility:public"], +) +""" + +http_archive( + name = "com_github_tencent_rapidjson", + build_file_content = RAPIDJSON_BUILD, + sha256 = "a2faafbc402394df0fa94602df4b5e4befd734aad6bb55dfef46f62fcaf1090b", + strip_prefix = "rapidjson-" + COM_GITHUB_TENCENT_RAPIDJSON, + url = "https://github.com/Tencent/rapidjson/archive/" + COM_GITHUB_TENCENT_RAPIDJSON + ".tar.gz", +) diff --git a/src/envoy/http/authn/http_filter.cc b/src/envoy/http/authn/http_filter.cc index c2237e8ea43..59333bea507 100644 --- a/src/envoy/http/authn/http_filter.cc +++ b/src/envoy/http/authn/http_filter.cc @@ -59,7 +59,6 @@ FilterHeadersStatus AuthenticationFilter::decodeHeaders( decoder_callbacks_->streamInfo().dynamicMetadata(), headers, decoder_callbacks_->connection(), filter_config_)); - std::cout << decoder_callbacks_->streamInfo().dynamicMetadata().DebugString() << std::endl; Payload payload; if (!createPeerAuthenticator(filter_context_.get())->run(&payload) && diff --git a/src/envoy/http/authn_wasm/BUILD b/src/envoy/http/authn_wasm/BUILD index bb856d3300e..95488da9752 100644 --- a/src/envoy/http/authn_wasm/BUILD +++ b/src/envoy/http/authn_wasm/BUILD @@ -44,12 +44,13 @@ wasm_cc_binary( copts = ["-UNULL_PLUGIN"], deps = [ "//external:authentication_policy_config_cc_proto", + "//external:com_github_tencent_rapidjson", "//src/envoy/utils:filter_names_lib", "//src/istio/authn:context_proto_cc_wasm", "@com_google_absl_wasm//absl/strings", "@com_google_absl_wasm//absl/types:optional", "@com_google_protobuf//:protobuf", - # "@proxy_wasm_cpp_host//:lib", + "@envoy//source/common/json:json_loader_lib", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", ], ) diff --git a/src/envoy/http/authn_wasm/base.cc b/src/envoy/http/authn_wasm/base.cc index ae3dd704309..c41c430c5c3 100644 --- a/src/envoy/http/authn_wasm/base.cc +++ b/src/envoy/http/authn_wasm/base.cc @@ -19,6 +19,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "common/json/json_loader.h" #ifndef NULL_PLUGIN #include "proxy_wasm_intrinsics.h" @@ -37,16 +38,103 @@ namespace AuthN { namespace { // The default header name for an exchanged token -// static constexpr absl::string_view kExchangedTokenHeaderName = -// "ingress-authorization"; +static constexpr absl::string_view kExchangedTokenHeaderName = + "ingress-authorization"; // Returns whether the header for an exchanged token is found -// bool FindHeaderOfExchangedToken( -// const istio::authentication::v1alpha1::Jwt& jwt) { -// return (jwt.jwt_headers_size() == 1 && -// Envoy::Http::LowerCaseString(kExchangedTokenHeaderName.data()) == -// Envoy::Http::LowerCaseString(jwt.jwt_headers(0))); -// } +bool FindHeaderOfExchangedToken( + const istio::authentication::v1alpha1::Jwt& jwt) { + return (jwt.jwt_headers_size() == 1 && + Envoy::Http::LowerCaseString(kExchangedTokenHeaderName.data()) == + Envoy::Http::LowerCaseString(jwt.jwt_headers(0))); +} + +// The JWT audience key name +static constexpr absl::string_view kJwtAudienceKey = "aud"; +// The JWT issuer key name +static constexpr absl::string_view kJwtIssuerKey = "iss"; +// The key name for the original claims in an exchanged token +static constexpr absl::string_view kExchangedTokenOriginalPayload = + "original_claims"; + +bool ExtractOriginalPayload(const std::string& token, + std::string* original_payload) { + Envoy::Json::ObjectSharedPtr json_obj; + try { + json_obj = Json::Factory::loadFromString(token); + } catch (...) { + return false; + } + + if (json_obj->hasObject(kExchangedTokenOriginalPayload) == false) { + return false; + } + + Envoy::Json::ObjectSharedPtr original_payload_obj; + try { + auto original_payload_obj = + json_obj->getObject(kExchangedTokenOriginalPayload); + *original_payload = original_payload_obj->asJsonString(); + logDebug(absl::StrCat(__FUNCTION__, + ": the original payload in exchanged token is ", + *original_payload)); + } catch (...) { + logDebug(absl::StrCat( + __FUNCTION__, + ": original_payload in exchanged token is of invalid format.")); + return false; + } + + return true; +} + +bool ProcessJwtPayload(const std::string& payload_str, + istio::authn::JwtPayload* payload) { + Envoy::Json::ObjectSharedPtr json_obj; + try { + json_obj = Json::Factory::loadFromString(payload_str); + ENVOY_LOG(debug, "{}: json object is {}", __FUNCTION__, + json_obj->asJsonString()); + } catch (...) { + return false; + } + + *payload->mutable_raw_claims() = payload_str; + + auto claims = payload->mutable_claims()->mutable_fields(); + // Extract claims as string lists + json_obj->iterate([json_obj, claims](const std::string& key, + const Json::Object&) -> bool { + // In current implementation, only string/string list objects are extracted + std::vector list; + ExtractStringList(key, *json_obj, &list); + for (auto s : list) { + (*claims)[key].mutable_list_value()->add_values()->set_string_value(s); + } + return true; + }); + // Copy audience to the audience in context.proto + if (claims->find(kJwtAudienceKey) != claims->end()) { + for (const auto& v : (*claims)[kJwtAudienceKey].list_value().values()) { + payload->add_audiences(v.string_value()); + } + } + + // Build user + if (claims->find(kJwtIssuerKey) != claims->end() && + claims->find("sub") != claims->end()) { + payload->set_user( + (*claims)[kJwtIssuerKey].list_value().values().Get(0).string_value() + + "/" + (*claims)["sub"].list_value().values().Get(0).string_value()); + } + // Build authorized presenter (azp) + if (claims->find("azp") != claims->end()) { + payload->set_presenter( + (*claims)["azp"].list_value().values().Get(0).string_value()); + } + + return true; +} } // namespace @@ -141,12 +229,62 @@ bool AuthenticatorBase::validateX509( // For TLS connection with valid certificate, validate trust domain for both // PERMISSIVE and STRICT mode. - return validateTrustDomain(); + return validateTrustDomain(conn_context); } -// TODO(shikugawa): implement validateJWT -bool AuthenticatorBase::validateJwt(const istio::authentication::v1alpha1::Jwt&, - istio::authn::Payload*) { +bool AuthenticatorBase::validateJwt( + const istio::authentication::v1alpha1::Jwt& jwt, + istio::authn::Payload* payload) { + auto jwt_payload = filterContext()->getJwtPayload(jwt.issuer()); + + if (jwt_payload.has_value()) { + std::string payload_to_process = jwt_payload; + std::string original_payload; + + if (FindHeaderOfExchangedToken(jwt)) { + if (ExtractOriginalPayload(jwt_payload, &original_payload)) { + // When the header of an exchanged token is found and the token + // contains the claim of the original payload, the original payload + // is extracted and used as the token payload. + payload_to_process = original_payload; + } else { + // When the header of an exchanged token is found but the token + // does not contain the claim of the original payload, it + // is regarded as an invalid exchanged token. + logError(absl::StrCat( + "Expect exchanged-token with original payload claim. Received: ", + jwt_payload)); + return false; + } + } + ProcessJwtPayload(payload_to_process, payload->mutable_jwt()); + } + return false; +} + +bool AuthenticatorBase::validateTrustDomain( + const ConnectionContext& connection) const { + auto peer_trust_domain = connection.peerCertificateInfo()->getTrustDomain(); + if (!peer_trust_domain.has_value()) { + logError("trust domain validation failed: cannot get peer trust domain"); + return false; + } + + auto local_trust_domain = connection.localCertificateInfo()->getTrustDomain(); + if (!local_trust_domain.has_value()) { + logError("trust domain validation failed: cannot get local trust domain"); + return false; + } + + if (peer_trust_domain.value() != local_trust_domain.value()) { + logError(absl::StrCat("trust domain validation failed: peer trust domain ", + peer_trust_domain.value(), + " different from local trust domain ", + local_trust_domain.value())); + return false; + } + + logDebug("trust domain validation succeeded"); return true; } diff --git a/src/envoy/http/authn_wasm/base.h b/src/envoy/http/authn_wasm/base.h index 3949600fda6..6d8296e7ab8 100644 --- a/src/envoy/http/authn_wasm/base.h +++ b/src/envoy/http/authn_wasm/base.h @@ -58,7 +58,7 @@ class AuthenticatorBase { FilterContextPtr filterContext() { return filter_context_; } private: - bool validateTrustDomain() const; + bool validateTrustDomain(const ConnectionContext& connection) const; // Pointer to filter state. Do not own. FilterContextPtr filter_context_; diff --git a/src/envoy/http/authn_wasm/filter_context.cc b/src/envoy/http/authn_wasm/filter_context.cc index d7bc0cc3302..e31210dd452 100644 --- a/src/envoy/http/authn_wasm/filter_context.cc +++ b/src/envoy/http/authn_wasm/filter_context.cc @@ -15,6 +15,8 @@ #include "src/envoy/http/authn_wasm/filter_context.h" +#include + #include "absl/strings/str_cat.h" #include "src/envoy/utils/filter_names.h" @@ -80,8 +82,6 @@ void FilterContext::setPrincipal( return; default: // Should never come here. - // TODO(shikugawa): add wasm logger and enable to write logging like under - // format. e.g. logDebug("Invalid binding value", binding) logDebug("Invalid binding value"); return; } @@ -93,6 +93,78 @@ void FilterContext::createHeaderMap(const RawHeaderMap& raw_header_map) { } } +absl::optional getJwtPayload(const std::string& issuer) const { + auto jwt_payload = getJwtPayloadFromEnvoyJwtFilter(issuer); + if (jwt_payload.has_value()) { + return jwt_payload; + } + + jwt_payload = getJwtPayloadFromIstioJwtFilter(issuer); + if (jwt_payload.has_value()) { + return jwt_payload; + } + + return absl::nullopt; +} + +absl::optional FilterContext::getJwtPayloadFromEnvoyJwtFilter( + const std::string& issuer) const { + // Try getting the Jwt payload from Envoy jwt_authn filter. + auto filter_it = dynamic_metadata_.filter_metadata().find( + Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn); + if (filter_it == dynamic_metadata_.filter_metadata().end()) { + logDebug("No dynamic_metadata found for filter ", + Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn); + return absl::nullopt; + } + + const auto& data_struct = filter_it->second; + + const auto entry_it = data_struct.fields().find(issuer); + if (entry_it == data_struct.fields().end()) { + return absl::nullopt; + } + + if (entry_it->second.struct_value().fields().empty()) { + return absl::nullopt; + } + + std::string payload; + // Serialize the payload from Envoy jwt filter first before writing it to + // |payload|. + // TODO (pitlv2109): Return protobuf Struct instead of string, once Istio jwt + // filter is removed. Also need to change how Istio authn filter processes the + // jwt payload. + Protobuf::util::MessageToJsonString(entry_it->second.struct_value(), + &payload); + return payload; +} + +absl::optional FilterContext::getJwtPayloadFromIstioJwtFilter( + const std::string& issuer) const { + // Try getting the Jwt payload from Istio jwt-auth filter. + auto filter_it = + dynamic_metadata_.filter_metadata().find(Utils::IstioFilterName::kJwt); + if (filter_it == dynamic_metadata_.filter_metadata().end()) { + logDebug("No dynamic_metadata found for filter ", + Utils::IstioFilterName::kJwt); + return absl::nullopt; + } + + const auto& data_struct = filter_it->second; + + const auto entry_it = data_struct.fields().find(issuer); + if (entry_it == data_struct.fields().end()) { + return absl::nullopt; + } + + if (entry_it->second.string_value().empty()) { + return absl::nullopt; + } + + return entry_it->second.string_value(); +} + #ifdef NULL_PLUGIN } // namespace AuthN diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h index 91bff850039..ec49aa21254 100644 --- a/src/envoy/http/authn_wasm/filter_context.h +++ b/src/envoy/http/authn_wasm/filter_context.h @@ -83,7 +83,7 @@ class FilterContext { // Gets JWT payload (output from JWT filter) for given issuer. If non-empty // payload found, returns true and set the output payload string. Otherwise, // returns false. - bool getJwtPayload(const std::string&, std::string*) const { return true; }; + absl::optional getJwtPayload(const std::string& issuer) const; // Return header map. const HeaderMap& requestHeader() { return header_map_; } @@ -95,17 +95,15 @@ class FilterContext { private: void createHeaderMap(const RawHeaderMap& raw_header_map); - // TODO(shikugawa): JWT implementation, required metadata retrieval. // Helper function for getJwtPayload(). It gets the jwt payload from Envoy jwt // filter metadata and write to |payload|. - bool getJwtPayloadFromEnvoyJwtFilter(const std::string&, std::string*) const { - return true; - }; + absl::optional getJwtPayloadFromEnvoyJwtFilter( + const std::string& issuer) const; + // Helper function for getJwtPayload(). It gets the jwt payload from Istio jwt // filter metadata and write to |payload|. - bool getJwtPayloadFromIstioJwtFilter(const std::string&, std::string*) const { - return true; - }; + absl::optional getJwtPayloadFromIstioJwtFilter( + const std::string& issuer) const; // http request header HeaderMap header_map_; diff --git a/src/envoy/http/authn_wasm/json.cc b/src/envoy/http/authn_wasm/json.cc new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/envoy/http/authn_wasm/json.h b/src/envoy/http/authn_wasm/json.h new file mode 100644 index 00000000000..2ed9e612261 --- /dev/null +++ b/src/envoy/http/authn_wasm/json.h @@ -0,0 +1,43 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; + +namespace proxy_wasm { +namespace null_plugin { +namespace Http { +namespace AuthN { + +#endif + +class JsonObject { + public: + JsonObject(std::string& json_str); +}; + +#ifdef NULL_PLUGIN + +} // namespace AuthN +} // namespace Http +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/request.cc b/src/envoy/http/authn_wasm/request.cc index 6efc2b7405f..17445363f88 100644 --- a/src/envoy/http/authn_wasm/request.cc +++ b/src/envoy/http/authn_wasm/request.cc @@ -64,6 +64,47 @@ RequestAuthenticator::run(istio::authn::Payload* payload) { logDebug("CORS preflight request allowed regardless of JWT policy"); return true; } + + absl::string_view path; + if (filterContext()->headerMap().find(":path") != filterContext()->headerMap().end()) { + path = filterContext()->headerMap().at(":path"); + + size_t offset = path.find_first_of("?#"); + if (offset != absl::string_view::npos) { + path.remove_suffix(path.length() - offset); + } + logTrace(absl::StrCat("Got request path {}", path)); + } else { + logError(absl::StrCat("Failed to get request path, JWT will always be used for validation")); + } + + bool triggered = false; + bool triggered_success = false; + for (const auto& method : policy_.origins()) { + const auto& jwt = method.jwt(); + + if (AuthnUtils::ShouldValidateJwtPerPath(path, jwt)) { + logDebug("Validating request path ", path, " for jwt ", jwt.DebugString()); + // set triggered to true if any of the jwt trigger rule matched. + triggered = true; + if (validateJwt(jwt, payload)) { + ENVOY_LOG(debug, "JWT validation succeeded"); + triggered_success = true; + break; + } + } + } + + // returns true if no jwt was triggered, or triggered and success. + if (!triggered || triggered_success) { + filterContext()->setOriginResult(payload); + filterContext()->setPrincipal(policy_.principal_binding()); + logDebug("Origin authenticator succeeded"); + return true; + } + + logDebug("Origin authenticator failed"); + return false; } #ifdef NULL_PLUGIN From 04d88bd2a3fcf4ec83bc9248296f8488a7459b1c Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sun, 7 Jun 2020 08:28:31 +0000 Subject: [PATCH 07/44] tmp --- src/envoy/http/authn_wasm/BUILD | 2 - src/envoy/http/authn_wasm/base.cc | 1 - src/envoy/http/jwt_auth/BUILD | 2 +- src/envoy/http/jwt_auth/jwt.cc | 299 ++++++++++++------ src/envoy/http/jwt_auth/jwt.h | 9 +- .../http/jwt_auth/jwt_authenticator_test.cc | 1 - src/envoy/http/jwt_auth/jwt_test.cc | 10 +- 7 files changed, 219 insertions(+), 105 deletions(-) diff --git a/src/envoy/http/authn_wasm/BUILD b/src/envoy/http/authn_wasm/BUILD index 95488da9752..49557a4671f 100644 --- a/src/envoy/http/authn_wasm/BUILD +++ b/src/envoy/http/authn_wasm/BUILD @@ -44,13 +44,11 @@ wasm_cc_binary( copts = ["-UNULL_PLUGIN"], deps = [ "//external:authentication_policy_config_cc_proto", - "//external:com_github_tencent_rapidjson", "//src/envoy/utils:filter_names_lib", "//src/istio/authn:context_proto_cc_wasm", "@com_google_absl_wasm//absl/strings", "@com_google_absl_wasm//absl/types:optional", "@com_google_protobuf//:protobuf", - "@envoy//source/common/json:json_loader_lib", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", ], ) diff --git a/src/envoy/http/authn_wasm/base.cc b/src/envoy/http/authn_wasm/base.cc index c41c430c5c3..ded1a31c111 100644 --- a/src/envoy/http/authn_wasm/base.cc +++ b/src/envoy/http/authn_wasm/base.cc @@ -19,7 +19,6 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "common/json/json_loader.h" #ifndef NULL_PLUGIN #include "proxy_wasm_intrinsics.h" diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index fcc3c1ad918..6d7f9a6b7b2 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -29,11 +29,11 @@ envoy_cc_library( srcs = ["jwt.cc"], hdrs = ["jwt.h"], external_deps = [ - "rapidjson", "ssl", ], repository = "@envoy", deps = [ + "@com_google_protobuf//:protobuf", "@envoy//source/exe:envoy_common_lib", ], ) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 0dec1668c2c..b9bddd63be1 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -20,13 +20,14 @@ #include #include #include +#include #include #include #include "common/common/assert.h" #include "common/common/base64.h" #include "common/common/utility.h" -#include "common/json/json_loader.h" +#include "google/protobuf/util/json_util.h" #include "openssl/bn.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" @@ -237,6 +238,75 @@ class EvpPkeyGetter : public WithStatus { } }; +template +absl::optional> getProtoListValue(const ProtobufMapType &, + std::string) { + static_assert(true, "Unsupported Type"); +} + +template <> +absl::optional> getProtoListValue( + const ProtobufMapType &struct_value, std::string key) { + const auto field_iter = struct_value.find(key); + if (field_iter == struct_value.end()) { + return absl::nullopt; + } + std::vector list_values; + for (const auto &value : field_iter->second.list_value().values()) { + if (value.kind_case() != google::protobuf::Value::KindCase::kStringValue) { + return absl::nullopt; + } + list_values.emplace_back(value.string_value()); + } + return list_values; +} + +template <> +absl::optional> getProtoListValue( + const ProtobufMapType &struct_value, std::string key) { + const auto field_iter = struct_value.find(key); + if (field_iter == struct_value.end()) { + return absl::nullopt; + } + std::vector list_values; + for (const auto &value : field_iter->second.list_value().values()) { + if (value.kind_case() != google::protobuf::Value::KindCase::kStructValue) { + return absl::nullopt; + } + list_values.emplace_back(value.struct_value()); + } + return list_values; +} + +template +absl::optional getProtoMapValue(const ProtobufMapType &, std::string) { + static_assert(true, "Unsupported Type"); +} + +template <> +absl::optional getProtoMapValue( + const ProtobufMapType &struct_value, std::string key) { + const auto field_iter = struct_value.find(key); + if (field_iter == struct_value.end() || + field_iter->second.kind_case() != + google::protobuf::Value::KindCase::kStringValue) { + return absl::nullopt; + } + return field_iter->second.string_value(); +} + +template <> +absl::optional getProtoMapValue(const ProtobufMapType &struct_value, + std::string key) { + const auto field_iter = struct_value.find(key); + if (field_iter == struct_value.end() || + field_iter->second.kind_case() != + google::protobuf::Value::KindCase::kNumberValue) { + return absl::nullopt; + } + return field_iter->second.number_value(); +} + } // namespace Jwt::Jwt(const std::string &jwt) { @@ -254,25 +324,30 @@ Jwt::Jwt(const std::string &jwt) { // Parse header json header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); header_str_ = Base64UrlDecode(header_str_base64url_); - try { - header_ = Json::Factory::loadFromString(header_str_); - } catch (Json::Exception &e) { + + auto status = + google::protobuf::util::JsonStringToMessage(header_str_, &header_); + if (!status.ok()) { UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); return; } // Header should contain "alg". - if (!header_->hasObject("alg")) { + const auto header_fields = header_.fields(); + const auto alg_field_iter = header_fields.find("alg"); + + if (alg_field_iter == header_fields.end()) { UpdateStatus(Status::JWT_HEADER_NO_ALG); return; } - try { - alg_ = header_->getString("alg"); - } catch (Json::Exception &e) { + + if (alg_field_iter->second.kind_case() != + google::protobuf::Value::kStringValue) { UpdateStatus(Status::JWT_HEADER_BAD_ALG); return; } + alg_ = alg_field_iter->second.string_value(); if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && alg_ != "RS512") { UpdateStatus(Status::ALG_NOT_IMPLEMENTED); @@ -280,41 +355,41 @@ Jwt::Jwt(const std::string &jwt) { } // Header may contain "kid", which should be a string if exists. - try { - kid_ = header_->getString("kid", ""); - } catch (Json::Exception &e) { + const auto kid_iter = header_fields.find("kid"); + if (kid_iter != header_fields.end() && + kid_iter->second.kind_case() != google::protobuf::Value::kStringValue) { UpdateStatus(Status::JWT_HEADER_BAD_KID); return; } + kid_ = kid_iter != header_fields.end() ? kid_iter->second.string_value() : ""; + // Parse payload json payload_str_base64url_ = std::string(jwt_split[1].begin(), jwt_split[1].end()); payload_str_ = Base64UrlDecode(payload_str_base64url_); - try { - payload_ = Json::Factory::loadFromString(payload_str_); - } catch (Json::Exception &e) { + + status = google::protobuf::util::JsonStringToMessage(payload_str_, &payload_); + + if (!status.ok()) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; } - iss_ = payload_->getString("iss", ""); - sub_ = payload_->getString("sub", ""); - exp_ = payload_->getInteger("exp", 0); + const auto payload_fields = payload_.fields(); + + iss_ = getProtoMapValue(payload_fields, "iss").value_or(""); + sub_ = getProtoMapValue(payload_fields, "sub").value_or(""); + exp_ = getProtoMapValue(payload_fields, "exp").value_or(0); // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. - try { - aud_ = payload_->getStringArray("aud", true); - } catch (Json::Exception &e) { - // Try as string - try { - auto audience = payload_->getString("aud"); - aud_.push_back(audience); - } catch (Json::Exception &e) { - UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); - return; - } + auto actual_list_aud = getProtoListValue(payload_fields, "aud"); + if (actual_list_aud.has_value()) { + aud_ = actual_list_aud.value(); + } else { + auto actual_str_aud = getProtoMapValue(payload_fields, "aud"); + if (actual_str_aud.has_value()) aud_.emplace_back(actual_str_aud.value()); } // Set up signature @@ -441,7 +516,7 @@ bool Verifier::Verify(const Jwt &jwt, const Pubkeys &pubkeys) { } // Returns the parsed header. -Json::ObjectSharedPtr Jwt::Header() { return header_; } +google::protobuf::Struct &Jwt::Header() { return header_; } const std::string &Jwt::HeaderStr() { return header_str_; } const std::string &Jwt::HeaderStrBase64Url() { return header_str_base64url_; } @@ -449,7 +524,7 @@ const std::string &Jwt::Alg() { return alg_; } const std::string &Jwt::Kid() { return kid_; } // Returns payload JSON. -Json::ObjectSharedPtr Jwt::Payload() { return payload_; } +google::protobuf::Struct &Jwt::Payload() { return payload_; } const std::string &Jwt::PayloadStr() { return payload_str_; } const std::string &Jwt::PayloadStrBase64Url() { return payload_str_base64url_; } @@ -466,36 +541,38 @@ void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { key_ptr->pem_format_ = true; UpdateStatus(e.GetStatus()); if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(key_ptr)); + keys_.emplace_back(std::move(key_ptr)); } } void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { keys_.clear(); - Json::ObjectSharedPtr jwks_json; - try { - jwks_json = Json::Factory::loadFromString(pkey_jwks); - } catch (Json::Exception &e) { + google::protobuf::Struct jwks_object; + auto status = + google::protobuf::util::JsonStringToMessage(pkey_jwks, &jwks_object); + if (!status.ok()) { UpdateStatus(Status::JWK_PARSE_ERROR); return; } - std::vector keys; - if (!jwks_json->hasObject("keys")) { + + const auto jwks_field = jwks_object.fields(); + const auto keys_iter = jwks_field.find("keys"); + + if (keys_iter == jwks_field.end()) { UpdateStatus(Status::JWK_NO_KEYS); return; } - try { - keys = jwks_json->getObjectArray("keys", true); - } catch (Json::Exception &e) { + + auto actual_keys = + getProtoListValue(jwks_field, "keys"); + if (!actual_keys.has_value()) { UpdateStatus(Status::JWK_BAD_KEYS); return; } - for (auto jwk_json : keys) { - try { - ExtractPubkeyFromJwk(jwk_json); - } catch (Json::Exception &e) { + for (const auto &jwk_field : actual_keys.value()) { + if (!ExtractPubkeyFromJwk(jwk_field.fields())) { continue; } } @@ -505,87 +582,119 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { } } -void Pubkeys::ExtractPubkeyFromJwk(Json::ObjectSharedPtr jwk_json) { +bool Pubkeys::ExtractPubkeyFromJwk(const ProtobufMapType &jwk_field) { // Check "kty" parameter, it should exist. // https://tools.ietf.org/html/rfc7517#section-4.1 // If "kty" is missing, getString throws an exception. - std::string kty = jwk_json->getString("kty"); + auto kty = getProtoMapValue(jwk_field, "kty"); + if (!kty.has_value()) { + return false; + } // Extract public key according to "kty" value. // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty == "EC") { - ExtractPubkeyFromJwkEC(jwk_json); - } else if (kty == "RSA") { - ExtractPubkeyFromJwkRSA(jwk_json); + if (kty.value() == "EC") { + return ExtractPubkeyFromJwkEC(jwk_field); + } else if (kty.value() == "RSA") { + return ExtractPubkeyFromJwkRSA(jwk_field); } + + return false; } -void Pubkeys::ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json) { +bool Pubkeys::ExtractPubkeyFromJwkRSA(const ProtobufMapType &jwk_field) { std::unique_ptr pubkey(new Pubkey()); - std::string n_str, e_str; - try { - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_json->hasObject("kid")) { - pubkey->kid_ = jwk_json->getString("kid"); - pubkey->kid_specified_ = true; + + // "kid" and "alg" are optional, if they do not exist, set them to "". + // https://tools.ietf.org/html/rfc7517#page-8 + if (jwk_field.find("kid") != jwk_field.end()) { + auto actual_kid = getProtoMapValue(jwk_field, "kid"); + if (!actual_kid.has_value()) { + return false; } - if (jwk_json->hasObject("alg")) { - pubkey->alg_ = jwk_json->getString("alg"); - if (pubkey->alg_.compare(0, 2, "RS") != 0) { - return; - } - pubkey->alg_specified_ = true; + pubkey->kid_ = actual_kid.value(); + pubkey->kid_specified_ = true; + } + + if (jwk_field.find("alg") != jwk_field.end()) { + auto actual_alg = getProtoMapValue(jwk_field, "alg"); + // Allow only "RS" prefixed algorithms. + // https://tools.ietf.org/html/rfc7518#section-3.1 + if (!actual_alg.has_value() || + !(actual_alg.value() == "RS256" || actual_alg.value() == "RS384" || + actual_alg.value() == "RS512")) { + return false; } - pubkey->kty_ = jwk_json->getString("kty"); - n_str = jwk_json->getString("n"); - e_str = jwk_json->getString("e"); - } catch (Json::Exception &e) { - // Do not extract public key if jwk_json has bad format. - return; + pubkey->alg_ = actual_alg.value(); + pubkey->alg_specified_ = true; + } + + auto actual_kty = getProtoMapValue(jwk_field, "kty"); + assert(actual_kty.has_value()); + + pubkey->kty_ = actual_kty.value(); + + auto n_str = getProtoMapValue(jwk_field, "n"); + auto e_str = getProtoMapValue(jwk_field, "e"); + + if (!n_str.has_value() || !e_str.has_value()) { + return false; } EvpPkeyGetter e; - pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); + pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str.value(), e_str.value()); if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(pubkey)); + keys_.emplace_back(std::move(pubkey)); } else { UpdateStatus(e.GetStatus()); } + + return true; } -void Pubkeys::ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json) { +bool Pubkeys::ExtractPubkeyFromJwkEC(const ProtobufMapType &jwk_field) { std::unique_ptr pubkey(new Pubkey()); - std::string x_str, y_str; - try { - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_json->hasObject("kid")) { - pubkey->kid_ = jwk_json->getString("kid"); - pubkey->kid_specified_ = true; + + if (jwk_field.find("kid") != jwk_field.end()) { + auto actual_kid = getProtoMapValue(jwk_field, "kid"); + if (!actual_kid.has_value()) { + return false; } - if (jwk_json->hasObject("alg")) { - pubkey->alg_ = jwk_json->getString("alg"); - if (pubkey->alg_ != "ES256") { - return; - } - pubkey->alg_specified_ = true; + pubkey->kid_ = actual_kid.value(); + pubkey->kid_specified_ = true; + } + + if (jwk_field.find("alg") != jwk_field.end()) { + auto actual_alg = getProtoMapValue(jwk_field, "alg"); + if (!actual_alg.has_value() || actual_alg.value() != "ES256") { + return false; } - pubkey->kty_ = jwk_json->getString("kty"); - x_str = jwk_json->getString("x"); - y_str = jwk_json->getString("y"); - } catch (Json::Exception &e) { - // Do not extract public key if jwk_json has bad format. - return; + pubkey->alg_ = actual_alg.value(); + pubkey->alg_specified_ = true; + } + + auto actual_kty = getProtoMapValue(jwk_field, "kty"); + if (!actual_kty.has_value()) { + return false; + } + pubkey->kty_ = actual_kty.value(); + + auto x_str = getProtoMapValue(jwk_field, "x"); + auto y_str = getProtoMapValue(jwk_field, "y"); + + if (!x_str.has_value() || !y_str.has_value()) { + return false; } EvpPkeyGetter e; - pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); + pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str.value(), y_str.value()); if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(pubkey)); + keys_.emplace_back(std::move(pubkey)); } else { UpdateStatus(e.GetStatus()); } + + return true; } std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, @@ -606,4 +715,4 @@ std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, } // namespace JwtAuth } // namespace Http -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/src/envoy/http/jwt_auth/jwt.h b/src/envoy/http/jwt_auth/jwt.h index 750a1ca3387..877ce8ae608 100644 --- a/src/envoy/http/jwt_auth/jwt.h +++ b/src/envoy/http/jwt_auth/jwt.h @@ -19,7 +19,8 @@ #include #include -#include "envoy/json/json_object.h" +#include "google/protobuf/struct.pb.h" + #include "openssl/ec.h" #include "openssl/evp.h" @@ -133,6 +134,10 @@ class WithStatus { class Pubkeys; class Jwt; +using ProtobufMapType = + google::protobuf::Map; +using ProtobufListValueType = google::protobuf::ListValue; + // JWT Verifier class. // // Usage example: @@ -286,7 +291,7 @@ class Pubkeys : public WithStatus { bool pem_format_ = false; std::string alg_; }; - std::vector > keys_; + std::vector> keys_; /* * TODO: try not to use friend function diff --git a/src/envoy/http/jwt_auth/jwt_authenticator_test.cc b/src/envoy/http/jwt_auth/jwt_authenticator_test.cc index cf54a86d1bb..c7fc0d4d2bc 100644 --- a/src/envoy/http/jwt_auth/jwt_authenticator_test.cc +++ b/src/envoy/http/jwt_auth/jwt_authenticator_test.cc @@ -16,7 +16,6 @@ #include "src/envoy/http/jwt_auth/jwt_authenticator.h" #include "common/http/message_impl.h" -#include "common/json/json_loader.h" #include "gtest/gtest.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/utility.h" diff --git a/src/envoy/http/jwt_auth/jwt_test.cc b/src/envoy/http/jwt_auth/jwt_test.cc index 611f29100c7..19e37b3ba87 100644 --- a/src/envoy/http/jwt_auth/jwt_test.cc +++ b/src/envoy/http/jwt_auth/jwt_test.cc @@ -18,7 +18,10 @@ #include #include "common/common/utility.h" -#include "common/json/json_loader.h" + +#include "google/protobuf/struct.pb.h" +#include "google/protobuf/util/json_util.h" + #include "test/test_common/utility.h" namespace Envoy { @@ -512,8 +515,9 @@ class DatasetJwk { namespace { -bool EqJson(Json::ObjectSharedPtr p1, Json::ObjectSharedPtr p2) { - return p1->asJsonString() == p2->asJsonString(); +bool EqJson(const google::protobuf::Struct& p1, + const google::protobuf::Struct& p2) { + return p1.DebugString() == p2.DebugString(); } } // namespace From dd527fb7ae329fe635b680d6705d0a35fc7cebcf Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 9 Jun 2020 16:50:24 +0000 Subject: [PATCH 08/44] jwt_authn: replace json processing strategy --- extensions/common/json_util.cc | 70 +++++++++- extensions/common/json_util.h | 50 +++++-- extensions/stats/plugin.cc | 80 ++++++------ src/envoy/http/jwt_auth/BUILD | 2 +- src/envoy/http/jwt_auth/jwt.cc | 195 ++++++++++++++++------------ src/envoy/http/jwt_auth/jwt.h | 17 +-- src/envoy/http/jwt_auth/jwt_test.cc | 53 ++++---- 7 files changed, 296 insertions(+), 171 deletions(-) diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index c72a76aae90..9f11a575252 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -20,12 +20,13 @@ namespace Wasm { namespace Common { -::nlohmann::json JsonParse(absl::string_view str) { - return ::nlohmann::json::parse(str, nullptr, false); +::nlohmann::json JsonParse(absl::string_view str, const bool allow_exceptions) { + return ::nlohmann::json::parse(str, nullptr, allow_exceptions); } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j) { +absl::optional JsonValueAs(const ::nlohmann::json& j, + const bool allow_exception) { if (j.is_number()) { return j.get(); } else if (j.is_string()) { @@ -34,29 +35,58 @@ absl::optional JsonValueAs(const ::nlohmann::json& j) { return result; } } + if (allow_exception) { + throw JsonParserTypeErrorException::create(302, + "Type must be string or number"); + } + return absl::nullopt; +} + +template <> +absl::optional JsonValueAs(const ::nlohmann::json& j, + const bool allow_exception) { + if (j.is_number()) { + return j.get(); + } else if (j.is_string()) { + uint64_t result = 0; + if (absl::SimpleAtoi(j.get_ref(), &result)) { + return result; + } + } + if (allow_exception) { + throw JsonParserTypeErrorException::create(302, + "Type must be number or string"); + } return absl::nullopt; } template <> absl::optional JsonValueAs( - const ::nlohmann::json& j) { + const ::nlohmann::json& j, const bool allow_exception) { if (j.is_string()) { return absl::string_view(j.get_ref()); } + if (allow_exception) { + throw JsonParserTypeErrorException::create(302, "Type must be string"); + } return absl::nullopt; } template <> absl::optional JsonValueAs( - const ::nlohmann::json& j) { + const ::nlohmann::json& j, const bool allow_exception) { if (j.is_string()) { return j.get(); } + if (allow_exception) { + throw JsonParserTypeErrorException::create(302, "Type must be string"); + } return absl::nullopt; } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j) { +absl::optional JsonValueAs(const ::nlohmann::json& j, + const bool allow_exception) { if (j.is_boolean()) { return j.get(); } @@ -68,9 +98,14 @@ absl::optional JsonValueAs(const ::nlohmann::json& j) { return false; } } + if (allow_exception) { + throw JsonParserTypeErrorException::create( + 302, "Type must be boolean or string(true/false)"); + } return absl::nullopt; } +template <> bool JsonArrayIterate( const ::nlohmann::json& j, absl::string_view field, const std::function& visitor) { @@ -82,6 +117,27 @@ bool JsonArrayIterate( return false; } for (const auto& elt : it.value().items()) { + assert(elt.value().is_object()); + if (!visitor(elt.value())) { + return false; + } + } + return true; +} + +template <> +bool JsonArrayIterate( + const ::nlohmann::json& j, absl::string_view field, + const std::function& visitor) { + auto it = j.find(field); + if (it == j.end()) { + return true; + } + if (!it.value().is_array()) { + return false; + } + for (const auto& elt : it.value().items()) { + assert(elt.value().is_string()); if (!visitor(elt.value())) { return false; } @@ -99,7 +155,7 @@ bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, return false; } for (const auto& elt : it.value().items()) { - auto key = JsonValueAs(elt.key()); + auto key = JsonValueAs(elt.key(), false); if (!key.has_value()) { return false; } diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 6835fa41a45..428f12a8363 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -15,6 +15,8 @@ #pragma once +#include + #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "extensions/common/nlohmann_json.hpp" @@ -26,46 +28,78 @@ namespace Wasm { namespace Common { // Parse JSON. Returns the discarded value if fails. -::nlohmann::json JsonParse(absl::string_view str); +::nlohmann::json JsonParse(absl::string_view str, + const bool allow_exceptions = false); template -absl::optional JsonValueAs(const ::nlohmann::json& j); +absl::optional JsonValueAs(const ::nlohmann::json&, const bool) { + static_assert(true, "Unsupported Type"); +} template <> absl::optional JsonValueAs( - const ::nlohmann::json& j); + const ::nlohmann::json& j, const bool allow_exception); + +template <> +absl::optional JsonValueAs( + const ::nlohmann::json& j, const bool allow_exception); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j); +absl::optional JsonValueAs(const ::nlohmann::json& j, + const bool allow_exception); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j); +absl::optional JsonValueAs(const ::nlohmann::json& j, + const bool allow_exception); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j); +absl::optional JsonValueAs(const ::nlohmann::json& j, + const bool allow_exception); template absl::optional JsonGetField(const ::nlohmann::json& j, - absl::string_view field) { + absl::string_view field, + const bool allow_exception = false) { auto it = j.find(field); if (it == j.end()) { + if (allow_exception) { + throw ::nlohmann::detail::out_of_range::create( + 403, "Key " + std::string(field) + " is not found"); + } return absl::nullopt; } - return JsonValueAs(it.value()); + return JsonValueAs(it.value(), allow_exception); } // Iterate over an optional array field. // Returns false if set and not an array, or any of the visitor calls returns // false. +template +bool JsonArrayIterate(const ::nlohmann::json&, absl::string_view, + const std::function&) { + static_assert(true, "Unsupported type"); +} + +template <> bool JsonArrayIterate( const ::nlohmann::json& j, absl::string_view field, const std::function& visitor); +template <> +bool JsonArrayIterate( + const ::nlohmann::json& j, absl::string_view field, + const std::function& visitor); + // Iterate over an optional object field key set. // Returns false if set and not an object, or any of the visitor calls returns // false. bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, const std::function& visitor); +using JsonObject = ::nlohmann::json; +using JsonParserException = ::nlohmann::detail::exception; +using JsonParserOutOfRangeException = ::nlohmann::detail::out_of_range; +using JsonParserTypeErrorException = ::nlohmann::detail::type_error; + } // namespace Common } // namespace Wasm diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index 2287426b775..44789677a4f 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -263,46 +263,48 @@ bool PluginRootContext::initializeDimensions(const json& j) { } // Process the metric definitions (overriding existing). - if (!JsonArrayIterate(j, "definitions", [&](const json& definition) -> bool { - auto name = JsonGetField(definition, "name").value_or(""); - auto value = - JsonGetField(definition, "value").value_or(""); - if (name.empty() || value.empty()) { - LOG_WARN("empty name or value in 'definitions'"); - return false; - } - auto token = addIntExpression(value); - if (!token.has_value()) { - LOG_WARN(absl::StrCat("failed to construct expression: ", value)); - return false; - } - auto& factory = factories[name]; - factory.name = name; - factory.extractor = - [token, name, - value](const ::Wasm::Common::RequestInfo&) -> uint64_t { - int64_t result = 0; - if (!evaluateExpression(token.value(), &result)) { - LOG_TRACE(absl::StrCat("Failed to evaluate expression: <", value, - "> for dimension:<", name, ">")); - } - return result; - }; - factory.type = MetricType::Counter; - auto type = - JsonGetField(definition, "type").value_or(""); - if (type == "GAUGE") { - factory.type = MetricType::Gauge; - } else if (type == "HISTOGRAM") { - factory.type = MetricType::Histogram; - } - return true; - })) { + if (!JsonArrayIterate( + j, "definitions", [&](const json& definition) -> bool { + auto name = + JsonGetField(definition, "name").value_or(""); + auto value = + JsonGetField(definition, "value").value_or(""); + if (name.empty() || value.empty()) { + LOG_WARN("empty name or value in 'definitions'"); + return false; + } + auto token = addIntExpression(value); + if (!token.has_value()) { + LOG_WARN(absl::StrCat("failed to construct expression: ", value)); + return false; + } + auto& factory = factories[name]; + factory.name = name; + factory.extractor = + [token, name, + value](const ::Wasm::Common::RequestInfo&) -> uint64_t { + int64_t result = 0; + if (!evaluateExpression(token.value(), &result)) { + LOG_TRACE(absl::StrCat("Failed to evaluate expression: <", + value, "> for dimension:<", name, ">")); + } + return result; + }; + factory.type = MetricType::Counter; + auto type = JsonGetField(definition, "type") + .value_or(""); + if (type == "GAUGE") { + factory.type = MetricType::Gauge; + } else if (type == "HISTOGRAM") { + factory.type = MetricType::Histogram; + } + return true; + })) { LOG_WARN("failed to parse 'definitions'"); } // Process the dimension overrides. - if (!JsonArrayIterate(j, "metrics", [&](const json& metric) -> bool { + if (!JsonArrayIterate(j, "metrics", [&](const json& metric) -> bool { // Sort tag override tags to keep the order of tags deterministic. std::vector tags; if (!JsonObjectIterate(metric, "dimensions", @@ -323,9 +325,9 @@ bool PluginRootContext::initializeDimensions(const json& j) { auto& indexes = metric_indexes[factory_it.first]; // Process tag deletions. - if (!JsonArrayIterate( + if (!JsonArrayIterate( metric, "tags_to_remove", [&](const json& tag) -> bool { - auto tag_string = JsonValueAs(tag); + auto tag_string = JsonValueAs(tag, false); if (!tag_string.has_value()) { LOG_WARN( absl::StrCat("unexpected tag to remove", tag.dump())); @@ -344,7 +346,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process tag overrides. for (const auto& tag : tags) { auto expr_string = - JsonValueAs(metric["dimensions"][tag]); + JsonValueAs(metric["dimensions"][tag], false); if (!expr_string.has_value()) { LOG_WARN("failed to parse 'dimensions' value"); return false; diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index fcc3c1ad918..69d10e6fb97 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -29,11 +29,11 @@ envoy_cc_library( srcs = ["jwt.cc"], hdrs = ["jwt.h"], external_deps = [ - "rapidjson", "ssl", ], repository = "@envoy", deps = [ + "//extensions/common:json_util", "@envoy//source/exe:envoy_common_lib", ], ) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 0dec1668c2c..55d7488ebb6 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -254,24 +254,26 @@ Jwt::Jwt(const std::string &jwt) { // Parse header json header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); header_str_ = Base64UrlDecode(header_str_base64url_); + try { - header_ = Json::Factory::loadFromString(header_str_); - } catch (Json::Exception &e) { + header_ = Wasm::Common::JsonParse(header_str_, true); + } catch (Wasm::Common::JsonParserException &e) { UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); return; } // Header should contain "alg". - if (!header_->hasObject("alg")) { + if (header_.find("alg") == header_.end()) { UpdateStatus(Status::JWT_HEADER_NO_ALG); return; } - try { - alg_ = header_->getString("alg"); - } catch (Json::Exception &e) { + + auto alg_field_opt = Wasm::Common::JsonGetField(header_, "alg"); + if (!alg_field_opt.has_value()) { UpdateStatus(Status::JWT_HEADER_BAD_ALG); return; } + alg_ = alg_field_opt.value(); if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && alg_ != "RS512") { @@ -281,10 +283,14 @@ Jwt::Jwt(const std::string &jwt) { // Header may contain "kid", which should be a string if exists. try { - kid_ = header_->getString("kid", ""); - } catch (Json::Exception &e) { + auto kid_field_opt = + Wasm::Common::JsonGetField(header_, "kid", true); + kid_ = kid_field_opt.value(); + } catch (Wasm::Common::JsonParserTypeErrorException &) { UpdateStatus(Status::JWT_HEADER_BAD_KID); return; + } catch (Wasm::Common::JsonParserOutOfRangeException &) { + kid_ = ""; } // Parse payload json @@ -292,29 +298,29 @@ Jwt::Jwt(const std::string &jwt) { std::string(jwt_split[1].begin(), jwt_split[1].end()); payload_str_ = Base64UrlDecode(payload_str_base64url_); try { - payload_ = Json::Factory::loadFromString(payload_str_); - } catch (Json::Exception &e) { + payload_ = Wasm::Common::JsonParse(payload_str_, true); + } catch (Wasm::Common::JsonParserException &e) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; } - iss_ = payload_->getString("iss", ""); - sub_ = payload_->getString("sub", ""); - exp_ = payload_->getInteger("exp", 0); + iss_ = Wasm::Common::JsonGetField(payload_, "iss").value_or(""); + sub_ = Wasm::Common::JsonGetField(payload_, "sub").value_or(""); + exp_ = Wasm::Common::JsonGetField(payload_, "exp").value_or(0); // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. - try { - aud_ = payload_->getStringArray("aud", true); - } catch (Json::Exception &e) { - // Try as string - try { - auto audience = payload_->getString("aud"); - aud_.push_back(audience); - } catch (Json::Exception &e) { + if (!Wasm::Common::JsonArrayIterate( + payload_, "aud", [&](const std::string &obj) -> bool { + aud_.emplace_back(obj); + return true; + })) { + auto aud_opt = Wasm::Common::JsonGetField(payload_, "aud"); + if (!aud_opt.has_value()) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; } + aud_.emplace_back(aud_opt.value()); } // Set up signature @@ -441,7 +447,7 @@ bool Verifier::Verify(const Jwt &jwt, const Pubkeys &pubkeys) { } // Returns the parsed header. -Json::ObjectSharedPtr Jwt::Header() { return header_; } +Wasm::Common::JsonObject &Jwt::Header() { return header_; } const std::string &Jwt::HeaderStr() { return header_str_; } const std::string &Jwt::HeaderStrBase64Url() { return header_str_base64url_; } @@ -449,7 +455,7 @@ const std::string &Jwt::Alg() { return alg_; } const std::string &Jwt::Kid() { return kid_; } // Returns payload JSON. -Json::ObjectSharedPtr Jwt::Payload() { return payload_; } +Wasm::Common::JsonObject &Jwt::Payload() { return payload_; } const std::string &Jwt::PayloadStr() { return payload_str_; } const std::string &Jwt::PayloadStrBase64Url() { return payload_str_base64url_; } @@ -473,29 +479,33 @@ void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { keys_.clear(); - Json::ObjectSharedPtr jwks_json; + Wasm::Common::JsonObject jwks_json; try { - jwks_json = Json::Factory::loadFromString(pkey_jwks); - } catch (Json::Exception &e) { + jwks_json = Wasm::Common::JsonParse(pkey_jwks, true); + } catch (Wasm::Common::JsonParserException &) { UpdateStatus(Status::JWK_PARSE_ERROR); return; } - std::vector keys; - if (!jwks_json->hasObject("keys")) { + + std::vector> key_refs; + + if (jwks_json.find("keys") == jwks_json.end()) { UpdateStatus(Status::JWK_NO_KEYS); return; } - try { - keys = jwks_json->getObjectArray("keys", true); - } catch (Json::Exception &e) { + + if (!Wasm::Common::JsonArrayIterate( + jwks_json, "keys", [&](const Wasm::Common::JsonObject &obj) -> bool { + key_refs.emplace_back( + std::reference_wrapper(obj)); + return true; + })) { UpdateStatus(Status::JWK_BAD_KEYS); return; } - for (auto jwk_json : keys) { - try { - ExtractPubkeyFromJwk(jwk_json); - } catch (Json::Exception &e) { + for (auto &key_ref : key_refs) { + if (!ExtractPubkeyFromJwk(key_ref.get())) { continue; } } @@ -505,44 +515,58 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { } } -void Pubkeys::ExtractPubkeyFromJwk(Json::ObjectSharedPtr jwk_json) { +bool Pubkeys::ExtractPubkeyFromJwk(const Wasm::Common::JsonObject &jwk_json) { // Check "kty" parameter, it should exist. // https://tools.ietf.org/html/rfc7517#section-4.1 // If "kty" is missing, getString throws an exception. - std::string kty = jwk_json->getString("kty"); + auto kty_opt = Wasm::Common::JsonGetField(jwk_json, "kty"); + if (!kty_opt.has_value()) { + return false; + } // Extract public key according to "kty" value. // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty == "EC") { - ExtractPubkeyFromJwkEC(jwk_json); - } else if (kty == "RSA") { - ExtractPubkeyFromJwkRSA(jwk_json); + if (kty_opt.value() == "EC") { + return ExtractPubkeyFromJwkEC(jwk_json); + } else if (kty_opt.value() == "RSA") { + return ExtractPubkeyFromJwkRSA(jwk_json); } + + return false; } -void Pubkeys::ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json) { +bool Pubkeys::ExtractPubkeyFromJwkRSA( + const Wasm::Common::JsonObject &jwk_json) { std::unique_ptr pubkey(new Pubkey()); std::string n_str, e_str; - try { - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_json->hasObject("kid")) { - pubkey->kid_ = jwk_json->getString("kid"); - pubkey->kid_specified_ = true; - } - if (jwk_json->hasObject("alg")) { - pubkey->alg_ = jwk_json->getString("alg"); - if (pubkey->alg_.compare(0, 2, "RS") != 0) { - return; - } - pubkey->alg_specified_ = true; + + // "kid" and "alg" are optional, if they do not exist, set them to "". + // https://tools.ietf.org/html/rfc7517#page-8 + auto kid_opt = Wasm::Common::JsonGetField(jwk_json, "kid"); + if (kid_opt.has_value()) { + pubkey->kid_ = kid_opt.value(); + pubkey->kid_specified_ = true; + } + + auto alg_opt = Wasm::Common::JsonGetField(jwk_json, "alg"); + if (alg_opt.has_value()) { + // Allow only "RS" prefixed algorithms. + // https://tools.ietf.org/html/rfc7518#section-3.1 + if (!(alg_opt.value() == "RS256" || alg_opt.value() == "RS384" || + alg_opt.value() == "RS512")) { + return false; } - pubkey->kty_ = jwk_json->getString("kty"); - n_str = jwk_json->getString("n"); - e_str = jwk_json->getString("e"); - } catch (Json::Exception &e) { - // Do not extract public key if jwk_json has bad format. - return; + pubkey->alg_ = alg_opt.value(); + pubkey->alg_specified_ = true; + } + + try { + pubkey->kty_ = + Wasm::Common::JsonGetField(jwk_json, "kty").value(); + n_str = Wasm::Common::JsonGetField(jwk_json, "n").value(); + e_str = Wasm::Common::JsonGetField(jwk_json, "e").value(); + } catch (Wasm::Common::JsonParserOutOfRangeException &) { + return false; } EvpPkeyGetter e; @@ -552,31 +576,38 @@ void Pubkeys::ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json) { } else { UpdateStatus(e.GetStatus()); } + + return true; } -void Pubkeys::ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json) { +bool Pubkeys::ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject &jwk_json) { std::unique_ptr pubkey(new Pubkey()); std::string x_str, y_str; - try { - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_json->hasObject("kid")) { - pubkey->kid_ = jwk_json->getString("kid"); - pubkey->kid_specified_ = true; - } - if (jwk_json->hasObject("alg")) { - pubkey->alg_ = jwk_json->getString("alg"); - if (pubkey->alg_ != "ES256") { - return; - } - pubkey->alg_specified_ = true; + + // "kid" and "alg" are optional, if they do not exist, set them to "". + // https://tools.ietf.org/html/rfc7517#page-8 + auto kid_opt = Wasm::Common::JsonGetField(jwk_json, "kid"); + if (kid_opt.has_value()) { + pubkey->kid_ = kid_opt.value(); + pubkey->kid_specified_ = true; + } + + auto alg_opt = Wasm::Common::JsonGetField(jwk_json, "alg"); + if (alg_opt.has_value()) { + if (alg_opt.value() != "ES256") { + return false; } - pubkey->kty_ = jwk_json->getString("kty"); - x_str = jwk_json->getString("x"); - y_str = jwk_json->getString("y"); - } catch (Json::Exception &e) { - // Do not extract public key if jwk_json has bad format. - return; + pubkey->alg_ = alg_opt.value(); + pubkey->alg_specified_ = true; + } + + try { + pubkey->kty_ = + Wasm::Common::JsonGetField(jwk_json, "kty").value(); + x_str = Wasm::Common::JsonGetField(jwk_json, "x").value(); + y_str = Wasm::Common::JsonGetField(jwk_json, "y").value(); + } catch (Wasm::Common::JsonParserOutOfRangeException &) { + return false; } EvpPkeyGetter e; @@ -586,6 +617,8 @@ void Pubkeys::ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json) { } else { UpdateStatus(e.GetStatus()); } + + return true; } std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, diff --git a/src/envoy/http/jwt_auth/jwt.h b/src/envoy/http/jwt_auth/jwt.h index 750a1ca3387..48bc23f10d8 100644 --- a/src/envoy/http/jwt_auth/jwt.h +++ b/src/envoy/http/jwt_auth/jwt.h @@ -15,11 +15,12 @@ #pragma once +#include #include #include #include -#include "envoy/json/json_object.h" +#include "extensions/common/json_util.h" #include "openssl/ec.h" #include "openssl/evp.h" @@ -190,7 +191,7 @@ class Jwt : public WithStatus { // It returns a pointer to a JSON object of the header of the given JWT. // When the given JWT has a format error, it returns nullptr. // It returns the header JSON even if the signature is invalid. - Json::ObjectSharedPtr Header(); + Wasm::Common::JsonObject& Header(); // They return a string (or base64url-encoded string) of the header JSON of // the given JWT. @@ -207,7 +208,7 @@ class Jwt : public WithStatus { // It returns a pointer to a JSON object of the payload of the given JWT. // When the given jWT has a format error, it returns nullptr. // It returns the payload JSON even if the signature is invalid. - Json::ObjectSharedPtr Payload(); + Wasm::Common::JsonObject& Payload(); // They return a string (or base64url-encoded string) of the payload JSON of // the given JWT. @@ -231,10 +232,10 @@ class Jwt : public WithStatus { int64_t Exp(); private: - Json::ObjectSharedPtr header_; + Wasm::Common::JsonObject header_; std::string header_str_; std::string header_str_base64url_; - Json::ObjectSharedPtr payload_; + Wasm::Common::JsonObject payload_; std::string payload_str_; std::string payload_str_base64url_; std::string signature_; @@ -270,9 +271,9 @@ class Pubkeys : public WithStatus { void CreateFromPemCore(const std::string& pkey_pem); void CreateFromJwksCore(const std::string& pkey_jwks); // Extracts the public key from a jwk key (jkey) and sets it to keys_; - void ExtractPubkeyFromJwk(Json::ObjectSharedPtr jwk_json); - void ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json); - void ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json); + bool ExtractPubkeyFromJwk(const Wasm::Common::JsonObject& jwk_json); + bool ExtractPubkeyFromJwkRSA(const Wasm::Common::JsonObject& jwk_json); + bool ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject& jwk_json); class Pubkey { public: diff --git a/src/envoy/http/jwt_auth/jwt_test.cc b/src/envoy/http/jwt_auth/jwt_test.cc index 611f29100c7..141844af53e 100644 --- a/src/envoy/http/jwt_auth/jwt_test.cc +++ b/src/envoy/http/jwt_auth/jwt_test.cc @@ -18,7 +18,6 @@ #include #include "common/common/utility.h" -#include "common/json/json_loader.h" #include "test/test_common/utility.h" namespace Envoy { @@ -512,15 +511,15 @@ class DatasetJwk { namespace { -bool EqJson(Json::ObjectSharedPtr p1, Json::ObjectSharedPtr p2) { - return p1->asJsonString() == p2->asJsonString(); +bool EqJson(Wasm::Common::JsonObject& p1, Wasm::Common::JsonObject& p2) { + return p1.dump() == p2.dump(); } } // namespace class JwtTest : public testing::Test { protected: void DoTest(std::string jwt_str, std::string pkey, std::string pkey_type, - bool verified, Status status, Json::ObjectSharedPtr payload) { + bool verified, Status status, Wasm::Common::JsonObject* payload) { Jwt jwt(jwt_str); Verifier v; std::unique_ptr key; @@ -534,8 +533,8 @@ class JwtTest : public testing::Test { EXPECT_EQ(verified, v.Verify(jwt, *key)); EXPECT_EQ(status, v.GetStatus()); if (verified) { - ASSERT_TRUE(jwt.Payload()); - EXPECT_TRUE(EqJson(payload, jwt.Payload())); + ASSERT_NE(0, jwt.Payload().size()); + EXPECT_TRUE(EqJson(*payload, jwt.Payload())); } } }; @@ -548,18 +547,18 @@ class JwtTestPem : public JwtTest { }; TEST_F(JwtTestPem, OK) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); + DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, OKWithAlgRs384) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); + DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, OKWithAlgRs512) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); + DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, MultiAudiences) { @@ -673,12 +672,12 @@ class JwtTestJwks : public JwtTest { }; TEST_F(JwtTestJwks, OkNoKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); + DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"RS256\","; std::string pubkey_no_alg = ds.kPublicKeyRSA; @@ -687,7 +686,7 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, payload); + DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, &payload); // Remove "kid" claim from public key. std::string kid_claim1 = @@ -699,19 +698,19 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, payload); + DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, OkNoKidLogExp) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadLongExp); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadLongExp, true); DoTest(ds.kJwtNoKidLongExp, ds.kPublicKeyRSA, "jwks", true, Status::OK, - payload); + &payload); } TEST_F(JwtTestJwks, OkCorrectKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); DoTest(ds.kJwtWithCorrectKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, - payload); + &payload); } TEST_F(JwtTestJwks, IncorrectKid) { @@ -754,16 +753,16 @@ TEST_F(JwtTestJwks, JwkBadPublicKey) { } TEST_F(JwtTestJwks, OkTokenJwkEC) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadEC); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC, true); // ES256-signed token with kid specified. - DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, payload); + DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, &payload); // ES256-signed token without kid specified. DoTest(ds.kTokenECNoKid, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, - payload); + &payload); } TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadEC); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC, true); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"ES256\","; std::string pubkey_no_alg = ds.kPublicKeyJwkEC; @@ -772,7 +771,7 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, payload); + DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, &payload); // Remove "kid" claim from public key. std::string kid_claim1 = ",\"kid\": \"abc\""; @@ -782,7 +781,7 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, payload); + DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, NonExistKidEC) { From d2d2e9bd09c4d82b1690d541ff6c5af801105721 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 9 Jun 2020 17:00:23 +0000 Subject: [PATCH 09/44] cleanup --- extensions/common/json_util.cc | 22 ++++++++--------- extensions/common/json_util.h | 43 ++++++++++++++++------------------ 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index 9f11a575252..40afa3043cc 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -20,12 +20,12 @@ namespace Wasm { namespace Common { -::nlohmann::json JsonParse(absl::string_view str, const bool allow_exceptions) { - return ::nlohmann::json::parse(str, nullptr, allow_exceptions); +JsonObject JsonParse(absl::string_view str, const bool allow_exceptions) { + return JsonObject::parse(str, nullptr, allow_exceptions); } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j, +absl::optional JsonValueAs(const JsonObject& j, const bool allow_exception) { if (j.is_number()) { return j.get(); @@ -43,7 +43,7 @@ absl::optional JsonValueAs(const ::nlohmann::json& j, } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j, +absl::optional JsonValueAs(const JsonObject& j, const bool allow_exception) { if (j.is_number()) { return j.get(); @@ -62,7 +62,7 @@ absl::optional JsonValueAs(const ::nlohmann::json& j, template <> absl::optional JsonValueAs( - const ::nlohmann::json& j, const bool allow_exception) { + const JsonObject& j, const bool allow_exception) { if (j.is_string()) { return absl::string_view(j.get_ref()); } @@ -74,7 +74,7 @@ absl::optional JsonValueAs( template <> absl::optional JsonValueAs( - const ::nlohmann::json& j, const bool allow_exception) { + const JsonObject& j, const bool allow_exception) { if (j.is_string()) { return j.get(); } @@ -85,7 +85,7 @@ absl::optional JsonValueAs( } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j, +absl::optional JsonValueAs(const JsonObject& j, const bool allow_exception) { if (j.is_boolean()) { return j.get(); @@ -107,8 +107,8 @@ absl::optional JsonValueAs(const ::nlohmann::json& j, template <> bool JsonArrayIterate( - const ::nlohmann::json& j, absl::string_view field, - const std::function& visitor) { + const JsonObject& j, absl::string_view field, + const std::function& visitor) { auto it = j.find(field); if (it == j.end()) { return true; @@ -127,7 +127,7 @@ bool JsonArrayIterate( template <> bool JsonArrayIterate( - const ::nlohmann::json& j, absl::string_view field, + const JsonObject& j, absl::string_view field, const std::function& visitor) { auto it = j.find(field); if (it == j.end()) { @@ -145,7 +145,7 @@ bool JsonArrayIterate( return true; } -bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, +bool JsonObjectIterate(const JsonObject& j, absl::string_view field, const std::function& visitor) { auto it = j.find(field); if (it == j.end()) { diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 428f12a8363..d772cb6b92a 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -15,8 +15,6 @@ #pragma once -#include - #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "extensions/common/nlohmann_json.hpp" @@ -27,43 +25,47 @@ namespace Wasm { namespace Common { +using JsonObject = ::nlohmann::json; +using JsonParserException = ::nlohmann::detail::exception; +using JsonParserOutOfRangeException = ::nlohmann::detail::out_of_range; +using JsonParserTypeErrorException = ::nlohmann::detail::type_error; + // Parse JSON. Returns the discarded value if fails. -::nlohmann::json JsonParse(absl::string_view str, - const bool allow_exceptions = false); +JsonObject JsonParse(absl::string_view str, + const bool allow_exceptions = false); template -absl::optional JsonValueAs(const ::nlohmann::json&, const bool) { +absl::optional JsonValueAs(const JsonObject&, const bool) { static_assert(true, "Unsupported Type"); } template <> absl::optional JsonValueAs( - const ::nlohmann::json& j, const bool allow_exception); + const JsonObject& j, const bool allow_exception); template <> absl::optional JsonValueAs( - const ::nlohmann::json& j, const bool allow_exception); + const JsonObject& j, const bool allow_exception); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j, +absl::optional JsonValueAs(const JsonObject& j, const bool allow_exception); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j, +absl::optional JsonValueAs(const JsonObject& j, const bool allow_exception); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j, +absl::optional JsonValueAs(const JsonObject& j, const bool allow_exception); template -absl::optional JsonGetField(const ::nlohmann::json& j, - absl::string_view field, +absl::optional JsonGetField(const JsonObject& j, absl::string_view field, const bool allow_exception = false) { auto it = j.find(field); if (it == j.end()) { if (allow_exception) { - throw ::nlohmann::detail::out_of_range::create( + throw JsonParserOutOfRangeException::create( 403, "Key " + std::string(field) + " is not found"); } return absl::nullopt; @@ -75,31 +77,26 @@ absl::optional JsonGetField(const ::nlohmann::json& j, // Returns false if set and not an array, or any of the visitor calls returns // false. template -bool JsonArrayIterate(const ::nlohmann::json&, absl::string_view, +bool JsonArrayIterate(const JsonObject&, absl::string_view, const std::function&) { static_assert(true, "Unsupported type"); } template <> bool JsonArrayIterate( - const ::nlohmann::json& j, absl::string_view field, - const std::function& visitor); + const JsonObject& j, absl::string_view field, + const std::function& visitor); template <> bool JsonArrayIterate( - const ::nlohmann::json& j, absl::string_view field, + const JsonObject& j, absl::string_view field, const std::function& visitor); // Iterate over an optional object field key set. // Returns false if set and not an object, or any of the visitor calls returns // false. -bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, +bool JsonObjectIterate(const JsonObject& j, absl::string_view field, const std::function& visitor); -using JsonObject = ::nlohmann::json; -using JsonParserException = ::nlohmann::detail::exception; -using JsonParserOutOfRangeException = ::nlohmann::detail::out_of_range; -using JsonParserTypeErrorException = ::nlohmann::detail::type_error; - } // namespace Common } // namespace Wasm From f76039556432312e34af620a1c183dfc6eec5cc9 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 16 Jun 2020 08:13:20 +0000 Subject: [PATCH 10/44] not to use exception --- extensions/common/json_util.cc | 92 +++++++++++---------- extensions/common/json_util.h | 53 +++++++----- src/envoy/http/jwt_auth/jwt.cc | 120 ++++++++++++++++------------ src/envoy/http/jwt_auth/jwt_test.cc | 50 +++++++----- 4 files changed, 175 insertions(+), 140 deletions(-) diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index 40afa3043cc..f5d2f0841c8 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -20,89 +20,87 @@ namespace Wasm { namespace Common { -JsonObject JsonParse(absl::string_view str, const bool allow_exceptions) { - return JsonObject::parse(str, nullptr, allow_exceptions); +absl::optional JsonParse(absl::string_view str) { + const auto result = JsonObject::parse(str, nullptr, false); + if (result.empty() || result.is_discarded()) { + return absl::nullopt; + } + return result; } template <> -absl::optional JsonValueAs(const JsonObject& j, - const bool allow_exception) { +std::pair, absl::optional> +JsonValueAs(const JsonObject& j) { if (j.is_number()) { - return j.get(); + return std::make_pair(j.get(), absl::nullopt); } else if (j.is_string()) { int64_t result = 0; if (absl::SimpleAtoi(j.get_ref(), &result)) { - return result; + return std::make_pair(result, absl::nullopt); } } - if (allow_exception) { - throw JsonParserTypeErrorException::create(302, - "Type must be string or number"); - } - return absl::nullopt; + return std::make_pair(absl::nullopt, + JsonParseError{JsonParserErrorDetail::TYPE_ERROR, + "Type must be string or number"}); } template <> -absl::optional JsonValueAs(const JsonObject& j, - const bool allow_exception) { +std::pair, absl::optional> +JsonValueAs(const JsonObject& j) { if (j.is_number()) { - return j.get(); + return std::make_pair(j.get(), absl::nullopt); } else if (j.is_string()) { uint64_t result = 0; if (absl::SimpleAtoi(j.get_ref(), &result)) { - return result; + return std::make_pair(result, absl::nullopt); } } - if (allow_exception) { - throw JsonParserTypeErrorException::create(302, - "Type must be number or string"); - } - return absl::nullopt; + return std::make_pair(absl::nullopt, + JsonParseError{JsonParserErrorDetail::TYPE_ERROR, + "Type must be string or number"}); } template <> -absl::optional JsonValueAs( - const JsonObject& j, const bool allow_exception) { +std::pair, absl::optional> +JsonValueAs(const JsonObject& j) { if (j.is_string()) { - return absl::string_view(j.get_ref()); - } - if (allow_exception) { - throw JsonParserTypeErrorException::create(302, "Type must be string"); + return std::make_pair(absl::string_view(j.get_ref()), + absl::nullopt); } - return absl::nullopt; + return std::make_pair( + absl::nullopt, + JsonParseError{JsonParserErrorDetail::TYPE_ERROR, "Type must be string"}); } template <> -absl::optional JsonValueAs( - const JsonObject& j, const bool allow_exception) { +std::pair, absl::optional> +JsonValueAs(const JsonObject& j) { if (j.is_string()) { - return j.get(); + return std::make_pair(j.get(), absl::nullopt); } - if (allow_exception) { - throw JsonParserTypeErrorException::create(302, "Type must be string"); - } - return absl::nullopt; + return std::make_pair( + absl::nullopt, + JsonParseError{JsonParserErrorDetail::TYPE_ERROR, "Type must be string"}); } template <> -absl::optional JsonValueAs(const JsonObject& j, - const bool allow_exception) { +std::pair, absl::optional> +JsonValueAs(const JsonObject& j) { if (j.is_boolean()) { - return j.get(); + return std::make_pair(j.get(), absl::nullopt); } if (j.is_string()) { const std::string& v = j.get_ref(); if (v == "true") { - return true; + return std::make_pair(true, absl::nullopt); } else if (v == "false") { - return false; + return std::make_pair(false, absl::nullopt); } } - if (allow_exception) { - throw JsonParserTypeErrorException::create( - 302, "Type must be boolean or string(true/false)"); - } - return absl::nullopt; + return std::make_pair( + absl::nullopt, + JsonParseError{JsonParserErrorDetail::TYPE_ERROR, + "Type must be boolean or string(true/false)"}); } template <> @@ -155,11 +153,11 @@ bool JsonObjectIterate(const JsonObject& j, absl::string_view field, return false; } for (const auto& elt : it.value().items()) { - auto key = JsonValueAs(elt.key(), false); - if (!key.has_value()) { + auto result = JsonValueAs(elt.key()); + if (!result.first.has_value()) { return false; } - if (!visitor(key.value())) { + if (!visitor(result.first.value())) { return false; } } diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index d772cb6b92a..138a1179b13 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -26,51 +26,62 @@ namespace Wasm { namespace Common { using JsonObject = ::nlohmann::json; +using JsonObjectValueType = ::nlohmann::detail::value_t; using JsonParserException = ::nlohmann::detail::exception; using JsonParserOutOfRangeException = ::nlohmann::detail::out_of_range; using JsonParserTypeErrorException = ::nlohmann::detail::type_error; +enum JsonParserErrorDetail { + OUT_OF_RANGE, + TYPE_ERROR, + PARSE_ERROR, +}; + +struct JsonParseError { + JsonParserErrorDetail error_detail_; + std::string message_; +}; + // Parse JSON. Returns the discarded value if fails. -JsonObject JsonParse(absl::string_view str, - const bool allow_exceptions = false); +absl::optional JsonParse(absl::string_view str); template -absl::optional JsonValueAs(const JsonObject&, const bool) { +std::pair, absl::optional> JsonValueAs( + const JsonObject&) { static_assert(true, "Unsupported Type"); } template <> -absl::optional JsonValueAs( - const JsonObject& j, const bool allow_exception); +std::pair, absl::optional> +JsonValueAs(const JsonObject& j); template <> -absl::optional JsonValueAs( - const JsonObject& j, const bool allow_exception); +std::pair, absl::optional> +JsonValueAs(const JsonObject& j); template <> -absl::optional JsonValueAs(const JsonObject& j, - const bool allow_exception); +std::pair, absl::optional> +JsonValueAs(const JsonObject& j); template <> -absl::optional JsonValueAs(const JsonObject& j, - const bool allow_exception); +std::pair, absl::optional> +JsonValueAs(const JsonObject& j); template <> -absl::optional JsonValueAs(const JsonObject& j, - const bool allow_exception); +std::pair, absl::optional> +JsonValueAs(const JsonObject& j); template -absl::optional JsonGetField(const JsonObject& j, absl::string_view field, - const bool allow_exception = false) { +std::pair, absl::optional> JsonGetField( + const JsonObject& j, absl::string_view field) { auto it = j.find(field); if (it == j.end()) { - if (allow_exception) { - throw JsonParserOutOfRangeException::create( - 403, "Key " + std::string(field) + " is not found"); - } - return absl::nullopt; + return std::make_pair( + absl::nullopt, + JsonParseError{JsonParserErrorDetail::OUT_OF_RANGE, + "Key " + std::string(field) + " is not found"}); } - return JsonValueAs(it.value(), allow_exception); + return JsonValueAs(it.value()); } // Iterate over an optional array field. diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 55d7488ebb6..1a4b267daaa 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -255,11 +255,12 @@ Jwt::Jwt(const std::string &jwt) { header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); header_str_ = Base64UrlDecode(header_str_base64url_); - try { - header_ = Wasm::Common::JsonParse(header_str_, true); - } catch (Wasm::Common::JsonParserException &e) { + auto parse_result = Wasm::Common::JsonParse(header_str_); + if (!parse_result.has_value()) { UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); return; + } else { + header_ = parse_result.value(); } // Header should contain "alg". @@ -269,11 +270,11 @@ Jwt::Jwt(const std::string &jwt) { } auto alg_field_opt = Wasm::Common::JsonGetField(header_, "alg"); - if (!alg_field_opt.has_value()) { + if (!alg_field_opt.first.has_value()) { UpdateStatus(Status::JWT_HEADER_BAD_ALG); return; } - alg_ = alg_field_opt.value(); + alg_ = alg_field_opt.first.value(); if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && alg_ != "RS512") { @@ -282,31 +283,40 @@ Jwt::Jwt(const std::string &jwt) { } // Header may contain "kid", which should be a string if exists. - try { - auto kid_field_opt = - Wasm::Common::JsonGetField(header_, "kid", true); - kid_ = kid_field_opt.value(); - } catch (Wasm::Common::JsonParserTypeErrorException &) { - UpdateStatus(Status::JWT_HEADER_BAD_KID); - return; - } catch (Wasm::Common::JsonParserOutOfRangeException &) { - kid_ = ""; + auto kid_field_opt = Wasm::Common::JsonGetField(header_, "kid"); + if (kid_field_opt.second.has_value()) { + if (kid_field_opt.second->error_detail_ == + Wasm::Common::JsonParserErrorDetail::TYPE_ERROR) { + UpdateStatus(Status::JWT_HEADER_BAD_KID); + return; + } else if (kid_field_opt.second->error_detail_ == + Wasm::Common::JsonParserErrorDetail::OUT_OF_RANGE) { + kid_ = ""; + } else { + return; + } + } else { + kid_ = kid_field_opt.first.value(); } // Parse payload json payload_str_base64url_ = std::string(jwt_split[1].begin(), jwt_split[1].end()); payload_str_ = Base64UrlDecode(payload_str_base64url_); - try { - payload_ = Wasm::Common::JsonParse(payload_str_, true); - } catch (Wasm::Common::JsonParserException &e) { + auto payload_parse_result = Wasm::Common::JsonParse(payload_str_); + if (!payload_parse_result.has_value()) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; + } else { + payload_ = payload_parse_result.value(); } - iss_ = Wasm::Common::JsonGetField(payload_, "iss").value_or(""); - sub_ = Wasm::Common::JsonGetField(payload_, "sub").value_or(""); - exp_ = Wasm::Common::JsonGetField(payload_, "exp").value_or(0); + iss_ = Wasm::Common::JsonGetField(payload_, "iss") + .first.value_or(""); + sub_ = Wasm::Common::JsonGetField(payload_, "sub") + .first.value_or(""); + exp_ = + Wasm::Common::JsonGetField(payload_, "exp").first.value_or(0); // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. @@ -316,11 +326,11 @@ Jwt::Jwt(const std::string &jwt) { return true; })) { auto aud_opt = Wasm::Common::JsonGetField(payload_, "aud"); - if (!aud_opt.has_value()) { + if (!aud_opt.first.has_value()) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; } - aud_.emplace_back(aud_opt.value()); + aud_.emplace_back(aud_opt.first.value()); } // Set up signature @@ -480,11 +490,12 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { keys_.clear(); Wasm::Common::JsonObject jwks_json; - try { - jwks_json = Wasm::Common::JsonParse(pkey_jwks, true); - } catch (Wasm::Common::JsonParserException &) { + auto jwks_parse_result = Wasm::Common::JsonParse(pkey_jwks); + if (!jwks_parse_result.has_value()) { UpdateStatus(Status::JWK_PARSE_ERROR); return; + } else { + jwks_json = jwks_parse_result.value(); } std::vector> key_refs; @@ -520,15 +531,15 @@ bool Pubkeys::ExtractPubkeyFromJwk(const Wasm::Common::JsonObject &jwk_json) { // https://tools.ietf.org/html/rfc7517#section-4.1 // If "kty" is missing, getString throws an exception. auto kty_opt = Wasm::Common::JsonGetField(jwk_json, "kty"); - if (!kty_opt.has_value()) { + if (!kty_opt.first.has_value()) { return false; } // Extract public key according to "kty" value. // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty_opt.value() == "EC") { + if (kty_opt.first.value() == "EC") { return ExtractPubkeyFromJwkEC(jwk_json); - } else if (kty_opt.value() == "RSA") { + } else if (kty_opt.first.value() == "RSA") { return ExtractPubkeyFromJwkRSA(jwk_json); } @@ -543,31 +554,35 @@ bool Pubkeys::ExtractPubkeyFromJwkRSA( // "kid" and "alg" are optional, if they do not exist, set them to "". // https://tools.ietf.org/html/rfc7517#page-8 auto kid_opt = Wasm::Common::JsonGetField(jwk_json, "kid"); - if (kid_opt.has_value()) { - pubkey->kid_ = kid_opt.value(); + if (kid_opt.first.has_value()) { + pubkey->kid_ = kid_opt.first.value(); pubkey->kid_specified_ = true; } auto alg_opt = Wasm::Common::JsonGetField(jwk_json, "alg"); - if (alg_opt.has_value()) { + if (alg_opt.first.has_value()) { // Allow only "RS" prefixed algorithms. // https://tools.ietf.org/html/rfc7518#section-3.1 - if (!(alg_opt.value() == "RS256" || alg_opt.value() == "RS384" || - alg_opt.value() == "RS512")) { + if (!(alg_opt.first.value() == "RS256" || + alg_opt.first.value() == "RS384" || + alg_opt.first.value() == "RS512")) { return false; } - pubkey->alg_ = alg_opt.value(); + pubkey->alg_ = alg_opt.first.value(); pubkey->alg_specified_ = true; } - try { - pubkey->kty_ = - Wasm::Common::JsonGetField(jwk_json, "kty").value(); - n_str = Wasm::Common::JsonGetField(jwk_json, "n").value(); - e_str = Wasm::Common::JsonGetField(jwk_json, "e").value(); - } catch (Wasm::Common::JsonParserOutOfRangeException &) { + auto pubkey_kty_opt = + Wasm::Common::JsonGetField(jwk_json, "kty"); + assert(pubkey_kty_opt.first.has_value()); + pubkey->kty_ = pubkey_kty_opt.first.value(); + auto n_str_opt = Wasm::Common::JsonGetField(jwk_json, "n"); + auto e_str_opt = Wasm::Common::JsonGetField(jwk_json, "e"); + if (n_str_opt.second.has_value() || n_str_opt.second.has_value()) { return false; } + n_str = n_str_opt.first.value(); + e_str = e_str_opt.first.value(); EvpPkeyGetter e; pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); @@ -587,28 +602,31 @@ bool Pubkeys::ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject &jwk_json) { // "kid" and "alg" are optional, if they do not exist, set them to "". // https://tools.ietf.org/html/rfc7517#page-8 auto kid_opt = Wasm::Common::JsonGetField(jwk_json, "kid"); - if (kid_opt.has_value()) { - pubkey->kid_ = kid_opt.value(); + if (kid_opt.first.has_value()) { + pubkey->kid_ = kid_opt.first.value(); pubkey->kid_specified_ = true; } auto alg_opt = Wasm::Common::JsonGetField(jwk_json, "alg"); - if (alg_opt.has_value()) { - if (alg_opt.value() != "ES256") { + if (alg_opt.first.has_value()) { + if (alg_opt.first.value() != "ES256") { return false; } - pubkey->alg_ = alg_opt.value(); + pubkey->alg_ = alg_opt.first.value(); pubkey->alg_specified_ = true; } - try { - pubkey->kty_ = - Wasm::Common::JsonGetField(jwk_json, "kty").value(); - x_str = Wasm::Common::JsonGetField(jwk_json, "x").value(); - y_str = Wasm::Common::JsonGetField(jwk_json, "y").value(); - } catch (Wasm::Common::JsonParserOutOfRangeException &) { + auto pubkey_kty_opt = + Wasm::Common::JsonGetField(jwk_json, "kty"); + assert(pubkey_kty_opt.first.has_value()); + pubkey->kty_ = pubkey_kty_opt.first.value(); + auto x_str_opt = Wasm::Common::JsonGetField(jwk_json, "x"); + auto y_str_opt = Wasm::Common::JsonGetField(jwk_json, "y"); + if (x_str_opt.second.has_value() || y_str_opt.second.has_value()) { return false; } + x_str = x_str_opt.first.value(); + y_str = y_str_opt.first.value(); EvpPkeyGetter e; pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); diff --git a/src/envoy/http/jwt_auth/jwt_test.cc b/src/envoy/http/jwt_auth/jwt_test.cc index 141844af53e..ac7ca03f8af 100644 --- a/src/envoy/http/jwt_auth/jwt_test.cc +++ b/src/envoy/http/jwt_auth/jwt_test.cc @@ -547,18 +547,20 @@ class JwtTestPem : public JwtTest { }; TEST_F(JwtTestPem, OK) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); - DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, &payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); + DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, &payload.value()); } TEST_F(JwtTestPem, OKWithAlgRs384) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); - DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, &payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); + DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, + &payload.value()); } TEST_F(JwtTestPem, OKWithAlgRs512) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); - DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, &payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); + DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, + &payload.value()); } TEST_F(JwtTestPem, MultiAudiences) { @@ -672,12 +674,13 @@ class JwtTestJwks : public JwtTest { }; TEST_F(JwtTestJwks, OkNoKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); - DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, &payload); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); + DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, + &payload.value()); } TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"RS256\","; std::string pubkey_no_alg = ds.kPublicKeyRSA; @@ -686,7 +689,8 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, &payload); + DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, + &payload.value()); // Remove "kid" claim from public key. std::string kid_claim1 = @@ -698,19 +702,20 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, &payload); + DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, + &payload.value()); } TEST_F(JwtTestJwks, OkNoKidLogExp) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadLongExp, true); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadLongExp); DoTest(ds.kJwtNoKidLongExp, ds.kPublicKeyRSA, "jwks", true, Status::OK, - &payload); + &payload.value()); } TEST_F(JwtTestJwks, OkCorrectKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload, true); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); DoTest(ds.kJwtWithCorrectKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, - &payload); + &payload.value()); } TEST_F(JwtTestJwks, IncorrectKid) { @@ -753,16 +758,17 @@ TEST_F(JwtTestJwks, JwkBadPublicKey) { } TEST_F(JwtTestJwks, OkTokenJwkEC) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC, true); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC); // ES256-signed token with kid specified. - DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, &payload); + DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, + &payload.value()); // ES256-signed token without kid specified. DoTest(ds.kTokenECNoKid, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, - &payload); + &payload.value()); } TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC, true); + auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"ES256\","; std::string pubkey_no_alg = ds.kPublicKeyJwkEC; @@ -771,7 +777,8 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, &payload); + DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, + &payload.value()); // Remove "kid" claim from public key. std::string kid_claim1 = ",\"kid\": \"abc\""; @@ -781,7 +788,8 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, &payload); + DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, + &payload.value()); } TEST_F(JwtTestJwks, NonExistKidEC) { From 1d6845bef9b04508ae3666259544d194db83dd51 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 16 Jun 2020 08:31:27 +0000 Subject: [PATCH 11/44] fix --- extensions/metadata_exchange/plugin.cc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 7c49a85cd7b..9f1a3754c27 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -127,16 +127,21 @@ bool PluginRootContext::configure(size_t configuration_size) { 0, configuration_size); // Parse configuration JSON string. auto j = ::Wasm::Common::JsonParse(configuration_data->view()); - if (!j.is_object()) { + if (!j.has_value()) { LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", - configuration_data->view(), j.dump())); + configuration_data->view())); + return false; + } + if (!j->is_object()) { + LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", + configuration_data->view(), j->dump())); return false; } - auto max_peer_cache_size = - ::Wasm::Common::JsonGetField(j, "max_peer_cache_size"); - if (max_peer_cache_size.has_value()) { - max_peer_cache_size_ = max_peer_cache_size.value(); + auto max_peer_cache_size_result = + ::Wasm::Common::JsonGetField(j.value(), "max_peer_cache_size"); + if (max_peer_cache_size_result.first.has_value()) { + max_peer_cache_size_ = max_peer_cache_size_result.first.value(); } return true; } From 9b3f4f63c60fdb5d8c4451ff6e9b7bb2ed73433c Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 16 Jun 2020 13:44:15 +0000 Subject: [PATCH 12/44] fix --- extensions/common/json_util.cc | 83 ++++++++-------- extensions/common/json_util.h | 76 ++++++++++----- extensions/metadata_exchange/plugin.cc | 7 +- extensions/stats/plugin.cc | 40 ++++---- src/envoy/http/jwt_auth/jwt.cc | 130 +++++++++++++------------ src/envoy/http/jwt_auth/jwt_test.cc | 61 ++++++------ 6 files changed, 222 insertions(+), 175 deletions(-) diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index f5d2f0841c8..63d850f9f95 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -20,87 +20,94 @@ namespace Wasm { namespace Common { -absl::optional JsonParse(absl::string_view str) { +void JsonParser::parse(absl::string_view str) { + reset(); const auto result = JsonObject::parse(str, nullptr, false); if (result.empty() || result.is_discarded()) { - return absl::nullopt; + detail_ = JsonParserResultDetail::PARSE_ERROR; + return; } - return result; + detail_ = JsonParserResultDetail::OK; + object_ = result; + return; +} + +void JsonParser::reset() { + object_ = JsonObject{}; + detail_ = JsonParserResultDetail::EMPTY; } template <> -std::pair, absl::optional> -JsonValueAs(const JsonObject& j) { +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j) { if (j.is_number()) { - return std::make_pair(j.get(), absl::nullopt); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } else if (j.is_string()) { int64_t result = 0; if (absl::SimpleAtoi(j.get_ref(), &result)) { - return std::make_pair(result, absl::nullopt); + return std::make_pair(result, JsonParserResultDetail::OK); + } else { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::INVALID_VALUE); } } - return std::make_pair(absl::nullopt, - JsonParseError{JsonParserErrorDetail::TYPE_ERROR, - "Type must be string or number"}); + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -std::pair, absl::optional> +std::pair, JsonParserResultDetail> JsonValueAs(const JsonObject& j) { if (j.is_number()) { - return std::make_pair(j.get(), absl::nullopt); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } else if (j.is_string()) { uint64_t result = 0; if (absl::SimpleAtoi(j.get_ref(), &result)) { - return std::make_pair(result, absl::nullopt); + return std::make_pair(result, JsonParserResultDetail::OK); + } else { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::INVALID_VALUE); } } - return std::make_pair(absl::nullopt, - JsonParseError{JsonParserErrorDetail::TYPE_ERROR, - "Type must be string or number"}); + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -std::pair, absl::optional> +std::pair, JsonParserResultDetail> JsonValueAs(const JsonObject& j) { if (j.is_string()) { return std::make_pair(absl::string_view(j.get_ref()), - absl::nullopt); + JsonParserResultDetail::OK); } - return std::make_pair( - absl::nullopt, - JsonParseError{JsonParserErrorDetail::TYPE_ERROR, "Type must be string"}); + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -std::pair, absl::optional> +std::pair, JsonParserResultDetail> JsonValueAs(const JsonObject& j) { if (j.is_string()) { - return std::make_pair(j.get(), absl::nullopt); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } - return std::make_pair( - absl::nullopt, - JsonParseError{JsonParserErrorDetail::TYPE_ERROR, "Type must be string"}); + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -std::pair, absl::optional> -JsonValueAs(const JsonObject& j) { +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j) { if (j.is_boolean()) { - return std::make_pair(j.get(), absl::nullopt); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } if (j.is_string()) { const std::string& v = j.get_ref(); if (v == "true") { - return std::make_pair(true, absl::nullopt); + return std::make_pair(true, JsonParserResultDetail::OK); } else if (v == "false") { - return std::make_pair(false, absl::nullopt); + return std::make_pair(false, JsonParserResultDetail::OK); + } else { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::INVALID_VALUE); } } - return std::make_pair( - absl::nullopt, - JsonParseError{JsonParserErrorDetail::TYPE_ERROR, - "Type must be boolean or string(true/false)"}); + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> @@ -153,11 +160,11 @@ bool JsonObjectIterate(const JsonObject& j, absl::string_view field, return false; } for (const auto& elt : it.value().items()) { - auto result = JsonValueAs(elt.key()); - if (!result.first.has_value()) { + auto json_value = JsonValueAs(elt.key()); + if (json_value.second != JsonParserResultDetail::OK) { return false; } - if (!visitor(result.first.value())) { + if (!visitor(json_value.first.value())) { return false; } } diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 138a1179b13..9100566644c 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -15,6 +15,9 @@ #pragma once +#include +#include + #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "extensions/common/nlohmann_json.hpp" @@ -31,57 +34,84 @@ using JsonParserException = ::nlohmann::detail::exception; using JsonParserOutOfRangeException = ::nlohmann::detail::out_of_range; using JsonParserTypeErrorException = ::nlohmann::detail::type_error; -enum JsonParserErrorDetail { +enum JsonParserResultDetail { + EMPTY, + OK, OUT_OF_RANGE, TYPE_ERROR, PARSE_ERROR, + INVALID_VALUE, }; -struct JsonParseError { - JsonParserErrorDetail error_detail_; - std::string message_; -}; +class JsonParser { + public: + void parse(absl::string_view str); + JsonObject object() { return object_; }; + const JsonParserResultDetail& detail() { return detail_; } -// Parse JSON. Returns the discarded value if fails. -absl::optional JsonParse(absl::string_view str); + private: + void reset(); + + JsonParserResultDetail detail_{JsonParserResultDetail::EMPTY}; + JsonObject object_{}; +}; template -std::pair, absl::optional> JsonValueAs( +std::pair, JsonParserResultDetail> JsonValueAs( const JsonObject&) { static_assert(true, "Unsupported Type"); } template <> -std::pair, absl::optional> +std::pair, JsonParserResultDetail> JsonValueAs(const JsonObject& j); template <> -std::pair, absl::optional> +std::pair, JsonParserResultDetail> JsonValueAs(const JsonObject& j); template <> -std::pair, absl::optional> -JsonValueAs(const JsonObject& j); +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j); template <> -std::pair, absl::optional> +std::pair, JsonParserResultDetail> JsonValueAs(const JsonObject& j); template <> -std::pair, absl::optional> -JsonValueAs(const JsonObject& j); +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j); + +template +class JsonGetField { + public: + JsonGetField(const JsonObject& j, absl::string_view field); + const JsonParserResultDetail& detail() { return detail_; } + T fetch() { return object_; } + T fetch_or(T v) { + if (detail_ != JsonParserResultDetail::OK) + return v; + else + return object_; + }; + + private: + JsonParserResultDetail detail_; + T object_; +}; -template -std::pair, absl::optional> JsonGetField( - const JsonObject& j, absl::string_view field) { +template +JsonGetField::JsonGetField(const JsonObject& j, absl::string_view field) { auto it = j.find(field); if (it == j.end()) { - return std::make_pair( - absl::nullopt, - JsonParseError{JsonParserErrorDetail::OUT_OF_RANGE, - "Key " + std::string(field) + " is not found"}); + detail_ = JsonParserResultDetail::OUT_OF_RANGE; + return; + } + auto value = JsonValueAs(it.value()); + detail_ = value.second; + if (value.first.has_value()) { + object_ = value.first.value(); } - return JsonValueAs(it.value()); } // Iterate over an optional array field. diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 9f1a3754c27..a9952533a1a 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -138,10 +138,11 @@ bool PluginRootContext::configure(size_t configuration_size) { return false; } - auto max_peer_cache_size_result = + auto max_peer_cache_size_field = ::Wasm::Common::JsonGetField(j.value(), "max_peer_cache_size"); - if (max_peer_cache_size_result.first.has_value()) { - max_peer_cache_size_ = max_peer_cache_size_result.first.value(); + if (max_peer_cache_size_value.detail() != + Wasm::Common::JsonParserResultDetail::OK) { + max_peer_cache_size_ = max_peer_cache_size_value.fetch(); } return true; } diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index 44789677a4f..bb3537fa6f7 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -45,7 +45,7 @@ using ::nlohmann::json; using ::Wasm::Common::JsonArrayIterate; using ::Wasm::Common::JsonGetField; using ::Wasm::Common::JsonObjectIterate; -using ::Wasm::Common::JsonValueAs; +using ::Wasm::Common::JsonValue; namespace { @@ -266,9 +266,9 @@ bool PluginRootContext::initializeDimensions(const json& j) { if (!JsonArrayIterate( j, "definitions", [&](const json& definition) -> bool { auto name = - JsonGetField(definition, "name").value_or(""); + JsonGetField(definition, "name").fetch_or(""); auto value = - JsonGetField(definition, "value").value_or(""); + JsonGetField(definition, "value").fetch_or(""); if (name.empty() || value.empty()) { LOG_WARN("empty name or value in 'definitions'"); return false; @@ -292,7 +292,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { }; factory.type = MetricType::Counter; auto type = JsonGetField(definition, "type") - .value_or(""); + .fetch_or(""); if (type == "GAUGE") { factory.type = MetricType::Gauge; } else if (type == "HISTOGRAM") { @@ -317,7 +317,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { } std::sort(tags.begin(), tags.end()); - auto name = JsonGetField(metric, "name").value_or(""); + auto name = JsonGetField(metric, "name").fetch_or(""); for (const auto& factory_it : factories) { if (!name.empty() && name != factory_it.first) { continue; @@ -327,13 +327,14 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process tag deletions. if (!JsonArrayIterate( metric, "tags_to_remove", [&](const json& tag) -> bool { - auto tag_string = JsonValueAs(tag, false); - if (!tag_string.has_value()) { + auto tag_string = JsonValueAs(tag); + if (tag_string.second != + Wasm::Common::JsonParserResultDetail::OK) { LOG_WARN( absl::StrCat("unexpected tag to remove", tag.dump())); return false; } - auto it = indexes.find(tag_string.value()); + auto it = indexes.find(tag_string.first.value()); if (it != indexes.end()) { it->second = {}; } @@ -346,12 +347,13 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process tag overrides. for (const auto& tag : tags) { auto expr_string = - JsonValueAs(metric["dimensions"][tag], false); - if (!expr_string.has_value()) { + JsonValue(metric["dimensions"][tag]); + if (expr_string.second != + Wasm::Common::JsonParserResultDetail::OK) { LOG_WARN("failed to parse 'dimensions' value"); return false; } - auto expr_index = addStringExpression(expr_string.value()); + auto expr_index = addStringExpression(expr_string.first.value()); Optional value = {}; if (expr_index.has_value()) { value = count_standard_labels + expr_index.value(); @@ -381,11 +383,11 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Instantiate stat factories using the new dimensions auto field_separator = JsonGetField(j, "field_separator") - .value_or(default_field_separator); + .fetch_or(default_field_separator); auto value_separator = JsonGetField(j, "value_separator") - .value_or(default_value_separator); + .fetch_or(default_value_separator); auto stat_prefix = - JsonGetField(j, "stat_prefix").value_or(default_stat_prefix); + JsonGetField(j, "stat_prefix").fetch_or(default_stat_prefix); // prepend "_" to opt out of automatic namespacing // If "_" is not prepended, envoy_ is automatically added by prometheus @@ -438,7 +440,9 @@ bool PluginRootContext::configure(size_t configuration_size) { outbound_ = ::Wasm::Common::TrafficDirection::Outbound == ::Wasm::Common::getTrafficDirection(); - auto j = ::Wasm::Common::JsonParse(configuration_data->view()); + auto json_parser = ::Wasm::Common::JsonParser(); + json_parser.parse(configuration_data->view()); + auto j = json_parser.object(); if (!j.is_object()) { LOG_WARN(absl::StrCat("cannot parse configuration as JSON: ", configuration_data->view())); @@ -453,9 +457,9 @@ bool PluginRootContext::configure(size_t configuration_size) { peer_metadata_key_ = ::Wasm::Common::kDownstreamMetadataKey; } - debug_ = JsonGetField(j, "debug").value_or(false); + debug_ = JsonGetField(j, "debug").fetch_or(false); use_host_header_fallback_ = - !JsonGetField(j, "disable_host_header_fallback").value_or(false); + !JsonGetField(j, "disable_host_header_fallback").fetch_or(false); if (!initializeDimensions(j)) { return false; @@ -463,7 +467,7 @@ bool PluginRootContext::configure(size_t configuration_size) { uint32_t tcp_report_duration_milis = kDefaultTCPReportDurationMilliseconds; auto tcp_reporting_duration = - JsonGetField(j, "tcp_reporting_duration"); + JsonGetField(j, "tcp_reporting_duration").first; absl::Duration duration; if (tcp_reporting_duration.has_value()) { if (absl::ParseDuration(tcp_reporting_duration.value(), &duration)) { diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 1a4b267daaa..df4ec1cfe25 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -252,15 +252,16 @@ Jwt::Jwt(const std::string &jwt) { } // Parse header json + auto parser = Wasm::Common::JsonParser(); header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); header_str_ = Base64UrlDecode(header_str_base64url_); - auto parse_result = Wasm::Common::JsonParse(header_str_); - if (!parse_result.has_value()) { + parser.parse(header_str_); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); return; } else { - header_ = parse_result.value(); + header_ = parser.object(); } // Header should contain "alg". @@ -269,12 +270,12 @@ Jwt::Jwt(const std::string &jwt) { return; } - auto alg_field_opt = Wasm::Common::JsonGetField(header_, "alg"); - if (!alg_field_opt.first.has_value()) { + auto alg_field = Wasm::Common::JsonGetField(header_, "alg"); + if (alg_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_HEADER_BAD_ALG); return; } - alg_ = alg_field_opt.first.value(); + alg_ = alg_field.fetch(); if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && alg_ != "RS512") { @@ -283,40 +284,37 @@ Jwt::Jwt(const std::string &jwt) { } // Header may contain "kid", which should be a string if exists. - auto kid_field_opt = Wasm::Common::JsonGetField(header_, "kid"); - if (kid_field_opt.second.has_value()) { - if (kid_field_opt.second->error_detail_ == - Wasm::Common::JsonParserErrorDetail::TYPE_ERROR) { + auto kid_field = Wasm::Common::JsonGetField(header_, "kid"); + if (kid_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { + if (kid_field.detail() == + Wasm::Common::JsonParserResultDetail::TYPE_ERROR) { UpdateStatus(Status::JWT_HEADER_BAD_KID); return; - } else if (kid_field_opt.second->error_detail_ == - Wasm::Common::JsonParserErrorDetail::OUT_OF_RANGE) { + } else if (kid_field.detail() == + Wasm::Common::JsonParserResultDetail::OUT_OF_RANGE) { kid_ = ""; } else { return; } } else { - kid_ = kid_field_opt.first.value(); + kid_ = kid_field.fetch(); } // Parse payload json payload_str_base64url_ = std::string(jwt_split[1].begin(), jwt_split[1].end()); payload_str_ = Base64UrlDecode(payload_str_base64url_); - auto payload_parse_result = Wasm::Common::JsonParse(payload_str_); - if (!payload_parse_result.has_value()) { + parser.parse(payload_str_); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; } else { - payload_ = payload_parse_result.value(); + payload_ = parser.object(); } - iss_ = Wasm::Common::JsonGetField(payload_, "iss") - .first.value_or(""); - sub_ = Wasm::Common::JsonGetField(payload_, "sub") - .first.value_or(""); - exp_ = - Wasm::Common::JsonGetField(payload_, "exp").first.value_or(0); + iss_ = Wasm::Common::JsonGetField(payload_, "iss").fetch_or(""); + sub_ = Wasm::Common::JsonGetField(payload_, "sub").fetch_or(""); + exp_ = Wasm::Common::JsonGetField(payload_, "exp").fetch_or(0); // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. @@ -325,12 +323,12 @@ Jwt::Jwt(const std::string &jwt) { aud_.emplace_back(obj); return true; })) { - auto aud_opt = Wasm::Common::JsonGetField(payload_, "aud"); - if (!aud_opt.first.has_value()) { + auto aud_field = Wasm::Common::JsonGetField(payload_, "aud"); + if (aud_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; } - aud_.emplace_back(aud_opt.first.value()); + aud_.emplace_back(aud_field.fetch()); } // Set up signature @@ -489,13 +487,14 @@ void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { keys_.clear(); + auto parser = Wasm::Common::JsonParser(); Wasm::Common::JsonObject jwks_json; - auto jwks_parse_result = Wasm::Common::JsonParse(pkey_jwks); - if (!jwks_parse_result.has_value()) { + parser.parse(pkey_jwks); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWK_PARSE_ERROR); return; } else { - jwks_json = jwks_parse_result.value(); + jwks_json = parser.object(); } std::vector> key_refs; @@ -530,16 +529,16 @@ bool Pubkeys::ExtractPubkeyFromJwk(const Wasm::Common::JsonObject &jwk_json) { // Check "kty" parameter, it should exist. // https://tools.ietf.org/html/rfc7517#section-4.1 // If "kty" is missing, getString throws an exception. - auto kty_opt = Wasm::Common::JsonGetField(jwk_json, "kty"); - if (!kty_opt.first.has_value()) { + auto kty_field = Wasm::Common::JsonGetField(jwk_json, "kty"); + if (kty_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } // Extract public key according to "kty" value. // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty_opt.first.value() == "EC") { + if (kty_field.fetch() == "EC") { return ExtractPubkeyFromJwkEC(jwk_json); - } else if (kty_opt.first.value() == "RSA") { + } else if (kty_field.fetch() == "RSA") { return ExtractPubkeyFromJwkRSA(jwk_json); } @@ -553,36 +552,36 @@ bool Pubkeys::ExtractPubkeyFromJwkRSA( // "kid" and "alg" are optional, if they do not exist, set them to "". // https://tools.ietf.org/html/rfc7517#page-8 - auto kid_opt = Wasm::Common::JsonGetField(jwk_json, "kid"); - if (kid_opt.first.has_value()) { - pubkey->kid_ = kid_opt.first.value(); + auto kid_field = Wasm::Common::JsonGetField(jwk_json, "kid"); + if (kid_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { + pubkey->kid_ = kid_field.fetch(); pubkey->kid_specified_ = true; } - auto alg_opt = Wasm::Common::JsonGetField(jwk_json, "alg"); - if (alg_opt.first.has_value()) { + auto alg_field = Wasm::Common::JsonGetField(jwk_json, "alg"); + if (alg_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { // Allow only "RS" prefixed algorithms. // https://tools.ietf.org/html/rfc7518#section-3.1 - if (!(alg_opt.first.value() == "RS256" || - alg_opt.first.value() == "RS384" || - alg_opt.first.value() == "RS512")) { + if (!(alg_field.fetch() == "RS256" || alg_field.fetch() == "RS384" || + alg_field.fetch() == "RS512")) { return false; } - pubkey->alg_ = alg_opt.first.value(); + pubkey->alg_ = alg_field.fetch(); pubkey->alg_specified_ = true; } - auto pubkey_kty_opt = + auto pubkey_kty_field = Wasm::Common::JsonGetField(jwk_json, "kty"); - assert(pubkey_kty_opt.first.has_value()); - pubkey->kty_ = pubkey_kty_opt.first.value(); - auto n_str_opt = Wasm::Common::JsonGetField(jwk_json, "n"); - auto e_str_opt = Wasm::Common::JsonGetField(jwk_json, "e"); - if (n_str_opt.second.has_value() || n_str_opt.second.has_value()) { + assert(pubkey_kty_field.detail() == Wasm::Common::JsonParserResultDetail::OK); + pubkey->kty_ = pubkey_kty_field.fetch(); + auto n_str_field = Wasm::Common::JsonGetField(jwk_json, "n"); + auto e_str_field = Wasm::Common::JsonGetField(jwk_json, "e"); + if (n_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK || + e_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } - n_str = n_str_opt.first.value(); - e_str = e_str_opt.first.value(); + n_str = n_str_field.fetch(); + e_str = e_str_field.fetch(); EvpPkeyGetter e; pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); @@ -601,32 +600,35 @@ bool Pubkeys::ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject &jwk_json) { // "kid" and "alg" are optional, if they do not exist, set them to "". // https://tools.ietf.org/html/rfc7517#page-8 - auto kid_opt = Wasm::Common::JsonGetField(jwk_json, "kid"); - if (kid_opt.first.has_value()) { - pubkey->kid_ = kid_opt.first.value(); + auto kid_field = Wasm::Common::JsonGetField(jwk_json, "kid"); + if (kid_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { + pubkey->kid_ = kid_field.fetch(); pubkey->kid_specified_ = true; } - auto alg_opt = Wasm::Common::JsonGetField(jwk_json, "alg"); - if (alg_opt.first.has_value()) { - if (alg_opt.first.value() != "ES256") { + auto alg_field = Wasm::Common::JsonGetField(jwk_json, "alg"); + if (alg_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { + // Allow only "RS" prefixed algorithms. + // https://tools.ietf.org/html/rfc7518#section-3.1 + if (alg_field.fetch() != "ES256") { return false; } - pubkey->alg_ = alg_opt.first.value(); + pubkey->alg_ = alg_field.fetch(); pubkey->alg_specified_ = true; } - auto pubkey_kty_opt = + auto pubkey_kty_field = Wasm::Common::JsonGetField(jwk_json, "kty"); - assert(pubkey_kty_opt.first.has_value()); - pubkey->kty_ = pubkey_kty_opt.first.value(); - auto x_str_opt = Wasm::Common::JsonGetField(jwk_json, "x"); - auto y_str_opt = Wasm::Common::JsonGetField(jwk_json, "y"); - if (x_str_opt.second.has_value() || y_str_opt.second.has_value()) { + assert(pubkey_kty_field.detail() == Wasm::Common::JsonParserResultDetail::OK); + pubkey->kty_ = pubkey_kty_field.fetch(); + auto x_str_field = Wasm::Common::JsonGetField(jwk_json, "x"); + auto y_str_field = Wasm::Common::JsonGetField(jwk_json, "y"); + if (x_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK || + y_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } - x_str = x_str_opt.first.value(); - y_str = y_str_opt.first.value(); + x_str = x_str_field.fetch(); + y_str = y_str_field.fetch(); EvpPkeyGetter e; pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); diff --git a/src/envoy/http/jwt_auth/jwt_test.cc b/src/envoy/http/jwt_auth/jwt_test.cc index ac7ca03f8af..13e6ad91d14 100644 --- a/src/envoy/http/jwt_auth/jwt_test.cc +++ b/src/envoy/http/jwt_auth/jwt_test.cc @@ -537,6 +537,8 @@ class JwtTest : public testing::Test { EXPECT_TRUE(EqJson(*payload, jwt.Payload())); } } + + Wasm::Common::JsonParser parser_; }; // Test cases w/ PEM-formatted public key @@ -547,20 +549,21 @@ class JwtTestPem : public JwtTest { }; TEST_F(JwtTestPem, OK) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); - DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, &payload.value()); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, OKWithAlgRs384) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); - DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, - &payload.value()); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, OKWithAlgRs512) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); - DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, - &payload.value()); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, MultiAudiences) { @@ -674,13 +677,14 @@ class JwtTestJwks : public JwtTest { }; TEST_F(JwtTestJwks, OkNoKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); - DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, - &payload.value()); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"RS256\","; std::string pubkey_no_alg = ds.kPublicKeyRSA; @@ -689,8 +693,7 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, - &payload.value()); + DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, &payload); // Remove "kid" claim from public key. std::string kid_claim1 = @@ -702,20 +705,21 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, - &payload.value()); + DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, OkNoKidLogExp) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadLongExp); + parser_.parse(ds.kJwtPayloadLongExp); + auto payload = parser_.object(); DoTest(ds.kJwtNoKidLongExp, ds.kPublicKeyRSA, "jwks", true, Status::OK, - &payload.value()); + &payload); } TEST_F(JwtTestJwks, OkCorrectKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); DoTest(ds.kJwtWithCorrectKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, - &payload.value()); + &payload); } TEST_F(JwtTestJwks, IncorrectKid) { @@ -758,17 +762,18 @@ TEST_F(JwtTestJwks, JwkBadPublicKey) { } TEST_F(JwtTestJwks, OkTokenJwkEC) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC); + parser_.parse(ds.kJwtPayloadEC); + auto payload = parser_.object(); // ES256-signed token with kid specified. - DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, - &payload.value()); + DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, &payload); // ES256-signed token without kid specified. DoTest(ds.kTokenECNoKid, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, - &payload.value()); + &payload); } TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { - auto payload = Wasm::Common::JsonParse(ds.kJwtPayloadEC); + parser_.parse(ds.kJwtPayloadEC); + auto payload = parser_.object(); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"ES256\","; std::string pubkey_no_alg = ds.kPublicKeyJwkEC; @@ -777,8 +782,7 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, - &payload.value()); + DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, &payload); // Remove "kid" claim from public key. std::string kid_claim1 = ",\"kid\": \"abc\""; @@ -788,8 +792,7 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, - &payload.value()); + DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, NonExistKidEC) { From 087120b7d01adc8ab518e8b4a107ee2d9b964fe6 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 16 Jun 2020 14:00:43 +0000 Subject: [PATCH 13/44] fix --- extensions/metadata_exchange/plugin.cc | 16 +++++++++------- extensions/stats/plugin.cc | 14 +++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index a9952533a1a..5d4c763e4a6 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -126,23 +126,25 @@ bool PluginRootContext::configure(size_t configuration_size) { auto configuration_data = getBufferBytes(WasmBufferType::PluginConfiguration, 0, configuration_size); // Parse configuration JSON string. - auto j = ::Wasm::Common::JsonParse(configuration_data->view()); - if (!j.has_value()) { + auto parser = ::Wasm::Common::JsonParser(); + parser.parse(configuration_data->view()); + if (parser.detail() != ::Wasm::Common::JsonParserResultDetail::OK) { LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", configuration_data->view())); return false; } - if (!j->is_object()) { + auto j = parser.object(); + if (!j.is_object()) { LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", - configuration_data->view(), j->dump())); + configuration_data->view(), j.dump())); return false; } auto max_peer_cache_size_field = - ::Wasm::Common::JsonGetField(j.value(), "max_peer_cache_size"); - if (max_peer_cache_size_value.detail() != + ::Wasm::Common::JsonGetField(j, "max_peer_cache_size"); + if (max_peer_cache_size_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - max_peer_cache_size_ = max_peer_cache_size_value.fetch(); + max_peer_cache_size_ = max_peer_cache_size_field.fetch(); } return true; } diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index bb3537fa6f7..adda1757ff1 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -45,7 +45,7 @@ using ::nlohmann::json; using ::Wasm::Common::JsonArrayIterate; using ::Wasm::Common::JsonGetField; using ::Wasm::Common::JsonObjectIterate; -using ::Wasm::Common::JsonValue; +using ::Wasm::Common::JsonValueAs; namespace { @@ -347,7 +347,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process tag overrides. for (const auto& tag : tags) { auto expr_string = - JsonValue(metric["dimensions"][tag]); + JsonValueAs(metric["dimensions"][tag]); if (expr_string.second != Wasm::Common::JsonParserResultDetail::OK) { LOG_WARN("failed to parse 'dimensions' value"); @@ -466,15 +466,15 @@ bool PluginRootContext::configure(size_t configuration_size) { } uint32_t tcp_report_duration_milis = kDefaultTCPReportDurationMilliseconds; - auto tcp_reporting_duration = - JsonGetField(j, "tcp_reporting_duration").first; + auto tcp_reporting_duration_field = + JsonGetField(j, "tcp_reporting_duration"); absl::Duration duration; - if (tcp_reporting_duration.has_value()) { - if (absl::ParseDuration(tcp_reporting_duration.value(), &duration)) { + if (tcp_reporting_duration_field.detail() == ::Wasm::Common::JsonParserResultDetail::OK) { + if (absl::ParseDuration(tcp_reporting_duration_field.fetch(), &duration)) { tcp_report_duration_milis = uint32_t(duration / absl::Milliseconds(1)); } else { LOG_WARN(absl::StrCat("failed to parse 'tcp_reporting_duration': ", - tcp_reporting_duration.value())); + tcp_reporting_duration_field.fetch())); } } proxy_set_tick_period_milliseconds(tcp_report_duration_milis); From c14a931cd31cec96a398d35bec391dd694924f1d Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 16 Jun 2020 14:04:37 +0000 Subject: [PATCH 14/44] format --- extensions/stats/plugin.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index adda1757ff1..53c05e9bf85 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -469,7 +469,8 @@ bool PluginRootContext::configure(size_t configuration_size) { auto tcp_reporting_duration_field = JsonGetField(j, "tcp_reporting_duration"); absl::Duration duration; - if (tcp_reporting_duration_field.detail() == ::Wasm::Common::JsonParserResultDetail::OK) { + if (tcp_reporting_duration_field.detail() == + ::Wasm::Common::JsonParserResultDetail::OK) { if (absl::ParseDuration(tcp_reporting_duration_field.fetch(), &duration)) { tcp_report_duration_milis = uint32_t(duration / absl::Milliseconds(1)); } else { From fb8744e2bb3175a8efb4ab132c2428b6f77795cf Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 02:37:06 +0000 Subject: [PATCH 15/44] fix --- extensions/metadata_exchange/plugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 5d4c763e4a6..824dd89f254 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -142,7 +142,7 @@ bool PluginRootContext::configure(size_t configuration_size) { auto max_peer_cache_size_field = ::Wasm::Common::JsonGetField(j, "max_peer_cache_size"); - if (max_peer_cache_size_field.detail() != + if (max_peer_cache_size_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { max_peer_cache_size_ = max_peer_cache_size_field.fetch(); } From e598b67b1a8dfea1c946ad91db35c0d749113a65 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 03:56:31 +0000 Subject: [PATCH 16/44] fix --- extensions/common/json_util.cc | 22 ---------- extensions/common/json_util.h | 12 ------ extensions/stats/plugin.cc | 76 +++++++++++++++++----------------- src/envoy/http/jwt_auth/jwt.cc | 13 ++++-- 4 files changed, 46 insertions(+), 77 deletions(-) diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index 63d850f9f95..d5d0193345c 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -110,7 +110,6 @@ std::pair, JsonParserResultDetail> JsonValueAs( return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } -template <> bool JsonArrayIterate( const JsonObject& j, absl::string_view field, const std::function& visitor) { @@ -122,27 +121,6 @@ bool JsonArrayIterate( return false; } for (const auto& elt : it.value().items()) { - assert(elt.value().is_object()); - if (!visitor(elt.value())) { - return false; - } - } - return true; -} - -template <> -bool JsonArrayIterate( - const JsonObject& j, absl::string_view field, - const std::function& visitor) { - auto it = j.find(field); - if (it == j.end()) { - return true; - } - if (!it.value().is_array()) { - return false; - } - for (const auto& elt : it.value().items()) { - assert(elt.value().is_string()); if (!visitor(elt.value())) { return false; } diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 9100566644c..02dcb7e0d94 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -117,22 +117,10 @@ JsonGetField::JsonGetField(const JsonObject& j, absl::string_view field) { // Iterate over an optional array field. // Returns false if set and not an array, or any of the visitor calls returns // false. -template -bool JsonArrayIterate(const JsonObject&, absl::string_view, - const std::function&) { - static_assert(true, "Unsupported type"); -} - -template <> bool JsonArrayIterate( const JsonObject& j, absl::string_view field, const std::function& visitor); -template <> -bool JsonArrayIterate( - const JsonObject& j, absl::string_view field, - const std::function& visitor); - // Iterate over an optional object field key set. // Returns false if set and not an object, or any of the visitor calls returns // false. diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index 53c05e9bf85..ca6ad40983a 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -263,48 +263,46 @@ bool PluginRootContext::initializeDimensions(const json& j) { } // Process the metric definitions (overriding existing). - if (!JsonArrayIterate( - j, "definitions", [&](const json& definition) -> bool { - auto name = - JsonGetField(definition, "name").fetch_or(""); - auto value = - JsonGetField(definition, "value").fetch_or(""); - if (name.empty() || value.empty()) { - LOG_WARN("empty name or value in 'definitions'"); - return false; - } - auto token = addIntExpression(value); - if (!token.has_value()) { - LOG_WARN(absl::StrCat("failed to construct expression: ", value)); - return false; - } - auto& factory = factories[name]; - factory.name = name; - factory.extractor = - [token, name, - value](const ::Wasm::Common::RequestInfo&) -> uint64_t { - int64_t result = 0; - if (!evaluateExpression(token.value(), &result)) { - LOG_TRACE(absl::StrCat("Failed to evaluate expression: <", - value, "> for dimension:<", name, ">")); - } - return result; - }; - factory.type = MetricType::Counter; - auto type = JsonGetField(definition, "type") - .fetch_or(""); - if (type == "GAUGE") { - factory.type = MetricType::Gauge; - } else if (type == "HISTOGRAM") { - factory.type = MetricType::Histogram; - } - return true; - })) { + if (!JsonArrayIterate(j, "definitions", [&](const json& definition) -> bool { + auto name = JsonGetField(definition, "name").fetch_or(""); + auto value = + JsonGetField(definition, "value").fetch_or(""); + if (name.empty() || value.empty()) { + LOG_WARN("empty name or value in 'definitions'"); + return false; + } + auto token = addIntExpression(value); + if (!token.has_value()) { + LOG_WARN(absl::StrCat("failed to construct expression: ", value)); + return false; + } + auto& factory = factories[name]; + factory.name = name; + factory.extractor = + [token, name, + value](const ::Wasm::Common::RequestInfo&) -> uint64_t { + int64_t result = 0; + if (!evaluateExpression(token.value(), &result)) { + LOG_TRACE(absl::StrCat("Failed to evaluate expression: <", value, + "> for dimension:<", name, ">")); + } + return result; + }; + factory.type = MetricType::Counter; + auto type = + JsonGetField(definition, "type").fetch_or(""); + if (type == "GAUGE") { + factory.type = MetricType::Gauge; + } else if (type == "HISTOGRAM") { + factory.type = MetricType::Histogram; + } + return true; + })) { LOG_WARN("failed to parse 'definitions'"); } // Process the dimension overrides. - if (!JsonArrayIterate(j, "metrics", [&](const json& metric) -> bool { + if (!JsonArrayIterate(j, "metrics", [&](const json& metric) -> bool { // Sort tag override tags to keep the order of tags deterministic. std::vector tags; if (!JsonObjectIterate(metric, "dimensions", @@ -325,7 +323,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { auto& indexes = metric_indexes[factory_it.first]; // Process tag deletions. - if (!JsonArrayIterate( + if (!JsonArrayIterate( metric, "tags_to_remove", [&](const json& tag) -> bool { auto tag_string = JsonValueAs(tag); if (tag_string.second != diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index df4ec1cfe25..23daec354a0 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -318,9 +318,14 @@ Jwt::Jwt(const std::string &jwt) { // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. - if (!Wasm::Common::JsonArrayIterate( - payload_, "aud", [&](const std::string &obj) -> bool { - aud_.emplace_back(obj); + if (!Wasm::Common::JsonArrayIterate( + payload_, "aud", [&](const Wasm::Common::JsonObject &obj) -> bool { + auto str_obj_result = Wasm::Common::JsonValueAs(obj); + if (str_obj_result.second != + Wasm::Common::JsonParserResultDetail::OK) { + return false; + } + aud_.emplace_back(str_obj_result.first.value()); return true; })) { auto aud_field = Wasm::Common::JsonGetField(payload_, "aud"); @@ -504,7 +509,7 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { return; } - if (!Wasm::Common::JsonArrayIterate( + if (!Wasm::Common::JsonArrayIterate( jwks_json, "keys", [&](const Wasm::Common::JsonObject &obj) -> bool { key_refs.emplace_back( std::reference_wrapper(obj)); From 39a3bce1e486d97ba6a6847dcf41e79febb13ae0 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 03:56:31 +0000 Subject: [PATCH 17/44] fix --- extensions/authn/BUILD | 169 ++++++ extensions/authn/authenticator_base.cc | 159 ++++++ extensions/authn/authenticator_base.h | 75 +++ extensions/authn/authenticator_base_test.cc | 442 +++++++++++++++ extensions/authn/authn_utils.cc | 229 ++++++++ extensions/authn/authn_utils.h | 70 +++ extensions/authn/authn_utils_test.cc | 351 ++++++++++++ extensions/authn/filter_context.cc | 161 ++++++ extensions/authn/filter_context.h | 120 ++++ extensions/authn/filter_context_test.cc | 129 +++++ .../authn/http_filter_integration_test.cc | 199 +++++++ extensions/authn/http_filter_test.cc | 289 ++++++++++ extensions/authn/origin_authenticator.cc | 132 +++++ extensions/authn/origin_authenticator.h | 54 ++ extensions/authn/origin_authenticator_test.cc | 522 ++++++++++++++++++ extensions/authn/peer_authenticator.cc | 87 +++ extensions/authn/peer_authenticator.h | 54 ++ extensions/authn/peer_authenticator_test.cc | 347 ++++++++++++ extensions/authn/plugin.cc | 43 ++ extensions/authn/plugin.h | 146 +++++ .../authn/sample/APToken/APToken-example1.jwt | 1 + .../authn/sample/APToken/aptoken-envoy.conf | 118 ++++ extensions/authn/sample/APToken/guide.txt | 18 + extensions/authn/test_utils.h | 54 ++ extensions/common/json_util.cc | 15 + extensions/common/json_util.h | 5 + extensions/stats/plugin.cc | 10 + src/envoy/http/jwt_auth/jwt.cc | 28 + 28 files changed, 4027 insertions(+) create mode 100644 extensions/authn/BUILD create mode 100644 extensions/authn/authenticator_base.cc create mode 100644 extensions/authn/authenticator_base.h create mode 100644 extensions/authn/authenticator_base_test.cc create mode 100644 extensions/authn/authn_utils.cc create mode 100644 extensions/authn/authn_utils.h create mode 100644 extensions/authn/authn_utils_test.cc create mode 100644 extensions/authn/filter_context.cc create mode 100644 extensions/authn/filter_context.h create mode 100644 extensions/authn/filter_context_test.cc create mode 100644 extensions/authn/http_filter_integration_test.cc create mode 100644 extensions/authn/http_filter_test.cc create mode 100644 extensions/authn/origin_authenticator.cc create mode 100644 extensions/authn/origin_authenticator.h create mode 100644 extensions/authn/origin_authenticator_test.cc create mode 100644 extensions/authn/peer_authenticator.cc create mode 100644 extensions/authn/peer_authenticator.h create mode 100644 extensions/authn/peer_authenticator_test.cc create mode 100644 extensions/authn/plugin.cc create mode 100644 extensions/authn/plugin.h create mode 100644 extensions/authn/sample/APToken/APToken-example1.jwt create mode 100644 extensions/authn/sample/APToken/aptoken-envoy.conf create mode 100644 extensions/authn/sample/APToken/guide.txt create mode 100644 extensions/authn/test_utils.h diff --git a/extensions/authn/BUILD b/extensions/authn/BUILD new file mode 100644 index 00000000000..7553e2546dc --- /dev/null +++ b/extensions/authn/BUILD @@ -0,0 +1,169 @@ +# Copyright 2018 Istio Authors. All Rights Reserved. +# +# Licensed 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. +# +################################################################################ +# + +package(default_visibility = ["//visibility:public"]) + +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_cc_test", + "envoy_cc_test_library", +) + +envoy_cc_library( + name = "authenticator", + srcs = [ + "authenticator_base.cc", + "authn_utils.cc", + "filter_context.cc", + "origin_authenticator.cc", + "peer_authenticator.cc", + ], + hdrs = [ + "authenticator_base.h", + "authn_utils.h", + "filter_context.h", + "origin_authenticator.h", + "peer_authenticator.h", + ], + repository = "@envoy", + deps = [ + "//external:authentication_policy_config_cc_proto", + "//src/envoy/http/jwt_auth:jwt_lib", + "//src/envoy/utils:filter_names_lib", + "//src/envoy/utils:utils_lib", + "//src/istio/authn:context_proto_cc_proto", + "@envoy//source/common/http:headers_lib", + ], +) + +envoy_cc_library( + name = "authn_plugin", + srcs = [ + "plugin.cc", + ], + hdrs = [ + "plugin.h", + ], + repository = "@envoy", + deps = [ + ":authenticator", + "//external:authentication_policy_config_cc_proto", + "//src/envoy/utils:authn_lib", + "//src/envoy/utils:filter_names_lib", + "//src/envoy/utils:utils_lib", + "//src/istio/authn:context_proto_cc_proto", + "@envoy//source/exe:envoy_common_lib", + ], +) + +envoy_cc_test_library( + name = "test_utils", + hdrs = ["test_utils.h"], + repository = "@envoy", + deps = [ + "//src/istio/authn:context_proto_cc_proto", + ], +) + +envoy_cc_test( + name = "filter_context_test", + srcs = ["filter_context_test.cc"], + repository = "@envoy", + deps = [ + ":authenticator", + ":test_utils", + "@envoy//test/mocks/http:http_mocks", + "@envoy//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "authenticator_base_test", + srcs = ["authenticator_base_test.cc"], + repository = "@envoy", + deps = [ + ":authenticator", + ":test_utils", + "//src/envoy/utils:filter_names_lib", + "@envoy//test/mocks/network:network_mocks", + "@envoy//test/mocks/ssl:ssl_mocks", + "@envoy//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "authn_utils_test", + srcs = ["authn_utils_test.cc"], + repository = "@envoy", + deps = [ + ":authenticator", + ":test_utils", + "@envoy//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "peer_authenticator_test", + srcs = ["peer_authenticator_test.cc"], + repository = "@envoy", + deps = [ + ":authenticator", + ":test_utils", + "@envoy//test/mocks/http:http_mocks", + "@envoy//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "origin_authenticator_test", + srcs = ["origin_authenticator_test.cc"], + repository = "@envoy", + deps = [ + ":authenticator", + ":test_utils", + "@envoy//test/mocks/http:http_mocks", + "@envoy//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "http_filter_test", + srcs = ["http_filter_test.cc"], + repository = "@envoy", + deps = [ + ":filter_lib", + ":test_utils", + "//external:authentication_policy_config_cc_proto", + "@envoy//source/common/http:header_map_lib", + "@envoy//test/mocks/http:http_mocks", + "@envoy//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "http_filter_integration_test", + srcs = ["http_filter_integration_test.cc"], + data = glob(["testdata/*"]), + repository = "@envoy", + deps = [ + ":filter_lib", + "//src/envoy/utils:filter_names_lib", + "@envoy//source/common/common:utility_lib", + "@envoy//test/integration:http_protocol_integration_lib", + ], +) diff --git a/extensions/authn/authenticator_base.cc b/extensions/authn/authenticator_base.cc new file mode 100644 index 00000000000..7fff3586e2d --- /dev/null +++ b/extensions/authn/authenticator_base.cc @@ -0,0 +1,159 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "absl/strings/str_cat.h" +#include "src/envoy/http/authn/authenticator_base.h" + +#include "common/common/assert.h" +#include "common/config/metadata.h" +#include "src/envoy/http/authn/authn_utils.h" +#include "src/envoy/utils/filter_names.h" +#include "src/envoy/utils/utils.h" + +using istio::authn::Payload; + +namespace iaapi = istio::authentication::v1alpha1; + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +using proxy_wasm::null_plugin::logTrace; +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; +using proxy_wasm::null_plugin::logWarn; + +#endif // NULL_PLUGIN + +namespace { +// The default header name for an exchanged token +static const std::string kExchangedTokenHeaderName = "ingress-authorization"; + +// Returns whether the header for an exchanged token is found +bool FindHeaderOfExchangedToken(const iaapi::Jwt& jwt) { + return (jwt.jwt_headers_size() == 1 && + LowerCaseString(kExchangedTokenHeaderName) == + LowerCaseString(jwt.jwt_headers(0))); +} + +} // namespace + +AuthenticatorBase::AuthenticatorBase(FilterContext* filter_context) + : filter_context_(*filter_context) {} + +AuthenticatorBase::~AuthenticatorBase() {} + +bool AuthenticatorBase::validateTrustDomain( + const Network::Connection* connection) const { + std::string peer_trust_domain; + if (!Utils::GetTrustDomain(connection, true, &peer_trust_domain)) { + logError("trust domain validation failed: cannot get peer trust domain"); + return false; + } + + std::string local_trust_domain; + if (!Utils::GetTrustDomain(connection, false, &local_trust_domain)) { + logError("trust domain validation failed: cannot get local trust domain"); + return false; + } + + if (peer_trust_domain != local_trust_domain) { + logError(absl::StrCat("trust domain validation failed: peer trust domain {} different from local trust domain {}", peer_trust_domain, local_trust_domain)); + return false; + } + + logDebug("trust domain validation succeeded"); + return true; +} + +bool AuthenticatorBase::validateX509(const iaapi::MutualTls& mtls, + Payload* payload) const { + const Network::Connection* connection = filter_context_.connection(); + if (connection == nullptr) { + // It's wrong if connection does not exist. + logError("validateX509 failed: null connection."); + return false; + } + // Always try to get principal and set to output if available. + const bool has_user = + connection->ssl() != nullptr && + connection->ssl()->peerCertificatePresented() && + Utils::GetPrincipal(connection, true, + payload->mutable_x509()->mutable_user()); + logDebug(absl::StrCat("validateX509 mode {}: ssl={}, has_user={}", iaapi::MutualTls::Mode_Name(mtls.mode()), connection->ssl() != nullptr, has_user)); + + if (!has_user) { + // For plaintext connection, return value depend on mode: + // - PERMISSIVE: always true. + // - STRICT: always false. + switch (mtls.mode()) { + case iaapi::MutualTls::PERMISSIVE: + return true; + case iaapi::MutualTls::STRICT: + return false; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + } + + if (filter_context_.filter_config().skip_validate_trust_domain()) { + logDebug("trust domain validation skipped"); + return true; + } + + // For TLS connection with valid certificate, validate trust domain for both + // PERMISSIVE and STRICT mode. + return validateTrustDomain(connection); +} + +bool AuthenticatorBase::validateJwt(const iaapi::Jwt& jwt, Payload* payload) { + std::string jwt_payload; + if (filter_context()->getJwtPayload(jwt.issuer(), &jwt_payload)) { + std::string payload_to_process = jwt_payload; + std::string original_payload; + if (FindHeaderOfExchangedToken(jwt)) { + if (AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) { + // When the header of an exchanged token is found and the token + // contains the claim of the original payload, the original payload + // is extracted and used as the token payload. + payload_to_process = original_payload; + } else { + // When the header of an exchanged token is found but the token + // does not contain the claim of the original payload, it + // is regarded as an invalid exchanged token. + logError(absl::StrCat("Expect exchanged-token with original payload claim. Received: {}", jwt_payload)); + return false; + } + } + return AuthnUtils::ProcessJwtPayload(payload_to_process, + payload->mutable_jwt()); + } + return false; +} + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif diff --git a/extensions/authn/authenticator_base.h b/extensions/authn/authenticator_base.h new file mode 100644 index 00000000000..69b76c87705 --- /dev/null +++ b/extensions/authn/authenticator_base.h @@ -0,0 +1,75 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "authentication/v1alpha1/policy.pb.h" +#include "src/envoy/http/authn/filter_context.h" +#include "src/istio/authn/context.pb.h" + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +// AuthenticatorBase is the base class for authenticator. It provides functions +// to perform individual authentication methods, which can be used to construct +// compound authentication flow. +class AuthenticatorBase { + public: + AuthenticatorBase(FilterContext* filter_context); + virtual ~AuthenticatorBase(); + + // Perform authentication. + virtual bool run(istio::authn::Payload*) PURE; + + // Validate TLS/MTLS connection and extract authenticated attributes (just + // source user identity for now). Unlike mTLS, TLS connection does not require + // a client certificate. + virtual bool validateX509( + const istio::authentication::v1alpha1::MutualTls& params, + istio::authn::Payload* payload) const; + + // Validates JWT given the jwt params. If JWT is validated, it will extract + // attributes and claims (JwtPayload), returns status SUCCESS. + // Otherwise, returns status FAILED. + virtual bool validateJwt(const istio::authentication::v1alpha1::Jwt& params, + istio::authn::Payload* payload); + + // Mutable accessor to filter context. + FilterContext* filter_context() { return &filter_context_; } + + private: + // Pointer to filter state. Do not own. + FilterContext& filter_context_; + + bool validateTrustDomain(const Network::Connection* connection) const; +}; + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif diff --git a/extensions/authn/authenticator_base_test.cc b/extensions/authn/authenticator_base_test.cc new file mode 100644 index 00000000000..82d355661b0 --- /dev/null +++ b/extensions/authn/authenticator_base_test.cc @@ -0,0 +1,442 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn/authenticator_base.h" + +#include "common/common/base64.h" +#include "common/protobuf/protobuf.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" +#include "gmock/gmock.h" +#include "src/envoy/http/authn/test_utils.h" +#include "src/envoy/utils/filter_names.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/ssl/mocks.h" + +using google::protobuf::util::MessageDifferencer; +using istio::authn::Payload; +using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; +using testing::NiceMock; +using testing::Return; +using testing::StrictMock; + +namespace iaapi = istio::authentication::v1alpha1; + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace { + +const std::string kSecIstioAuthUserinfoHeaderValue = + R"( + { + "iss": "issuer@foo.com", + "sub": "sub@foo.com", + "aud": ["aud1", "aud2"], + "non-string-will-be-ignored": 1512754205, + "some-other-string-claims": "some-claims-kept" + } + )"; + +const std::string kExchangedTokenHeaderName = "ingress-authorization"; + +const std::string kExchangedTokenPayload = + R"( + { + "iss": "token-service", + "sub": "subject", + "aud": ["aud1", "aud2"], + "original_claims": { + "iss": "https://accounts.example.com", + "sub": "example-subject", + "email": "user@example.com" + } + } + )"; + +const std::string kExchangedTokenPayloadNoOriginalClaims = + R"( + { + "iss": "token-service", + "sub": "subject", + "aud": ["aud1", "aud2"] + } + )"; + +class MockAuthenticatorBase : public AuthenticatorBase { + public: + MockAuthenticatorBase(FilterContext* filter_context) + : AuthenticatorBase(filter_context) {} + MOCK_METHOD1(run, bool(Payload*)); +}; + +class ValidateX509Test : public testing::TestWithParam, + public Logger::Loggable { + public: + virtual ~ValidateX509Test() {} + + NiceMock connection_{}; + Envoy::Http::RequestHeaderMapImpl header_{}; + FilterConfig filter_config_{}; + FilterContext filter_context_{ + envoy::config::core::v3::Metadata::default_instance(), header_, + &connection_, filter_config_}; + + MockAuthenticatorBase authenticator_{&filter_context_}; + + void SetUp() override { + mtls_params_.set_mode(GetParam()); + payload_ = new Payload(); + } + + void TearDown() override { delete (payload_); } + + protected: + iaapi::MutualTls mtls_params_; + iaapi::Jwt jwt_; + Payload* payload_; + Payload default_payload_; +}; + +TEST_P(ValidateX509Test, PlaintextConnection) { + // Should return false except mode is PERMISSIVE (accept plaintext) + if (GetParam() == iaapi::MutualTls::PERMISSIVE) { + EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); + } else { + EXPECT_FALSE(authenticator_.validateX509(mtls_params_, payload_)); + } + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_P(ValidateX509Test, SslConnectionWithNoPeerCert) { + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(false)); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); + + // Should return false except mode is PERMISSIVE (accept plaintext). + if (GetParam() == iaapi::MutualTls::PERMISSIVE) { + EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); + } else { + EXPECT_FALSE(authenticator_.validateX509(mtls_params_, payload_)); + } + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_P(ValidateX509Test, SslConnectionWithPeerCert) { + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(*ssl, uriSanPeerCertificate()) + .WillByDefault(Return(std::vector{"foo"})); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); + + // Should return false due to unable to extract trust domain from principal. + EXPECT_FALSE(authenticator_.validateX509(mtls_params_, payload_)); + // When client certificate is present on mTLS, authenticated attribute should + // be extracted. + EXPECT_EQ(payload_->x509().user(), "foo"); +} + +TEST_P(ValidateX509Test, SslConnectionWithCertsSkipTrustDomainValidation) { + // skip trust domain validation. + google::protobuf::util::JsonParseOptions options; + JsonStringToMessage("{ skip_validate_trust_domain: true }", &filter_config_, + options); + + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(*ssl, uriSanPeerCertificate()) + .WillByDefault(Return(std::vector{"foo"})); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); + + // Should return true due to trust domain validation skipped. + EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); + EXPECT_EQ(payload_->x509().user(), "foo"); +} + +TEST_P(ValidateX509Test, SslConnectionWithSpiffeCertsSameTrustDomain) { + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(*ssl, uriSanPeerCertificate()) + .WillByDefault(Return(std::vector{"spiffe://td/foo"})); + ON_CALL(*ssl, uriSanLocalCertificate()) + .WillByDefault(Return(std::vector{"spiffe://td/bar"})); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); + + EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); + // When client certificate is present on mTLS, authenticated attribute should + // be extracted. + EXPECT_EQ(payload_->x509().user(), "td/foo"); +} + +TEST_P(ValidateX509Test, SslConnectionWithSpiffeCertsDifferentTrustDomain) { + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(*ssl, uriSanPeerCertificate()) + .WillByDefault(Return(std::vector{"spiffe://td-1/foo"})); + ON_CALL(*ssl, uriSanLocalCertificate()) + .WillByDefault(Return(std::vector{"spiffe://td-2/bar"})); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); + + // Should return false due to trust domain validation failed. + EXPECT_FALSE(authenticator_.validateX509(mtls_params_, payload_)); + // When client certificate is present on mTLS, authenticated attribute should + // be extracted. + EXPECT_EQ(payload_->x509().user(), "td-1/foo"); +} + +TEST_P(ValidateX509Test, SslConnectionWithPeerMalformedSpiffeCert) { + // skip trust domain validation. + google::protobuf::util::JsonParseOptions options; + JsonStringToMessage("{ skip_validate_trust_domain: true }", &filter_config_, + options); + + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(*ssl, uriSanPeerCertificate()) + .WillByDefault(Return(std::vector{"spiffe:foo"})); + ON_CALL(*ssl, uriSanLocalCertificate()) + .WillByDefault(Return(std::vector{"spiffe://td-2/bar"})); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); + + EXPECT_TRUE(authenticator_.validateX509(mtls_params_, payload_)); + // When client certificate is present on mTLS and the spiffe subject format is + // wrong + // ("spiffe:foo" instead of "spiffe://foo"), the user attribute should be + // extracted. + EXPECT_EQ(payload_->x509().user(), "spiffe:foo"); +} + +INSTANTIATE_TEST_SUITE_P(ValidateX509Tests, ValidateX509Test, + testing::Values(iaapi::MutualTls::STRICT, + iaapi::MutualTls::PERMISSIVE)); + +class ValidateJwtTest : public testing::Test, + public Logger::Loggable { + public: + virtual ~ValidateJwtTest() {} + + // StrictMock request_info_{}; + envoy::config::core::v3::Metadata dynamic_metadata_; + NiceMock connection_{}; + Envoy::Http::RequestHeaderMapImpl header_{}; + FilterConfig filter_config_{}; + FilterContext filter_context_{dynamic_metadata_, header_, &connection_, + filter_config_}; + MockAuthenticatorBase authenticator_{&filter_context_}; + + void SetUp() override { payload_ = new Payload(); } + + void TearDown() override { delete (payload_); } + + protected: + iaapi::MutualTls mtls_params_; + iaapi::Jwt jwt_; + Payload* payload_; + Payload default_payload_; +}; + +TEST_F(ValidateJwtTest, NoIstioAuthnConfig) { + jwt_.set_issuer("issuer@foo.com"); + // authenticator_ has empty Istio authn config + // When there is empty Istio authn config, validateJwt() should return + // nullptr and failure. + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_F(ValidateJwtTest, NoIssuer) { + // no issuer in jwt + google::protobuf::util::JsonParseOptions options; + JsonStringToMessage( + R"({ + "jwt_output_payload_locations": + { + "issuer@foo.com": "sec-istio-auth-userinfo" + } + } + )", + &filter_config_, options); + + // When there is no issuer in the JWT config, validateJwt() should return + // nullptr and failure. + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_F(ValidateJwtTest, OutputPayloadLocationNotDefine) { + jwt_.set_issuer("issuer@foo.com"); + google::protobuf::util::JsonParseOptions options; + JsonStringToMessage( + R"({ + "jwt_output_payload_locations": + { + } + } + )", + &filter_config_, options); + + // authenticator has empty jwt_output_payload_locations in Istio authn config + // When there is no matching jwt_output_payload_locations for the issuer in + // the Istio authn config, validateJwt() should return nullptr and failure. + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_F(ValidateJwtTest, NoJwtPayloadOutput) { + jwt_.set_issuer("issuer@foo.com"); + + // When there is no JWT in request info dynamic metadata, validateJwt() should + // return nullptr and failure. + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_F(ValidateJwtTest, HasJwtPayloadOutputButNoDataForKey) { + jwt_.set_issuer("issuer@foo.com"); + + (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + .MergeFrom(MessageUtil::keyValueStruct("foo", "bar")); + + // When there is no JWT payload for given issuer in request info dynamic + // metadata, validateJwt() should return nullptr and failure. + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(*payload_, default_payload_)); +} + +TEST_F(ValidateJwtTest, JwtPayloadAvailableWithBadData) { + jwt_.set_issuer("issuer@foo.com"); + (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + .MergeFrom(MessageUtil::keyValueStruct("issuer@foo.com", "bad-data")); + // EXPECT_CALL(request_info_, dynamicMetadata()); + + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equivalent(*payload_, default_payload_)); +} + +TEST_F(ValidateJwtTest, JwtPayloadAvailable) { + jwt_.set_issuer("issuer@foo.com"); + (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + .MergeFrom(MessageUtil::keyValueStruct("issuer@foo.com", + kSecIstioAuthUserinfoHeaderValue)); + + Payload expected_payload; + JsonStringToMessage( + R"({ + "jwt": { + "user": "issuer@foo.com/sub@foo.com", + "audiences": ["aud1", "aud2"], + "presenter": "", + "claims": { + "aud": ["aud1", "aud2"], + "iss": ["issuer@foo.com"], + "some-other-string-claims": ["some-claims-kept"], + "sub": ["sub@foo.com"], + }, + "raw_claims": "\n {\n \"iss\": \"issuer@foo.com\",\n \"sub\": \"sub@foo.com\",\n \"aud\": [\"aud1\", \"aud2\"],\n \"non-string-will-be-ignored\": 1512754205,\n \"some-other-string-claims\": \"some-claims-kept\"\n }\n ", + } + } + )", + &expected_payload, google::protobuf::util::JsonParseOptions{}); + + EXPECT_TRUE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, *payload_)); +} + +TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedToken) { + jwt_.set_issuer("token-service"); + jwt_.add_jwt_headers(kExchangedTokenHeaderName); + + (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + .MergeFrom( + MessageUtil::keyValueStruct("token-service", kExchangedTokenPayload)); + + Payload expected_payload; + JsonStringToMessage( + R"({ + "jwt": { + "user": "https://accounts.example.com/example-subject", + "claims": { + "iss": ["https://accounts.example.com"], + "sub": ["example-subject"], + "email": ["user@example.com"] + }, + "raw_claims": "{\"email\":\"user@example.com\",\"iss\":\"https://accounts.example.com\",\"sub\":\"example-subject\"}" + } + } + )", + &expected_payload, google::protobuf::util::JsonParseOptions{}); + + EXPECT_TRUE(authenticator_.validateJwt(jwt_, payload_)); + // On different platforms, the order of fields in raw_claims may be + // different. E.g., on MacOs, the raw_claims in the payload_ can be: + // raw_claims: + // "{\"email\":\"user@example.com\",\"sub\":\"example-subject\",\"iss\":\"https://accounts.example.com\"}" + // Therefore, raw_claims is skipped to avoid a flaky test. + MessageDifferencer diff; + const google::protobuf::FieldDescriptor* field = + expected_payload.jwt().GetDescriptor()->FindFieldByName("raw_claims"); + diff.IgnoreField(field); + EXPECT_TRUE(diff.Compare(expected_payload, *payload_)); +} + +TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedTokenMissing) { + jwt_.set_issuer("token-service"); + jwt_.add_jwt_headers(kExchangedTokenHeaderName); + + (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + .MergeFrom(MessageUtil::keyValueStruct( + "token-service", kExchangedTokenPayloadNoOriginalClaims)); + + // When no original_claims in an exchanged token, the token + // is treated as invalid. + EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); +} + +TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedTokenNotInIntendedHeader) { + jwt_.set_issuer("token-service"); + + (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + .MergeFrom( + MessageUtil::keyValueStruct("token-service", kExchangedTokenPayload)); + + Payload expected_payload; + JsonStringToMessage( + R"({ + "jwt": { + "user": "token-service/subject", + "audiences": ["aud1", "aud2"], + "claims": { + "iss": ["token-service"], + "sub": ["subject"], + "aud": ["aud1", "aud2"] + }, + "raw_claims":"\n {\n \"iss\": \"token-service\",\n \"sub\": \"subject\",\n \"aud\": [\"aud1\", \"aud2\"],\n \"original_claims\": {\n \"iss\": \"https://accounts.example.com\",\n \"sub\": \"example-subject\",\n \"email\": \"user@example.com\"\n }\n }\n " + } + } + )", + &expected_payload, google::protobuf::util::JsonParseOptions{}); + + // When an exchanged token is not in the intended header, the token + // is treated as a normal token with its claims extracted. + EXPECT_TRUE(authenticator_.validateJwt(jwt_, payload_)); + EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, *payload_)); +} + +} // namespace +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/authn/authn_utils.cc b/extensions/authn/authn_utils.cc new file mode 100644 index 00000000000..e01352f9998 --- /dev/null +++ b/extensions/authn/authn_utils.cc @@ -0,0 +1,229 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "authn_utils.h" + +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/match.h" +#include "absl/strings/str_split.h" +#include "common/json/json_loader.h" +#include "google/protobuf/struct.pb.h" +#include "src/envoy/http/jwt_auth/jwt.h" + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +using proxy_wasm::null_plugin::logTrace; +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; +using proxy_wasm::null_plugin::logWarn; + +#endif // NULL_PLUGIN + +namespace { +// The JWT audience key name +static const std::string kJwtAudienceKey = "aud"; +// The JWT issuer key name +static const std::string kJwtIssuerKey = "iss"; +// The key name for the original claims in an exchanged token +static const std::string kExchangedTokenOriginalPayload = "original_claims"; + +// Extract JWT claim as a string list. +// This function only extracts string and string list claims. +// A string claim is extracted as a string list of 1 item. +// A string claim with whitespace is extracted as a string list with each +// sub-string delimited with the whitespace. +void ExtractStringList(const std::string& key, const Envoy::Json::Object& obj, + std::vector* list) { + // First, try as string + try { + // Try as string, will throw execption if object type is not string. + const std::vector keys = + absl::StrSplit(obj.getString(key), ' ', absl::SkipEmpty()); + for (auto key : keys) { + list->push_back(key); + } + } catch (Json::Exception& e) { + // Not convertable to string + } + // Next, try as string array + try { + std::vector vector = obj.getStringArray(key); + for (const std::string v : vector) { + list->push_back(v); + } + } catch (Json::Exception& e) { + // Not convertable to string array + } +} +}; // namespace + +bool AuthnUtils::ProcessJwtPayload(const std::string& payload_str, + istio::authn::JwtPayload* payload) { + Envoy::Json::ObjectSharedPtr json_obj; + try { + json_obj = Json::Factory::loadFromString(payload_str); + logDebug(absl::StrCat("{}: json object is {}", __FUNCTION__, + json_obj->asJsonString())); + } catch (...) { + return false; + } + + *payload->mutable_raw_claims() = payload_str; + + auto claims = payload->mutable_claims()->mutable_fields(); + // Extract claims as string lists + json_obj->iterate([json_obj, claims](const std::string& key, + const Json::Object&) -> bool { + // In current implementation, only string/string list objects are extracted + std::vector list; + ExtractStringList(key, *json_obj, &list); + for (auto s : list) { + (*claims)[key].mutable_list_value()->add_values()->set_string_value(s); + } + return true; + }); + // Copy audience to the audience in context.proto + if (claims->find(kJwtAudienceKey) != claims->end()) { + for (const auto& v : (*claims)[kJwtAudienceKey].list_value().values()) { + payload->add_audiences(v.string_value()); + } + } + + // Build user + if (claims->find("iss") != claims->end() && + claims->find("sub") != claims->end()) { + payload->set_user( + (*claims)["iss"].list_value().values().Get(0).string_value() + "/" + + (*claims)["sub"].list_value().values().Get(0).string_value()); + } + // Build authorized presenter (azp) + if (claims->find("azp") != claims->end()) { + payload->set_presenter( + (*claims)["azp"].list_value().values().Get(0).string_value()); + } + + return true; +} + +bool AuthnUtils::ExtractOriginalPayload(const std::string& token, + std::string* original_payload) { + Envoy::Json::ObjectSharedPtr json_obj; + try { + json_obj = Json::Factory::loadFromString(token); + } catch (...) { + return false; + } + + if (json_obj->hasObject(kExchangedTokenOriginalPayload) == false) { + return false; + } + + Envoy::Json::ObjectSharedPtr original_payload_obj; + try { + auto original_payload_obj = + json_obj->getObject(kExchangedTokenOriginalPayload); + *original_payload = original_payload_obj->asJsonString(); + logDebug("{}: the original payload in exchanged token is {}", __FUNCTION__, *original_payload); + } catch (...) { + logDebug("{}: original_payload in exchanged token is of invalid format.", + __FUNCTION__); + return false; + } + + return true; +} + +bool AuthnUtils::MatchString(absl::string_view str, + const iaapi::StringMatch& match) { + switch (match.match_type_case()) { + case iaapi::StringMatch::kExact: { + return match.exact() == str; + } + case iaapi::StringMatch::kPrefix: { + return absl::StartsWith(str, match.prefix()); + } + case iaapi::StringMatch::kSuffix: { + return absl::EndsWith(str, match.suffix()); + } + case iaapi::StringMatch::kRegex: { + return std::regex_match(std::string(str), std::regex(match.regex())); + } + default: + return false; + } +} + +static bool matchRule(absl::string_view path, + const iaapi::Jwt_TriggerRule& rule) { + for (const auto& excluded : rule.excluded_paths()) { + if (AuthnUtils::MatchString(path, excluded)) { + // The rule is not matched if any of excluded_paths matched. + return false; + } + } + + if (rule.included_paths_size() > 0) { + for (const auto& included : rule.included_paths()) { + if (AuthnUtils::MatchString(path, included)) { + // The rule is matched if any of included_paths matched. + return true; + } + } + + // The rule is not matched if included_paths is not empty and none of them + // matched. + return false; + } + + // The rule is matched if none of excluded_paths matched and included_paths is + // empty. + return true; +} + +bool AuthnUtils::ShouldValidateJwtPerPath(absl::string_view path, + const iaapi::Jwt& jwt) { + // If the path is empty which shouldn't happen for a HTTP request or if + // there are no trigger rules at all, then simply return true as if there're + // no per-path jwt support. + if (path == "" || jwt.trigger_rules_size() == 0) { + return true; + } + for (const auto& rule : jwt.trigger_rules()) { + if (matchRule(path, rule)) { + return true; + } + } + return false; +} + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif + diff --git a/extensions/authn/authn_utils.h b/extensions/authn/authn_utils.h new file mode 100644 index 00000000000..8e4b6547e7d --- /dev/null +++ b/extensions/authn/authn_utils.h @@ -0,0 +1,70 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "authentication/v1alpha1/policy.pb.h" +#include "common/common/utility.h" +#include "envoy/http/header_map.h" +#include "envoy/json/json_object.h" +#include "src/istio/authn/context.pb.h" + +namespace iaapi = istio::authentication::v1alpha1; + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +// AuthnUtils class provides utility functions used for authentication. +class AuthnUtils { + public: + // Parse JWT payload string (which typically is the output from jwt filter) + // and populate JwtPayload object. Return true if input string can be parsed + // successfully. Otherwise, return false. + static bool ProcessJwtPayload(const std::string& jwt_payload_str, + istio::authn::JwtPayload* payload); + + // Parses the original_payload in an exchanged JWT. + // Returns true if original_payload can be + // parsed successfully. Otherwise, returns false. + static bool ExtractOriginalPayload(const std::string& token, + std::string* original_payload); + + // Returns true if str is matched to match. + static bool MatchString(absl::string_view str, + const iaapi::StringMatch& match); + + // Returns true if the jwt should be validated. It will check if the request + // path is matched to the trigger rule in the jwt. + static bool ShouldValidateJwtPerPath(absl::string_view path, + const iaapi::Jwt& jwt); +}; + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif diff --git a/extensions/authn/authn_utils_test.cc b/extensions/authn/authn_utils_test.cc new file mode 100644 index 00000000000..0420d012257 --- /dev/null +++ b/extensions/authn/authn_utils_test.cc @@ -0,0 +1,351 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ +#include "src/envoy/http/authn/authn_utils.h" + +#include "common/common/base64.h" +#include "common/common/utility.h" +#include "src/envoy/http/authn/test_utils.h" +#include "test/test_common/utility.h" + +using google::protobuf::util::MessageDifferencer; +using istio::authn::JwtPayload; + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace { + +const std::string kSecIstioAuthUserinfoHeaderValue = + R"( + { + "iss": "issuer@foo.com", + "sub": "sub@foo.com", + "aud": "aud1", + "non-string-will-be-ignored": 1512754205, + "some-other-string-claims": "some-claims-kept" + } + )"; +const std::string kSecIstioAuthUserInfoHeaderWithAudValueList = + R"( + { + "iss": "issuer@foo.com", + "sub": "sub@foo.com", + "aud": "aud1 aud2", + "non-string-will-be-ignored": 1512754205, + "some-other-string-claims": "some-claims-kept" + } + )"; +const std::string kSecIstioAuthUserInfoHeaderWithAudValueArray = + R"( + { + "iss": "issuer@foo.com", + "sub": "sub@foo.com", + "aud": ["aud1", "aud2"], + "non-string-will-be-ignored": 1512754205, + "some-other-string-claims": "some-claims-kept" + } + )"; + +TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderTest) { + JwtPayload payload, expected_payload; + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + R"( + user: "issuer@foo.com/sub@foo.com" + audiences: ["aud1"] + claims: { + fields: { + key: "aud" + value: { + list_value: { + values: { + string_value: "aud1" + } + } + } + } + fields: { + key: "iss" + value: { + list_value: { + values: { + string_value: "issuer@foo.com" + } + } + } + } + fields: { + key: "sub" + value: { + list_value: { + values: { + string_value: "sub@foo.com" + } + } + } + } + fields: { + key: "some-other-string-claims" + value: { + list_value: { + values: { + string_value: "some-claims-kept" + } + } + } + } + } + raw_claims: ")" + + StringUtil::escape(kSecIstioAuthUserinfoHeaderValue) + R"(")", + &expected_payload)); + // The payload returned from ProcessJwtPayload() should be the same as + // the expected. + bool ret = + AuthnUtils::ProcessJwtPayload(kSecIstioAuthUserinfoHeaderValue, &payload); + EXPECT_TRUE(ret); + EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, payload)); +} + +TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudListTest) { + JwtPayload payload, expected_payload; + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + R"( + user: "issuer@foo.com/sub@foo.com" + audiences: "aud1" + audiences: "aud2" + claims: { + fields: { + key: "iss" + value: { + list_value: { + values: { + string_value: "issuer@foo.com" + } + } + } + } + fields: { + key: "sub" + value: { + list_value: { + values: { + string_value: "sub@foo.com" + } + } + } + } + fields: { + key: "aud" + value: { + list_value: { + values: { + string_value: "aud1" + } + values: { + string_value: "aud2" + } + } + } + } + fields: { + key: "some-other-string-claims" + value: { + list_value: { + values: { + string_value: "some-claims-kept" + } + } + } + } + } + raw_claims: ")" + + StringUtil::escape(kSecIstioAuthUserInfoHeaderWithAudValueList) + + R"(")", + &expected_payload)); + // The payload returned from ProcessJwtPayload() should be the same as + // the expected. When there is no aud, the aud is not saved in the payload + // and claims. + bool ret = AuthnUtils::ProcessJwtPayload( + kSecIstioAuthUserInfoHeaderWithAudValueList, &payload); + EXPECT_TRUE(ret); + EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, payload)); +} + +TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudArrayTest) { + JwtPayload payload, expected_payload; + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + R"( + user: "issuer@foo.com/sub@foo.com" + audiences: "aud1" + audiences: "aud2" + claims: { + fields: { + key: "aud" + value: { + list_value: { + values: { + string_value: "aud1" + } + values: { + string_value: "aud2" + } + } + } + } + fields: { + key: "iss" + value: { + list_value: { + values: { + string_value: "issuer@foo.com" + } + } + } + } + fields: { + key: "sub" + value: { + list_value: { + values: { + string_value: "sub@foo.com" + } + } + } + } + fields: { + key: "some-other-string-claims" + value: { + list_value: { + values: { + string_value: "some-claims-kept" + } + } + } + } + } + raw_claims: ")" + + StringUtil::escape(kSecIstioAuthUserInfoHeaderWithAudValueArray) + + R"(")", + &expected_payload)); + // The payload returned from ProcessJwtPayload() should be the same as + // the expected. When the aud is a string array, the aud is not saved in the + // claims. + bool ret = AuthnUtils::ProcessJwtPayload( + kSecIstioAuthUserInfoHeaderWithAudValueArray, &payload); + + EXPECT_TRUE(ret); + EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, payload)); +} + +TEST(AuthnUtilsTest, MatchString) { + iaapi::StringMatch match; + EXPECT_FALSE(AuthnUtils::MatchString(nullptr, match)); + EXPECT_FALSE(AuthnUtils::MatchString("", match)); + + match.set_exact("exact"); + EXPECT_TRUE(AuthnUtils::MatchString("exact", match)); + EXPECT_FALSE(AuthnUtils::MatchString("exac", match)); + EXPECT_FALSE(AuthnUtils::MatchString("exacy", match)); + + match.set_prefix("prefix"); + EXPECT_TRUE(AuthnUtils::MatchString("prefix-1", match)); + EXPECT_TRUE(AuthnUtils::MatchString("prefix", match)); + EXPECT_FALSE(AuthnUtils::MatchString("prefi", match)); + EXPECT_FALSE(AuthnUtils::MatchString("prefiy", match)); + + match.set_suffix("suffix"); + EXPECT_TRUE(AuthnUtils::MatchString("1-suffix", match)); + EXPECT_TRUE(AuthnUtils::MatchString("suffix", match)); + EXPECT_FALSE(AuthnUtils::MatchString("suffi", match)); + EXPECT_FALSE(AuthnUtils::MatchString("suffiy", match)); + + match.set_regex(".+abc.+"); + EXPECT_TRUE(AuthnUtils::MatchString("1-abc-1", match)); + EXPECT_FALSE(AuthnUtils::MatchString("1-abc", match)); + EXPECT_FALSE(AuthnUtils::MatchString("abc-1", match)); + EXPECT_FALSE(AuthnUtils::MatchString("1-ac-1", match)); +} + +TEST(AuthnUtilsTest, ShouldValidateJwtPerPathExcluded) { + iaapi::Jwt jwt; + + // Create a rule that triggers on everything except /good-x and /allow-x. + auto* rule = jwt.add_trigger_rules(); + rule->add_excluded_paths()->set_exact("/good-x"); + rule->add_excluded_paths()->set_exact("/allow-x"); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/good-x", jwt)); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/allow-x", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/good-1", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/allow-1", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/other", jwt)); + + // Change the rule to only triggers on prefix /good and /allow. + rule->add_included_paths()->set_prefix("/good"); + rule->add_included_paths()->set_prefix("/allow"); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/good-x", jwt)); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/allow-x", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/good-1", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/allow-1", jwt)); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/other", jwt)); +} + +TEST(AuthnUtilsTest, ShouldValidateJwtPerPathIncluded) { + iaapi::Jwt jwt; + + // Create a rule that triggers on everything with prefix /good and /allow. + auto* rule = jwt.add_trigger_rules(); + rule->add_included_paths()->set_prefix("/good"); + rule->add_included_paths()->set_prefix("/allow"); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/good-x", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/allow-x", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/good-2", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/allow-1", jwt)); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/other", jwt)); + + // Change the rule to also exclude /allow-x and /good-x. + rule->add_excluded_paths()->set_exact("/good-x"); + rule->add_excluded_paths()->set_exact("/allow-x"); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/good-x", jwt)); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/allow-x", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/good-2", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/allow-1", jwt)); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/other", jwt)); +} + +TEST(AuthnUtilsTest, ShouldValidateJwtPerPathDefault) { + iaapi::Jwt jwt; + + // Always trigger when path is unavailable. + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("", jwt)); + + // Always trigger when there is no rules in jwt. + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/test", jwt)); + + // Add a rule that triggers on everything except /hello. + jwt.add_trigger_rules()->add_excluded_paths()->set_exact("/hello"); + EXPECT_FALSE(AuthnUtils::ShouldValidateJwtPerPath("/hello", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/other", jwt)); + + // Add another rule that triggers on path /hello. + jwt.add_trigger_rules()->add_included_paths()->set_exact("/hello"); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/hello", jwt)); + EXPECT_TRUE(AuthnUtils::ShouldValidateJwtPerPath("/other", jwt)); +} + +} // namespace +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/authn/filter_context.cc b/extensions/authn/filter_context.cc new file mode 100644 index 00000000000..17a908baa93 --- /dev/null +++ b/extensions/authn/filter_context.cc @@ -0,0 +1,161 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "absl/strings/str_cat.h" +#include "src/envoy/http/authn/filter_context.h" + +#include "src/envoy/utils/filter_names.h" +#include "src/envoy/utils/utils.h" + +using istio::authn::Payload; +using istio::authn::Result; + +namespace iaapi = istio::authentication::v1alpha1; + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +using proxy_wasm::null_plugin::logTrace; +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; +using proxy_wasm::null_plugin::logWarn; + +#endif // NULL_PLUGIN + +void FilterContext::setPeerResult(const Payload* payload) { + if (payload != nullptr) { + switch (payload->payload_case()) { + case Payload::kX509: + logDebug(absl::StrCat("Set peer from X509: {}", payload->x509().user())); + result_.set_peer_user(payload->x509().user()); + break; + case Payload::kJwt: + logDebug(absl::StrCat("Set peer from JWT: {}", payload->jwt().user())); + result_.set_peer_user(payload->jwt().user()); + break; + default: + logDebug("Payload has not peer authentication data"); + break; + } + } +} +void FilterContext::setOriginResult(const Payload* payload) { + // Authentication pass, look at the return payload and store to the context + // output. Set filter to continueDecoding when done. + // At the moment, only JWT can be used for origin authentication, so + // it's ok just to check jwt payload. + if (payload != nullptr && payload->has_jwt()) { + *result_.mutable_origin() = payload->jwt(); + } +} + +void FilterContext::setPrincipal(const iaapi::PrincipalBinding& binding) { + switch (binding) { + case iaapi::PrincipalBinding::USE_PEER: + logDebug(absl::StrCat("Set principal from peer: {}", result_.peer_user())); + result_.set_principal(result_.peer_user()); + return; + case iaapi::PrincipalBinding::USE_ORIGIN: + logDebug(absl::StrCat("Set principal from origin: {}", + result_.origin().user())); + result_.set_principal(result_.origin().user()); + return; + default: + // Should never come here. + logError("Invalid binding value {}", binding); + return; + } +} + +bool FilterContext::getJwtPayload(const std::string& issuer, + std::string* payload) const { + // Prefer to use the jwt payload from Envoy jwt filter over the Istio jwt + // filter's one. + return getJwtPayloadFromEnvoyJwtFilter(issuer, payload) || + getJwtPayloadFromIstioJwtFilter(issuer, payload); +} + +bool FilterContext::getJwtPayloadFromEnvoyJwtFilter( + const std::string& issuer, std::string* payload) const { + // Try getting the Jwt payload from Envoy jwt_authn filter. + auto filter_it = dynamic_metadata_.filter_metadata().find( + Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn); + if (filter_it == dynamic_metadata_.filter_metadata().end()) { + logDebug(absl::StrCat("No dynamic_metadata found for filter {}", + Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn)); + return false; + } + + const auto& data_struct = filter_it->second; + + const auto entry_it = data_struct.fields().find(issuer); + if (entry_it == data_struct.fields().end()) { + return false; + } + + if (entry_it->second.struct_value().fields().empty()) { + return false; + } + + // Serialize the payload from Envoy jwt filter first before writing it to + // |payload|. + // TODO (pitlv2109): Return protobuf Struct instead of string, once Istio jwt + // filter is removed. Also need to change how Istio authn filter processes the + // jwt payload. + Protobuf::util::MessageToJsonString(entry_it->second.struct_value(), payload); + return true; +} + +bool FilterContext::getJwtPayloadFromIstioJwtFilter( + const std::string& issuer, std::string* payload) const { + // Try getting the Jwt payload from Istio jwt-auth filter. + auto filter_it = + dynamic_metadata_.filter_metadata().find(Utils::IstioFilterName::kJwt); + if (filter_it == dynamic_metadata_.filter_metadata().end()) { + logDebug("No dynamic_metadata found for filter {}", + Utils::IstioFilterName::kJwt); + return false; + } + + const auto& data_struct = filter_it->second; + + const auto entry_it = data_struct.fields().find(issuer); + if (entry_it == data_struct.fields().end()) { + return false; + } + + if (entry_it->second.string_value().empty()) { + return false; + } + + *payload = entry_it->second.string_value(); + return true; +} + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif \ No newline at end of file diff --git a/extensions/authn/filter_context.h b/extensions/authn/filter_context.h new file mode 100644 index 00000000000..63f3e09a94e --- /dev/null +++ b/extensions/authn/filter_context.h @@ -0,0 +1,120 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "authentication/v1alpha1/policy.pb.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" +#include "envoy/http/filter.h" +#include "envoy/network/connection.h" +#include "extensions/filters/http/well_known_names.h" +#include "src/istio/authn/context.pb.h" + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +// FilterContext holds inputs, such as request dynamic metadata and connection +// and result data for authentication process. +class FilterContext { + public: + FilterContext( + const envoy::config::core::v3::Metadata& dynamic_metadata, + const RequestHeaderMap& header_map, const Network::Connection* connection, + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + filter_config) + : dynamic_metadata_(dynamic_metadata), + header_map_(header_map), + connection_(connection), + filter_config_(filter_config) {} + virtual ~FilterContext() {} + + // Sets peer result based on authenticated payload. Input payload can be null, + // which basically changes nothing. + void setPeerResult(const istio::authn::Payload* payload); + + // Sets origin result based on authenticated payload. Input payload can be + // null, which basically changes nothing. + void setOriginResult(const istio::authn::Payload* payload); + + // Sets principal based on binding rule, and the existing peer and origin + // result. + void setPrincipal( + const istio::authentication::v1alpha1::PrincipalBinding& binding); + + // Returns the authentication result. + const istio::authn::Result& authenticationResult() { return result_; } + + // Accessor to connection + const Network::Connection* connection() { return connection_; } + // Accessor to the filter config + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + filter_config() const { + return filter_config_; + } + + // Gets JWT payload (output from JWT filter) for given issuer. If non-empty + // payload found, returns true and set the output payload string. Otherwise, + // returns false. + bool getJwtPayload(const std::string& issuer, std::string* payload) const; + + const RequestHeaderMap& headerMap() const { return header_map_; } + + private: + // Helper function for getJwtPayload(). It gets the jwt payload from Envoy jwt + // filter metadata and write to |payload|. + bool getJwtPayloadFromEnvoyJwtFilter(const std::string& issuer, + std::string* payload) const; + // Helper function for getJwtPayload(). It gets the jwt payload from Istio jwt + // filter metadata and write to |payload|. + bool getJwtPayloadFromIstioJwtFilter(const std::string& issuer, + std::string* payload) const; + + // Const reference to request info dynamic metadata. This provides data that + // output from other filters, e.g JWT. + const envoy::config::core::v3::Metadata& dynamic_metadata_; + + // Const reference to header map of the request. This provides request path + // that could be used to decide if a JWT should be used for validation. + const RequestHeaderMap& header_map_; + + // Pointer to network connection of the request. + const Network::Connection* connection_; + + // Holds authentication attribute outputs. + istio::authn::Result result_; + + // Store the Istio authn filter config. + const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& + filter_config_; +}; + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif \ No newline at end of file diff --git a/extensions/authn/filter_context_test.cc b/extensions/authn/filter_context_test.cc new file mode 100644 index 00000000000..94db0c38875 --- /dev/null +++ b/extensions/authn/filter_context_test.cc @@ -0,0 +1,129 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn/filter_context.h" + +#include "envoy/config/core/v3/base.pb.h" +#include "src/envoy/http/authn/test_utils.h" +#include "test/test_common/utility.h" + +using istio::authn::Payload; +using testing::StrictMock; + +namespace iaapi = istio::authentication::v1alpha1; + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace { + +class FilterContextTest : public testing::Test { + public: + virtual ~FilterContextTest() {} + + envoy::config::core::v3::Metadata metadata_; + Envoy::Http::TestRequestHeaderMapImpl header_{}; + // This test suit does not use connection, so ok to use null for it. + FilterContext filter_context_{metadata_, header_, nullptr, + istio::envoy::config::filter::http::authn:: + v2alpha1::FilterConfig::default_instance()}; + + Payload x509_payload_{TestUtilities::CreateX509Payload("foo")}; + Payload jwt_payload_{TestUtilities::CreateJwtPayload("bar", "istio.io")}; +}; + +TEST_F(FilterContextTest, SetPeerResult) { + filter_context_.setPeerResult(&x509_payload_); + EXPECT_TRUE(TestUtility::protoEqual( + TestUtilities::AuthNResultFromString("peer_user: \"foo\""), + filter_context_.authenticationResult())); +} + +TEST_F(FilterContextTest, SetOriginResult) { + filter_context_.setOriginResult(&jwt_payload_); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + origin { + user: "bar" + presenter: "istio.io" + } + )"), + filter_context_.authenticationResult())); +} + +TEST_F(FilterContextTest, SetBoth) { + filter_context_.setPeerResult(&x509_payload_); + filter_context_.setOriginResult(&jwt_payload_); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + peer_user: "foo" + origin { + user: "bar" + presenter: "istio.io" + } + )"), + filter_context_.authenticationResult())); +} + +TEST_F(FilterContextTest, UseOrigin) { + filter_context_.setPeerResult(&x509_payload_); + filter_context_.setOriginResult(&jwt_payload_); + filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_ORIGIN); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + principal: "bar" + peer_user: "foo" + origin { + user: "bar" + presenter: "istio.io" + } + )"), + filter_context_.authenticationResult())); +} + +TEST_F(FilterContextTest, UseOriginOnEmptyOrigin) { + filter_context_.setPeerResult(&x509_payload_); + filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_ORIGIN); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + peer_user: "foo" + )"), + filter_context_.authenticationResult())); +} + +TEST_F(FilterContextTest, PrincipalUsePeer) { + filter_context_.setPeerResult(&x509_payload_); + filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_PEER); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + principal: "foo" + peer_user: "foo" + )"), + filter_context_.authenticationResult())); +} + +TEST_F(FilterContextTest, PrincipalUsePeerOnEmptyPeer) { + filter_context_.setOriginResult(&jwt_payload_); + filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_PEER); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + origin { + user: "bar" + presenter: "istio.io" + } + )"), + filter_context_.authenticationResult())); +} + +} // namespace +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/authn/http_filter_integration_test.cc b/extensions/authn/http_filter_integration_test.cc new file mode 100644 index 00000000000..f2ee0e79ac8 --- /dev/null +++ b/extensions/authn/http_filter_integration_test.cc @@ -0,0 +1,199 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "common/common/base64.h" +#include "common/common/utility.h" +#include "extensions/filters/http/well_known_names.h" +#include "fmt/printf.h" +#include "src/envoy/utils/filter_names.h" +#include "src/istio/authn/context.pb.h" +#include "test/integration/http_protocol_integration.h" + +using google::protobuf::util::MessageDifferencer; +using istio::authn::Payload; +using istio::authn::Result; + +namespace Envoy { +namespace { + +static const Envoy::Http::LowerCaseString kSecIstioAuthnPayloadHeaderKey( + "sec-istio-authn-payload"); + +// Default request for testing. +Http::TestRequestHeaderMapImpl SimpleRequestHeaders() { + return Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}}; +} + +// Keep the same as issuer in the policy below. +static const char kJwtIssuer[] = "some@issuer"; + +static const char kAuthnFilterWithJwt[] = R"( + name: istio_authn + config: + policy: + origins: + - jwt: + issuer: some@issuer + jwks_uri: http://localhost:8081/)"; + +// Payload data to inject. Note the iss claim intentionally set different from +// kJwtIssuer. +static const char kMockJwtPayload[] = + "{\"iss\":\"https://example.com\"," + "\"sub\":\"test@example.com\",\"exp\":2001001001," + "\"aud\":\"example_service\"}"; +// Returns a simple header-to-metadata filter config that can be used to inject +// data into request info dynamic metadata for testing. +std::string MakeHeaderToMetadataConfig() { + return fmt::sprintf( + R"( + name: %s + config: + request_rules: + - header: x-mock-metadata-injection + on_header_missing: + metadata_namespace: %s + key: %s + value: "%s" + type: STRING)", + Extensions::HttpFilters::HttpFilterNames::get().HeaderToMetadata, + Utils::IstioFilterName::kJwt, kJwtIssuer, + StringUtil::escape(kMockJwtPayload)); +} + +typedef HttpProtocolIntegrationTest AuthenticationFilterIntegrationTest; + +INSTANTIATE_TEST_SUITE_P( + Protocols, AuthenticationFilterIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +TEST_P(AuthenticationFilterIntegrationTest, EmptyPolicy) { + config_helper_.addFilter("name: istio_authn"); + initialize(); + codec_client_ = + makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = codec_client_->makeHeaderOnlyRequest(SimpleRequestHeaders()); + // Wait for request to upstream (backend) + waitForNextUpstreamRequest(); + + // Send backend response. + upstream_request_->encodeHeaders( + Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); +} + +TEST_P(AuthenticationFilterIntegrationTest, SourceMTlsFail) { + config_helper_.addFilter(R"( + name: istio_authn + config: + policy: + peers: + - mtls: {})"); + initialize(); + + // AuthN filter use MTls, but request doesn't have certificate, request + // would be rejected. + codec_client_ = + makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = codec_client_->makeHeaderOnlyRequest(SimpleRequestHeaders()); + + // Request is rejected, there will be no upstream request (thus no + // waitForNextUpstreamRequest). + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("401", response->headers().Status()->value().getStringView()); +} + +// TODO (diemtvu/lei-tang): add test for MTls success. + +TEST_P(AuthenticationFilterIntegrationTest, OriginJwtRequiredHeaderNoJwtFail) { + config_helper_.addFilter(kAuthnFilterWithJwt); + initialize(); + + // The AuthN filter requires JWT, but request doesn't have JWT, request + // would be rejected. + codec_client_ = + makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = codec_client_->makeHeaderOnlyRequest(SimpleRequestHeaders()); + + // Request is rejected, there will be no upstream request (thus no + // waitForNextUpstreamRequest). + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("401", response->headers().Status()->value().getStringView()); +} + +TEST_P(AuthenticationFilterIntegrationTest, CheckValidJwtPassAuthentication) { + config_helper_.addFilter(kAuthnFilterWithJwt); + config_helper_.addFilter(MakeHeaderToMetadataConfig()); + initialize(); + + // The AuthN filter requires JWT. The http request contains validated JWT and + // the authentication should succeed. + codec_client_ = + makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = codec_client_->makeHeaderOnlyRequest(SimpleRequestHeaders()); + + // Wait for request to upstream (backend) + waitForNextUpstreamRequest(); + // Send backend response. + upstream_request_->encodeHeaders( + Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); +} + +TEST_P(AuthenticationFilterIntegrationTest, CORSPreflight) { + config_helper_.addFilter(kAuthnFilterWithJwt); + initialize(); + + // The AuthN filter requires JWT but should bypass CORS preflight request even + // it doesn't have JWT token. + codec_client_ = + makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto headers = Http::TestRequestHeaderMapImpl{ + {":method", "OPTIONS"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"access-control-request-method", "GET"}, + {"origin", "example.com"}, + }; + auto response = codec_client_->makeHeaderOnlyRequest(headers); + + // Wait for request to upstream (backend) + waitForNextUpstreamRequest(); + // Send backend response. + upstream_request_->encodeHeaders( + Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); +} + +} // namespace +} // namespace Envoy diff --git a/extensions/authn/http_filter_test.cc b/extensions/authn/http_filter_test.cc new file mode 100644 index 00000000000..8c471abbb62 --- /dev/null +++ b/extensions/authn/http_filter_test.cc @@ -0,0 +1,289 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn/http_filter.h" + +#include "common/common/base64.h" +#include "common/http/header_map_impl.h" +#include "common/stream_info/stream_info_impl.h" +#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "src/envoy/http/authn/authenticator_base.h" +#include "src/envoy/http/authn/test_utils.h" +#include "src/envoy/utils/authn.h" +#include "test/mocks/http/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +using Envoy::Http::Istio::AuthN::AuthenticatorBase; +using Envoy::Http::Istio::AuthN::FilterContext; +using istio::authn::Payload; +using istio::authn::Result; +using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; +using testing::_; +using testing::AtLeast; +using testing::Invoke; +using testing::NiceMock; +using testing::ReturnRef; +using testing::StrictMock; + +namespace iaapi = istio::authentication::v1alpha1; + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace { + +const char ingoreBothPolicy[] = R"( + peer_is_optional: true + origin_is_optional: true +)"; + +// Create a fake authenticator for test. This authenticator do nothing except +// making the authentication fail. +std::unique_ptr createAlwaysFailAuthenticator( + FilterContext *filter_context) { + class _local : public AuthenticatorBase { + public: + _local(FilterContext *filter_context) : AuthenticatorBase(filter_context) {} + bool run(Payload *) override { return false; } + }; + return std::make_unique<_local>(filter_context); +} + +// Create a fake authenticator for test. This authenticator do nothing except +// making the authentication successful. +std::unique_ptr createAlwaysPassAuthenticator( + FilterContext *filter_context) { + class _local : public AuthenticatorBase { + public: + _local(FilterContext *filter_context) : AuthenticatorBase(filter_context) {} + bool run(Payload *) override { + // Set some data to verify authentication result later. + auto payload = TestUtilities::CreateX509Payload( + "cluster.local/sa/test_user/ns/test_ns/"); + filter_context()->setPeerResult(&payload); + return true; + } + }; + return std::make_unique<_local>(filter_context); +} + +class MockAuthenticationFilter : public AuthenticationFilter { + public: + // We'll use fake authenticator for test, so policy is not really needed. Use + // default config for simplicity. + MockAuthenticationFilter(const FilterConfig &filter_config) + : AuthenticationFilter(filter_config) {} + + ~MockAuthenticationFilter(){}; + + MOCK_METHOD1(createPeerAuthenticator, + std::unique_ptr(FilterContext *)); + MOCK_METHOD1(createOriginAuthenticator, + std::unique_ptr(FilterContext *)); +}; + +class AuthenticationFilterTest : public testing::Test { + public: + AuthenticationFilterTest() + : request_headers_{{":method", "GET"}, {":path", "/"}} {} + ~AuthenticationFilterTest() {} + + void SetUp() override { + filter_.setDecoderFilterCallbacks(decoder_callbacks_); + } + + protected: + FilterConfig filter_config_ = FilterConfig::default_instance(); + + Http::TestRequestHeaderMapImpl request_headers_; + StrictMock filter_{filter_config_}; + NiceMock decoder_callbacks_; +}; + +TEST_F(AuthenticationFilterTest, PeerFail) { + // Peer authentication fail, request should be rejected with 401. No origin + // authentiation needed. + EXPECT_CALL(filter_, createPeerAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysFailAuthenticator)); + Envoy::Event::SimulatedTimeSystem test_time; + StreamInfo::StreamInfoImpl stream_info(Http::Protocol::Http2, + test_time.timeSystem()); + EXPECT_CALL(decoder_callbacks_, streamInfo()) + .Times(AtLeast(1)) + .WillRepeatedly(ReturnRef(stream_info)); + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .Times(1) + .WillOnce(testing::Invoke([](Http::ResponseHeaderMap &headers, bool) { + EXPECT_EQ("401", headers.Status()->value().getStringView()); + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_.decodeHeaders(request_headers_, true)); + EXPECT_FALSE(Utils::Authentication::GetResultFromMetadata( + stream_info.dynamicMetadata())); +} + +TEST_F(AuthenticationFilterTest, PeerPassOriginFail) { + // Peer pass thus origin authentication must be called. Final result should + // fail as origin authn fails. + EXPECT_CALL(filter_, createPeerAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysPassAuthenticator)); + EXPECT_CALL(filter_, createOriginAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysFailAuthenticator)); + Envoy::Event::SimulatedTimeSystem test_time; + StreamInfo::StreamInfoImpl stream_info(Http::Protocol::Http2, + test_time.timeSystem()); + EXPECT_CALL(decoder_callbacks_, streamInfo()) + .Times(AtLeast(1)) + .WillRepeatedly(ReturnRef(stream_info)); + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, _)) + .Times(1) + .WillOnce(testing::Invoke([](Http::ResponseHeaderMap &headers, bool) { + EXPECT_EQ("401", headers.Status()->value().getStringView()); + })); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_.decodeHeaders(request_headers_, true)); + EXPECT_FALSE(Utils::Authentication::GetResultFromMetadata( + stream_info.dynamicMetadata())); +} + +TEST_F(AuthenticationFilterTest, AllPass) { + EXPECT_CALL(filter_, createPeerAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysPassAuthenticator)); + EXPECT_CALL(filter_, createOriginAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysPassAuthenticator)); + Envoy::Event::SimulatedTimeSystem test_time; + StreamInfo::StreamInfoImpl stream_info(Http::Protocol::Http2, + test_time.timeSystem()); + EXPECT_CALL(decoder_callbacks_, streamInfo()) + .Times(AtLeast(1)) + .WillRepeatedly(ReturnRef(stream_info)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(request_headers_, true)); + + EXPECT_EQ(1, stream_info.dynamicMetadata().filter_metadata_size()); + const auto *data = Utils::Authentication::GetResultFromMetadata( + stream_info.dynamicMetadata()); + ASSERT_TRUE(data); + + ProtobufWkt::Struct expected_data; + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + fields { + key: "source.namespace" + value { + string_value: "test_ns" + } + } + fields { + key: "source.principal" + value { + string_value: "cluster.local/sa/test_user/ns/test_ns/" + } + } + fields { + key: "source.user" + value { + string_value: "cluster.local/sa/test_user/ns/test_ns/" + } + })", + &expected_data)); + EXPECT_TRUE(TestUtility::protoEqual(expected_data, *data)); +} + +TEST_F(AuthenticationFilterTest, IgnoreBothFail) { + iaapi::Policy policy_; + ASSERT_TRUE( + Protobuf::TextFormat::ParseFromString(ingoreBothPolicy, &policy_)); + *filter_config_.mutable_policy() = policy_; + StrictMock filter(filter_config_); + filter.setDecoderFilterCallbacks(decoder_callbacks_); + + EXPECT_CALL(filter, createPeerAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysFailAuthenticator)); + EXPECT_CALL(filter, createOriginAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysFailAuthenticator)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter.decodeHeaders(request_headers_, true)); +} + +TEST_F(AuthenticationFilterTest, IgnoreBothPass) { + iaapi::Policy policy_; + ASSERT_TRUE( + Protobuf::TextFormat::ParseFromString(ingoreBothPolicy, &policy_)); + *filter_config_.mutable_policy() = policy_; + StrictMock filter(filter_config_); + filter.setDecoderFilterCallbacks(decoder_callbacks_); + + EXPECT_CALL(filter, createPeerAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysPassAuthenticator)); + EXPECT_CALL(filter, createOriginAuthenticator(_)) + .Times(1) + .WillOnce(Invoke(createAlwaysPassAuthenticator)); + Envoy::Event::SimulatedTimeSystem test_time; + StreamInfo::StreamInfoImpl stream_info(Http::Protocol::Http2, + test_time.timeSystem()); + EXPECT_CALL(decoder_callbacks_, streamInfo()) + .Times(AtLeast(1)) + .WillRepeatedly(ReturnRef(stream_info)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter.decodeHeaders(request_headers_, true)); + + EXPECT_EQ(1, stream_info.dynamicMetadata().filter_metadata_size()); + const auto *data = Utils::Authentication::GetResultFromMetadata( + stream_info.dynamicMetadata()); + ASSERT_TRUE(data); + + ProtobufWkt::Struct expected_data; + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + fields { + key: "source.namespace" + value { + string_value: "test_ns" + } + } + fields { + key: "source.principal" + value { + string_value: "cluster.local/sa/test_user/ns/test_ns/" + } + } + fields { + key: "source.user" + value { + string_value: "cluster.local/sa/test_user/ns/test_ns/" + } + })", + &expected_data)); + EXPECT_TRUE(TestUtility::protoEqual(expected_data, *data)); +} + +} // namespace +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/authn/origin_authenticator.cc b/extensions/authn/origin_authenticator.cc new file mode 100644 index 00000000000..8621d1369a6 --- /dev/null +++ b/extensions/authn/origin_authenticator.cc @@ -0,0 +1,132 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn/origin_authenticator.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/match.h" +#include "authentication/v1alpha1/policy.pb.h" +#include "common/http/headers.h" +#include "common/http/utility.h" +#include "src/envoy/http/authn/authn_utils.h" + +using istio::authn::Payload; + +namespace iaapi = istio::authentication::v1alpha1; + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logTrace; +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; +using proxy_wasm::null_plugin::logWarn; + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +bool isCORSPreflightRequest(const Http::RequestHeaderMap& headers) { + return headers.Method() && + headers.Method()->value().getStringView() == + Http::Headers::get().MethodValues.Options && + headers.Origin() && !headers.Origin()->value().empty() && + headers.AccessControlRequestMethod() && + !headers.AccessControlRequestMethod()->value().empty(); +} + +OriginAuthenticator::OriginAuthenticator(FilterContext* filter_context, + const iaapi::Policy& policy) + : AuthenticatorBase(filter_context), policy_(policy) {} + +bool OriginAuthenticator::run(Payload* payload) { + if (policy_.origins_size() == 0 && + policy_.principal_binding() == iaapi::PrincipalBinding::USE_ORIGIN) { + // Validation should reject policy that have rule to USE_ORIGIN but + // does not provide any origin method so this code should + // never reach. However, it's ok to treat it as authentication + // fails. + logWarn(absl::StrCat("Principal is binded to origin, but no method specified in " + "policy {}", + policy_.DebugString())); + return false; + } + + if (isCORSPreflightRequest(filter_context()->headerMap())) { + // The CORS preflight doesn't include user credentials, allow regardless of + // JWT policy. See + // http://www.w3.org/TR/cors/#cross-origin-request-with-preflight. + logDebug("CORS preflight request allowed regardless of JWT policy"); + return true; + } + + absl::string_view path; + if (filter_context()->headerMap().Path() != nullptr) { + path = filter_context()->headerMap().Path()->value().getStringView(); + + // Trim query parameters and/or fragment if present + size_t offset = path.find_first_of("?#"); + if (offset != absl::string_view::npos) { + path.remove_suffix(path.length() - offset); + } + logTrace(absl::StrCat("Got request path {}", path)); + } else { + logError("Failed to get request path, JWT will always be used for " + "validation"); + } + + bool triggered = false; + bool triggered_success = false; + for (const auto& method : policy_.origins()) { + const auto& jwt = method.jwt(); + + if (AuthnUtils::ShouldValidateJwtPerPath(path, jwt)) { + logDebug(absl::StrCat("Validating request path {} for jwt {}", path, + jwt.DebugString())); + // set triggered to true if any of the jwt trigger rule matched. + triggered = true; + if (validateJwt(jwt, payload)) { + logDebug("JWT validation succeeded"); + triggered_success = true; + break; + } + } + } + + // returns true if no jwt was triggered, or triggered and success. + if (!triggered || triggered_success) { + filter_context()->setOriginResult(payload); + filter_context()->setPrincipal(policy_.principal_binding()); + logDebug("Origin authenticator succeeded"); + return true; + } + + logDebug("Origin authenticator failed"); + return false; +} + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif \ No newline at end of file diff --git a/extensions/authn/origin_authenticator.h b/extensions/authn/origin_authenticator.h new file mode 100644 index 00000000000..5c7fb194f3d --- /dev/null +++ b/extensions/authn/origin_authenticator.h @@ -0,0 +1,54 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "authentication/v1alpha1/policy.pb.h" +#include "src/envoy/http/authn/authenticator_base.h" + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +// OriginAuthenticator performs origin authentication for given credential rule. +class OriginAuthenticator : public AuthenticatorBase { + public: + OriginAuthenticator(FilterContext* filter_context, + const istio::authentication::v1alpha1::Policy& policy); + + bool run(istio::authn::Payload*) override; + + private: + // Reference to the authentication policy that the authenticator should + // enforce. Typically, the actual object is owned by filter. + const istio::authentication::v1alpha1::Policy& policy_; +}; + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif \ No newline at end of file diff --git a/extensions/authn/origin_authenticator_test.cc b/extensions/authn/origin_authenticator_test.cc new file mode 100644 index 00000000000..47390651fe7 --- /dev/null +++ b/extensions/authn/origin_authenticator_test.cc @@ -0,0 +1,522 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn/origin_authenticator.h" + +#include "authentication/v1alpha1/policy.pb.h" +#include "common/protobuf/protobuf.h" +#include "envoy/config/core/v3/base.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "src/envoy/http/authn/test_utils.h" +#include "test/mocks/http/mocks.h" +#include "test/test_common/utility.h" + +namespace iaapi = istio::authentication::v1alpha1; + +using istio::authn::Payload; +using istio::authn::Result; +using testing::_; +using testing::DoAll; +using testing::MockFunction; +using testing::NiceMock; +using testing::Return; +using testing::SetArgPointee; +using testing::StrictMock; + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace { + +const char kZeroOriginMethodPolicyBindPeer[] = R"( + principal_binding: USE_PEER +)"; + +const char kZeroOriginMethodPolicyBindOrigin[] = R"( + principal_binding: USE_ORIGIN +)"; + +const char kSingleOriginMethodPolicy[] = R"( + principal_binding: USE_ORIGIN + origins { + jwt { + issuer: "abc.xyz" + } + } +)"; + +const char kMultipleOriginMethodsPolicy[] = R"( + principal_binding: USE_ORIGIN + origins { + jwt { + issuer: "one" + } + } + origins { + jwt { + issuer: "two" + } + } + origins { + jwt { + issuer: "three" + } + } +)"; + +const char kPeerBinding[] = R"( + principal_binding: USE_PEER + origins { + jwt { + issuer: "abc.xyz" + } + } +)"; + +const char kSingleOriginMethodWithTriggerRulePolicy[] = R"( + principal_binding: USE_ORIGIN + origins { + jwt { + issuer: "abc.xyz" + trigger_rules: { + included_paths: { + exact: "/allow" + } + } + } + } +)"; + +const char kSingleOriginMethodWithExcludeTriggerRulePolicy[] = R"( + principal_binding: USE_ORIGIN + origins { + jwt { + issuer: "abc.xyz" + trigger_rules: { + excluded_paths: { + exact: "/login" + } + } + } + } +)"; + +const char kMultipleOriginMethodWithTriggerRulePolicy[] = R"( + principal_binding: USE_ORIGIN + origins { + jwt { + issuer: "one" + trigger_rules: { + excluded_paths: { + exact: "/bad" + } + } + } + } + origins { + jwt { + issuer: "two" + trigger_rules: { + included_paths: { + exact: "/two" + } + } + } + } + origins { + jwt { + issuer: "three" + trigger_rules: { + included_paths: { + exact: "/allow" + } + } + } + } +)"; + +class MockOriginAuthenticator : public OriginAuthenticator { + public: + MockOriginAuthenticator(FilterContext* filter_context, + const iaapi::Policy& policy) + : OriginAuthenticator(filter_context, policy) {} + + MOCK_CONST_METHOD2(validateX509, bool(const iaapi::MutualTls&, Payload*)); + MOCK_METHOD2(validateJwt, bool(const iaapi::Jwt&, Payload*)); +}; + +class OriginAuthenticatorTest : public testing::TestWithParam { + public: + OriginAuthenticatorTest() {} + virtual ~OriginAuthenticatorTest() {} + + void SetUp() override { + expected_result_when_pass_ = TestUtilities::AuthNResultFromString(R"( + principal: "foo" + origin { + user: "foo" + presenter: "istio.io" + } + )"); + set_peer_ = GetParam(); + if (set_peer_) { + auto peer_result = TestUtilities::CreateX509Payload("bar"); + filter_context_.setPeerResult(&peer_result); + expected_result_when_pass_.set_peer_user("bar"); + } + initial_result_ = filter_context_.authenticationResult(); + payload_ = new Payload(); + } + + void TearDown() override { delete (payload_); } + + void createAuthenticator() { + authenticator_.reset( + new StrictMock(&filter_context_, policy_)); + } + + protected: + std::unique_ptr> authenticator_; + // envoy::config::core::v3::Metadata metadata_; + Envoy::Http::TestRequestHeaderMapImpl header_{}; + FilterContext filter_context_{ + envoy::config::core::v3::Metadata::default_instance(), header_, nullptr, + istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig:: + default_instance()}; + iaapi::Policy policy_; + + Payload* payload_; + + // Mock response payload. + Payload jwt_payload_{TestUtilities::CreateJwtPayload("foo", "istio.io")}; + Payload jwt_extra_payload_{ + TestUtilities::CreateJwtPayload("bar", "istio.io")}; + + // Expected result (when authentication pass with mock payload above) + Result expected_result_when_pass_; + // Copy of authN result (from filter context) before running authentication. + // This should be the expected result if authn fail or do nothing. + Result initial_result_; + + // Indicates peer is set in the authN result before running. This is set from + // test GetParam() + bool set_peer_; + + void setPath(const std::string& path) { + header_.removePath(); + header_.addCopy(":path", path); + } + + void addHeader(const std::string& key, const std::string& value) { + header_.addCopy(key, value); + } +}; + +TEST_P(OriginAuthenticatorTest, Empty) { + createAuthenticator(); + authenticator_->run(payload_); + if (set_peer_) { + initial_result_.set_principal("bar"); + } + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +// It should fail if the binding is USE_ORIGIN but origin methods are empty. +TEST_P(OriginAuthenticatorTest, ZeroMethodFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kZeroOriginMethodPolicyBindOrigin, &policy_)); + createAuthenticator(); + EXPECT_FALSE(authenticator_->run(payload_)); +} + +// It should pass if the binding is USE_PEER and origin methods are empty. +TEST_P(OriginAuthenticatorTest, ZeroMethodPass) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kZeroOriginMethodPolicyBindPeer, &policy_)); + createAuthenticator(); + + Result expected_result = TestUtilities::AuthNResultFromString(R"( + origin { + user: "bar" + presenter: "istio.io" + } + )"); + if (set_peer_) { + expected_result.set_principal("bar"); + expected_result.set_peer_user("bar"); + } + + EXPECT_TRUE(authenticator_->run(&jwt_extra_payload_)); + EXPECT_TRUE(TestUtility::protoEqual(expected_result, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, SingleMethodPass) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, + &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, SingleMethodFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, + &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(false))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, CORSPreflight) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, + &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)).Times(0); + + addHeader(":method", "OPTIONS"); + addHeader("origin", "example.com"); + addHeader("access-control-request-method", "GET"); + EXPECT_TRUE(authenticator_->run(payload_)); +} + +TEST_P(OriginAuthenticatorTest, TriggeredWithNullPath) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kSingleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, SingleRuleTriggered) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kSingleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + setPath("/allow"); + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, SingleRuleTriggeredWithComponents) { + const std::vector input_paths{"/allow?", + "/allow?a=b&c=d", + "/allow??", + "/allow??", + "/allow?#", + "/allow#?", + "/allow#a", + "/allow#$" + "/allow?a=b#c", + "/allow#a?b=c"}; + for (const auto& path : input_paths) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kSingleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + setPath(path); + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual( + expected_result_when_pass_, filter_context_.authenticationResult())); + } +} + +TEST_P(OriginAuthenticatorTest, SingleRuleNotTriggered) { + const std::vector input_paths{"/bad", "/allow$?", "/allow$#"}; + for (const auto& path : input_paths) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kSingleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)).Times(0); + + setPath(path); + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual( + initial_result_, filter_context_.authenticationResult())); + } +} + +TEST_P(OriginAuthenticatorTest, SingleExcludeRuleTriggeredWithQueryParam) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kSingleOriginMethodWithExcludeTriggerRulePolicy, &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)).Times(0); + + setPath("/login?a=b&c=d"); + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, Multiple) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kMultipleOriginMethodsPolicy, &policy_)); + + createAuthenticator(); + + // First method fails, second success (thus third is ignored) + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(2) + .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, MultipleFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kMultipleOriginMethodsPolicy, &policy_)); + + createAuthenticator(); + + // All fail. + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(3) + .WillRepeatedly( + DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, MultipleRuleTriggeredValidationSucceeded) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kMultipleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + // First method triggered but failed, second method not triggered, third + // method triggered and succeeded. + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(2) + .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + setPath("/allow"); + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, MultipleRuleTriggeredValidationFailed) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kMultipleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + // Triggered on first and second method but all failed. + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(2) + .WillRepeatedly( + DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + + setPath("/two"); + EXPECT_FALSE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, MultipleRuleNotTriggered) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + kMultipleOriginMethodWithTriggerRulePolicy, &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateJwt(_, _)).Times(0); + + setPath("/bad"); + EXPECT_TRUE(authenticator_->run(payload_)); + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, PeerBindingPass) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); + // Expected principal is from peer_user. + expected_result_when_pass_.set_principal(initial_result_.peer_user()); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + filter_context_.authenticationResult())); +} + +TEST_P(OriginAuthenticatorTest, PeerBindingFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); + createAuthenticator(); + + // All fail. + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(false))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + filter_context_.authenticationResult())); +} + +INSTANTIATE_TEST_SUITE_P(OriginAuthenticatorTests, OriginAuthenticatorTest, + testing::Bool()); + +} // namespace +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/authn/peer_authenticator.cc b/extensions/authn/peer_authenticator.cc new file mode 100644 index 00000000000..20a95ee3d09 --- /dev/null +++ b/extensions/authn/peer_authenticator.cc @@ -0,0 +1,87 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "absl/strings/str_cat.h" +#include "src/envoy/http/authn/peer_authenticator.h" + +#include "common/http/utility.h" +#include "src/envoy/utils/utils.h" + +using istio::authn::Payload; + +namespace iaapi = istio::authentication::v1alpha1; +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logTrace; +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; +using proxy_wasm::null_plugin::logWarn; + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +PeerAuthenticator::PeerAuthenticator(FilterContext* filter_context, + const iaapi::Policy& policy) + : AuthenticatorBase(filter_context), policy_(policy) {} + +bool PeerAuthenticator::run(Payload* payload) { + bool success = false; + if (policy_.peers_size() == 0) { + logDebug("No method defined. Skip source authentication."); + success = true; + return success; + } + for (const auto& method : policy_.peers()) { + switch (method.params_case()) { + case iaapi::PeerAuthenticationMethod::ParamsCase::kMtls: + success = validateX509(method.mtls(), payload); + break; + case iaapi::PeerAuthenticationMethod::ParamsCase::kJwt: + success = validateJwt(method.jwt(), payload); + break; + default: + logError(absl::StrCat("Unknown peer authentication param ", + method.DebugString())); + success = false; + break; + } + + if (success) { + break; + } + } + + if (success) { + filter_context()->setPeerResult(payload); + } + + return success; +} + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif \ No newline at end of file diff --git a/extensions/authn/peer_authenticator.h b/extensions/authn/peer_authenticator.h new file mode 100644 index 00000000000..317adcce51d --- /dev/null +++ b/extensions/authn/peer_authenticator.h @@ -0,0 +1,54 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "authentication/v1alpha1/policy.pb.h" +#include "src/envoy/http/authn/authenticator_base.h" + +// WASM_PROLOG +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else // NULL_PLUGIN + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif // NULL_PLUGIN + +// PeerAuthenticator performs peer authentication for given policy. +class PeerAuthenticator : public AuthenticatorBase { + public: + PeerAuthenticator(FilterContext* filter_context, + const istio::authentication::v1alpha1::Policy& policy); + + bool run(istio::authn::Payload*) override; + + private: + // Reference to the authentication policy that the authenticator should + // enforce. Typically, the actual object is owned by filter. + const istio::authentication::v1alpha1::Policy& policy_; +}; + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm +#endif \ No newline at end of file diff --git a/extensions/authn/peer_authenticator_test.cc b/extensions/authn/peer_authenticator_test.cc new file mode 100644 index 00000000000..56f95f9f980 --- /dev/null +++ b/extensions/authn/peer_authenticator_test.cc @@ -0,0 +1,347 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn/peer_authenticator.h" + +#include "authentication/v1alpha1/policy.pb.h" +#include "common/protobuf/protobuf.h" +#include "envoy/config/core/v3/base.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "src/envoy/http/authn/test_utils.h" +#include "test/mocks/http/mocks.h" +#include "test/test_common/utility.h" + +namespace iaapi = istio::authentication::v1alpha1; + +using istio::authn::Payload; +using testing::_; +using testing::DoAll; +using testing::MockFunction; +using testing::NiceMock; +using testing::Return; +using testing::SetArgPointee; +using testing::StrictMock; + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace { + +class MockPeerAuthenticator : public PeerAuthenticator { + public: + MockPeerAuthenticator(FilterContext* filter_context, + const istio::authentication::v1alpha1::Policy& policy) + : PeerAuthenticator(filter_context, policy) {} + + MOCK_CONST_METHOD2(validateX509, bool(const iaapi::MutualTls&, Payload*)); + MOCK_METHOD2(validateJwt, bool(const iaapi::Jwt&, Payload*)); +}; + +class PeerAuthenticatorTest : public testing::Test { + public: + PeerAuthenticatorTest() {} + virtual ~PeerAuthenticatorTest() {} + + void createAuthenticator() { + authenticator_.reset( + new StrictMock(&filter_context_, policy_)); + } + + void SetUp() override { payload_ = new Payload(); } + + void TearDown() override { delete (payload_); } + + protected: + std::unique_ptr> authenticator_; + Envoy::Http::TestRequestHeaderMapImpl header_; + FilterContext filter_context_{ + envoy::config::core::v3::Metadata::default_instance(), header_, nullptr, + istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig:: + default_instance()}; + + iaapi::Policy policy_; + Payload* payload_; + + Payload x509_payload_{TestUtilities::CreateX509Payload("foo")}; + Payload jwt_payload_{TestUtilities::CreateJwtPayload("foo", "istio.io")}; + Payload jwt_extra_payload_{ + TestUtilities::CreateJwtPayload("bar", "istio.io")}; +}; + +TEST_F(PeerAuthenticatorTest, EmptyPolicy) { + createAuthenticator(); + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, MTlsOnlyPass) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls { + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual( + TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, TlsOnlyPass) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls { + allow_tls: true + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); + + authenticator_->run(payload_); + // When client certificate is present on TLS, authenticated attribute + // should be extracted. + EXPECT_TRUE(TestUtility::protoEqual( + TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, MTlsOnlyFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls { + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, TlsOnlyFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls { + allow_tls: true + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); + + authenticator_->run(payload_); + // When TLS authentication failse, the authenticated attribute should be + // empty. + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, JwtOnlyPass) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + jwt { + issuer: "abc.xyz" + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual( + TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, JwtOnlyFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + jwt { + issuer: "abc.xyz" + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, Multiple) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls {} + } + peers { + jwt { + issuer: "abc.xyz" + } + } + peers { + jwt { + issuer: "another" + } + } + )", + &policy_)); + + createAuthenticator(); + + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual( + TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, TlsFailAndJwtSucceed) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls { allow_tls: true } + } + peers { + jwt { + issuer: "abc.xyz" + } + } + peers { + jwt { + issuer: "another" + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); + authenticator_->run(payload_); + // validateX509 fail and validateJwt succeeds, + // result should be "foo", as expected as in jwt_payload. + EXPECT_TRUE(TestUtility::protoEqual( + TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, MultipleAllFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls {} + } + peers { + jwt { + issuer: "abc.xyz" + } + } + peers { + jwt { + issuer: "another" + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(2) + .WillRepeatedly( + DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + authenticator_->run(payload_); + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + filter_context_.authenticationResult())); +} + +TEST_F(PeerAuthenticatorTest, TlsFailJwtFail) { + ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + peers { + mtls { allow_tls: true } + } + peers { + jwt { + issuer: "abc.xyz" + } + } + peers { + jwt { + issuer: "another" + } + } + )", + &policy_)); + + createAuthenticator(); + EXPECT_CALL(*authenticator_, validateX509(_, _)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + EXPECT_CALL(*authenticator_, validateJwt(_, _)) + .Times(2) + .WillRepeatedly( + DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); + authenticator_->run(payload_); + // validateX509 and validateJwt fail, result should be empty. + EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + filter_context_.authenticationResult())); +} + +} // namespace +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/authn/plugin.cc b/extensions/authn/plugin.cc new file mode 100644 index 00000000000..c19fb5376bc --- /dev/null +++ b/extensions/authn/plugin.cc @@ -0,0 +1,43 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#include "src/envoy/http/authn_wasm/filter.h" + +#include "absl/strings/str_cat.h" +#include "authentication/v1alpha1/policy.pb.h" +#include "google/protobuf/text_format.h" +#include "google/protobuf/util/json_util.h" +#include "src/envoy/http/authn_wasm/connection_context.h" +#include "src/envoy/http/authn_wasm/peer.h" + +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif + +FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { + return FilterHeadersStatus::Continue; +} + + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/extensions/authn/plugin.h b/extensions/authn/plugin.h new file mode 100644 index 00000000000..b431e39d181 --- /dev/null +++ b/extensions/authn/plugin.h @@ -0,0 +1,146 @@ +/* Copyright 2020 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "absl/strings/string_view.h" +#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" +#include "proxy_wasm_intrinsics.h" +#include "src/envoy/http/authn_wasm/base.h" + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics.h" +#else +#include "include/proxy-wasm/null_plugin.h" + +using proxy_wasm::null_plugin::logDebug; +using proxy_wasm::null_plugin::logError; + +namespace proxy_wasm { +namespace null_plugin { +namespace AuthN { + +#endif + +using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; +using StringView = absl::string_view; + +// AuthnRootContext is the root context for all streams processed by the +// thread. It has the same lifetime as the worker thread and acts as target for +// interactions that outlives individual stream, e.g. timer, async calls. +class AuthnRootContext : public RootContext { + public: + AuthnRootContext(uint32_t id, absl::string_view root_id) + : RootContext(id, root_id) {} + ~AuthnRootContext() {} + + // RootContext + bool validateConfiguration(size_t) override { return true; } + bool onConfigure(size_t) override { return true; }; + bool onStart(size_t) override { return true; } + void onTick() override {} + void onQueueReady(uint32_t) override {} + bool onDone() override { return true; } + + // Low level HTTP/gRPC interface. + void onHttpCallResponse(uint32_t token, uint32_t headers, size_t body_size, + uint32_t trailers) override {} + void onGrpcReceiveInitialMetadata(uint32_t token, uint32_t headers) override { + } + void onGrpcReceiveTrailingMetadata(uint32_t token, + uint32_t trailers) override {} + void onGrpcReceive(uint32_t token, size_t body_size) override {} + void onGrpcClose(uint32_t token, GrpcStatus status) override {} + + const FilterConfig& filterConfig() { return filter_config_; }; + + private: + FilterConfig filter_config_; +}; + +// Per-stream context. +class AuthnContext : public Context { + public: + explicit AuthnContext(uint32_t id, RootContext* root) : Context(id, root) {} + ~AuthnContext() = default; + + void onCreate() override {} + + // Context + FilterStatus onNewConnection() override { return FilterStatus::Continue; } + FilterStatus onDownstreamData(size_t, bool) override { + return FilterStatus::Continue; + } + FilterStatus onUpstreamData(size_t, bool) override { + return FilterStatus::Continue; + } + void onDownstreamConnectionClose(PeerType) override {} + void onUpstreamConnectionClose(PeerType) override {} + FilterHeadersStatus onRequestHeaders(uint32_t) override; + FilterMetadataStatus onRequestMetadata(uint32_t) override { + return FilterMetadataStatus::Continue; + } + FilterDataStatus onRequestBody(size_t, bool) override { + return FilterDataStatus::Continue; + } + FilterTrailersStatus onRequestTrailers(uint32_t) override { + return FilterTrailersStatus::Continue; + } + FilterHeadersStatus onResponseHeaders(uint32_t) override { + return FilterHeadersStatus::Continue; + } + FilterMetadataStatus onResponseMetadata(uint32_t) override { + return FilterMetadataStatus::Continue; + } + FilterDataStatus onResponseBody(size_t, bool) override { + return FilterDataStatus::Continue; + } + FilterTrailersStatus onResponseTrailers(uint32_t) override { + return FilterTrailersStatus::Continue; + } + void onDone() override {} + void onLog() override {} + + const FilterConfig& filterConfig() { return rootContext()->filterConfig(); }; + + private: + std::unique_ptr createPeerAuthenticator( + FilterContextPtr filter_context); + // TODO(shikugawa): origin authenticator implementation. + // std::unique_ptr createOriginAuthenticator( + // istio::AuthN::FilterContext* filter_context); + + inline AuthnRootContext* rootContext() { + return dynamic_cast(this->root()); + }; + + // Context for authentication process. Created in decodeHeader to start + // authentication process. + FilterContextPtr filter_context_; +}; + +#ifdef NULL_PLUGIN +PROXY_WASM_NULL_PLUGIN_REGISTRY; +#endif + +static RegisterContextFactory register_AuthnWasm( + CONTEXT_FACTORY(AuthnContext), ROOT_FACTORY(AuthnRootContext)); + +#ifdef NULL_PLUGIN +} // namespace AuthN +} // namespace null_plugin +} // namespace proxy_wasm + +#endif \ No newline at end of file diff --git a/extensions/authn/sample/APToken/APToken-example1.jwt b/extensions/authn/sample/APToken/APToken-example1.jwt new file mode 100644 index 00000000000..82f1e6ab448 --- /dev/null +++ b/extensions/authn/sample/APToken/APToken-example1.jwt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJleGFtcGxlLWF1ZGllbmNlIiwiZW1haWwiOiJmb29AZ29vZ2xlLmNvbSIsImV4cCI6NDY5ODM2MTUwOCwiaWF0IjoxNTQ0NzYxNTA4LCJpc3MiOiJodHRwczovL2V4YW1wbGUudG9rZW5fc2VydmljZS5jb20iLCJpc3Rpb19hdHRyaWJ1dGVzIjpbeyJzb3VyY2UuaXAiOiIxMjcuMC4wLjEifV0sImtleTEiOlsidmFsMiIsInZhbDMiXSwib3JpZ2luYWxfY2xhaW1zIjp7ImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZXhhbXBsZS5jb20iLCJzdWIiOiJleGFtcGxlLXN1YmplY3QifSwic3ViIjoiaHR0cHM6Ly9hY2NvdW50cy5leGFtcGxlLmNvbS8xMjM0NTU2Nzg5MCJ9.mLm9Gmcd748anwybiPxGPEuYgJBChqoHkVOvRhQN-H9jMqVKyF-7ynud1CJp5n72VeMB1FzvKAV0ErzSyWQc0iofQywG6whYXP6zL-Oc0igUrLDvzb6PuBDkbWOcZrvHkHM4tIYAkF4j880GqMWEP3gGrykziIEY9g4povquCFSdkLjjyol2-Ge_6MFdayYoeWLLOaMP7tHiPTm_ajioQ4jcz5whBWu3DZWx4IuU5UIBYlHG_miJZv5zmwwQ60T1_p_sW7zkABJgDhCvu6cHh6g-hZdQvZbATFwMfN8VDzttTjRG8wuLlkQ1TTOCx5PDv-_gHfQfRWt8Z94HrIJPuQ \ No newline at end of file diff --git a/extensions/authn/sample/APToken/aptoken-envoy.conf b/extensions/authn/sample/APToken/aptoken-envoy.conf new file mode 100644 index 00000000000..a5905812f7c --- /dev/null +++ b/extensions/authn/sample/APToken/aptoken-envoy.conf @@ -0,0 +1,118 @@ +{ + "admin": { + "access_log_path": "/dev/stdout", + "address": { + "socket_address": { + "address": "0.0.0.0", + "port_value": 9001 + } + } + }, + "static_resources": { + "clusters": [ + { + "name": "service1", + "connect_timeout": "5s", + "type": "STATIC", + "hosts": [ + { + "socket_address": { + "address": "0.0.0.0", + "port_value": 8080 + } + } + ] + } + ], + "listeners": [ + { + "name": "server", + "address": { + "socket_address": { + "address": "0.0.0.0", + "port_value": 9090 + } + }, + "filter_chains": [ + { + "filters": [ + { + "name": "envoy.http_connection_manager", + "config": { + "codec_type": "AUTO", + "stat_prefix": "inbound_http", + "access_log": [ + { + "name": "envoy.file_access_log", + "config": { + "path": "/tmp/envoy-access.log" + } + } + ], + "http_filters": [ + { + "name": "jwt-auth", + "config": { + "rules": [ + { + "issuer": "https://example.token_service.com", + "local_jwks": { + "inline_string": "{ \"keys\":[ {\"e\":\"AQAB\",\"kid\":\"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ\",\"kty\":\"RSA\",\"n\":\"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ\"}]}", + }, + "from_headers": [{"name": "ingress-authorization"}], + "forward_payload_header": "test-jwt-payload-output" + } + ] + } + }, + { + "name":"istio_authn", + "config":{ + "policy":{ + "origins":[ + { + "jwt":{ + "issuer":"https://example.token_service.com", + "jwt_headers":["ingress-authorization"] + } + } + ], + "principal_binding":1 + } + } + }, + { + "name": "envoy.router" + } + ], + "route_config": { + "name": "backend", + "virtual_hosts": [ + { + "name": "backend", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "service1", + "timeout": "0s" + } + } + ] + } + ] + } + } + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/extensions/authn/sample/APToken/guide.txt b/extensions/authn/sample/APToken/guide.txt new file mode 100644 index 00000000000..7fdf21c9d6f --- /dev/null +++ b/extensions/authn/sample/APToken/guide.txt @@ -0,0 +1,18 @@ +This is a guide of sending an example exchanged token to +the jwt-authn filter and the Istio authn filter, and observing +that the example backend echoes back the request when +the authentication succeeds. + +1. Open a terminal, go to the root directory of the istio-proxy repository. +Start the example backend: + go run test/backend/echo/echo.go + +2. Build the Istio proxy and run the proxy with the config for authenticating +an example exchanged token. + bazel build //src/envoy:envoy + bazel-bin/src/envoy/envoy -l debug -c src/envoy/http/jwt_auth/sample/APToken/aptoken-envoy.conf + +3. Open a terminal, go to the root directory of the istio-proxy repository. +Send a request with the example exchanged token. + export token=$(cat src/envoy/http/jwt_auth/sample/APToken/APToken-example1.jwt) + curl --header "ingress-authorization:$token" http://localhost:9090/echo -d "hello world" diff --git a/extensions/authn/test_utils.h b/extensions/authn/test_utils.h new file mode 100644 index 00000000000..40ffaacc6c5 --- /dev/null +++ b/extensions/authn/test_utils.h @@ -0,0 +1,54 @@ +/* Copyright 2018 Istio Authors. All Rights Reserved. + * + * Licensed 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. + */ + +#pragma once + +#include "common/protobuf/protobuf.h" +#include "gmock/gmock.h" +#include "src/istio/authn/context.pb.h" + +namespace Envoy { +namespace Http { +namespace Istio { +namespace AuthN { +namespace TestUtilities { + +istio::authn::Payload CreateX509Payload(const std::string& user) { + istio::authn::Payload payload; + payload.mutable_x509()->set_user(user); + return payload; +} + +istio::authn::Payload CreateJwtPayload(const std::string& user, + const std::string& presenter) { + istio::authn::Payload payload; + payload.mutable_jwt()->set_user(user); + if (!presenter.empty()) { + payload.mutable_jwt()->set_presenter(presenter); + } + return payload; +} + +istio::authn::Result AuthNResultFromString(const std::string& text) { + istio::authn::Result result; + EXPECT_TRUE(Protobuf::TextFormat::ParseFromString(text, &result)); + return result; +} + +} // namespace TestUtilities +} // namespace AuthN +} // namespace Istio +} // namespace Http +} // namespace Envoy diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index c72a76aae90..8452a3f9658 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -65,6 +65,7 @@ absl::optional JsonValueAs(const ::nlohmann::json& j) { if (v == "true") { return true; } else if (v == "false") { +<<<<<<< HEAD return false; } } @@ -74,6 +75,20 @@ absl::optional JsonValueAs(const ::nlohmann::json& j) { bool JsonArrayIterate( const ::nlohmann::json& j, absl::string_view field, const std::function& visitor) { +======= + return std::make_pair(false, JsonParserResultDetail::OK); + } else { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::INVALID_VALUE); + } + } + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); +} + +bool JsonArrayIterate( + const JsonObject& j, absl::string_view field, + const std::function& visitor) { +>>>>>>> e598b67b... fix auto it = j.find(field); if (it == j.end()) { return true; diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 6835fa41a45..7c601500893 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -58,8 +58,13 @@ absl::optional JsonGetField(const ::nlohmann::json& j, // Returns false if set and not an array, or any of the visitor calls returns // false. bool JsonArrayIterate( +<<<<<<< HEAD const ::nlohmann::json& j, absl::string_view field, const std::function& visitor); +======= + const JsonObject& j, absl::string_view field, + const std::function& visitor); +>>>>>>> e598b67b... fix // Iterate over an optional object field key set. // Returns false if set and not an object, or any of the visitor calls returns diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index 2287426b775..d9c5b9a5073 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -264,9 +264,15 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process the metric definitions (overriding existing). if (!JsonArrayIterate(j, "definitions", [&](const json& definition) -> bool { +<<<<<<< HEAD auto name = JsonGetField(definition, "name").value_or(""); auto value = JsonGetField(definition, "value").value_or(""); +======= + auto name = JsonGetField(definition, "name").fetch_or(""); + auto value = + JsonGetField(definition, "value").fetch_or(""); +>>>>>>> e598b67b... fix if (name.empty() || value.empty()) { LOG_WARN("empty name or value in 'definitions'"); return false; @@ -290,7 +296,11 @@ bool PluginRootContext::initializeDimensions(const json& j) { }; factory.type = MetricType::Counter; auto type = +<<<<<<< HEAD JsonGetField(definition, "type").value_or(""); +======= + JsonGetField(definition, "type").fetch_or(""); +>>>>>>> e598b67b... fix if (type == "GAUGE") { factory.type = MetricType::Gauge; } else if (type == "HISTOGRAM") { diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index b9bddd63be1..6453cd32af2 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -384,12 +384,31 @@ Jwt::Jwt(const std::string &jwt) { // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. +<<<<<<< HEAD auto actual_list_aud = getProtoListValue(payload_fields, "aud"); if (actual_list_aud.has_value()) { aud_ = actual_list_aud.value(); } else { auto actual_str_aud = getProtoMapValue(payload_fields, "aud"); if (actual_str_aud.has_value()) aud_.emplace_back(actual_str_aud.value()); +======= + if (!Wasm::Common::JsonArrayIterate( + payload_, "aud", [&](const Wasm::Common::JsonObject &obj) -> bool { + auto str_obj_result = Wasm::Common::JsonValueAs(obj); + if (str_obj_result.second != + Wasm::Common::JsonParserResultDetail::OK) { + return false; + } + aud_.emplace_back(str_obj_result.first.value()); + return true; + })) { + auto aud_field = Wasm::Common::JsonGetField(payload_, "aud"); + if (aud_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { + UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); + return; + } + aud_.emplace_back(aud_field.fetch()); +>>>>>>> e598b67b... fix } // Set up signature @@ -564,9 +583,18 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { return; } +<<<<<<< HEAD auto actual_keys = getProtoListValue(jwks_field, "keys"); if (!actual_keys.has_value()) { +======= + if (!Wasm::Common::JsonArrayIterate( + jwks_json, "keys", [&](const Wasm::Common::JsonObject &obj) -> bool { + key_refs.emplace_back( + std::reference_wrapper(obj)); + return true; + })) { +>>>>>>> e598b67b... fix UpdateStatus(Status::JWK_BAD_KEYS); return; } From c9b0066702b6f71432a2e1a86e75328c3e7c5c8c Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 03:56:31 +0000 Subject: [PATCH 18/44] fix --- extensions/common/json_util.h | 3 +++ extensions/stats/plugin.cc | 10 ++++++++++ src/envoy/http/jwt_auth/jwt.cc | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 7c601500893..0b11657dbb3 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -64,6 +64,9 @@ bool JsonArrayIterate( ======= const JsonObject& j, absl::string_view field, const std::function& visitor); +<<<<<<< HEAD +>>>>>>> e598b67b... fix +======= >>>>>>> e598b67b... fix // Iterate over an optional object field key set. diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index d9c5b9a5073..08b4e518958 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -264,10 +264,16 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process the metric definitions (overriding existing). if (!JsonArrayIterate(j, "definitions", [&](const json& definition) -> bool { +<<<<<<< HEAD <<<<<<< HEAD auto name = JsonGetField(definition, "name").value_or(""); auto value = JsonGetField(definition, "value").value_or(""); +======= + auto name = JsonGetField(definition, "name").fetch_or(""); + auto value = + JsonGetField(definition, "value").fetch_or(""); +>>>>>>> e598b67b... fix ======= auto name = JsonGetField(definition, "name").fetch_or(""); auto value = @@ -296,8 +302,12 @@ bool PluginRootContext::initializeDimensions(const json& j) { }; factory.type = MetricType::Counter; auto type = +<<<<<<< HEAD <<<<<<< HEAD JsonGetField(definition, "type").value_or(""); +======= + JsonGetField(definition, "type").fetch_or(""); +>>>>>>> e598b67b... fix ======= JsonGetField(definition, "type").fetch_or(""); >>>>>>> e598b67b... fix diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 6453cd32af2..24e59079817 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -384,6 +384,7 @@ Jwt::Jwt(const std::string &jwt) { // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. +<<<<<<< HEAD <<<<<<< HEAD auto actual_list_aud = getProtoListValue(payload_fields, "aud"); if (actual_list_aud.has_value()) { @@ -392,6 +393,8 @@ Jwt::Jwt(const std::string &jwt) { auto actual_str_aud = getProtoMapValue(payload_fields, "aud"); if (actual_str_aud.has_value()) aud_.emplace_back(actual_str_aud.value()); ======= +======= +>>>>>>> e598b67b... fix if (!Wasm::Common::JsonArrayIterate( payload_, "aud", [&](const Wasm::Common::JsonObject &obj) -> bool { auto str_obj_result = Wasm::Common::JsonValueAs(obj); @@ -583,11 +586,14 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { return; } +<<<<<<< HEAD <<<<<<< HEAD auto actual_keys = getProtoListValue(jwks_field, "keys"); if (!actual_keys.has_value()) { ======= +======= +>>>>>>> e598b67b... fix if (!Wasm::Common::JsonArrayIterate( jwks_json, "keys", [&](const Wasm::Common::JsonObject &obj) -> bool { key_refs.emplace_back( From 9f80d3d029315530a9b3615194d55a07e62d3cf3 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 11:14:04 +0000 Subject: [PATCH 19/44] stash --- WORKSPACE | 6 +- extensions/BUILD | 29 +++ extensions/authn/BUILD | 41 +--- extensions/authn/authenticator_base.cc | 4 +- extensions/authn/authenticator_base.h | 2 +- extensions/authn/authn_utils.h | 2 +- extensions/authn/filter_context.cc | 2 +- extensions/authn/origin_authenticator.cc | 4 +- extensions/authn/origin_authenticator.h | 2 +- extensions/authn/peer_authenticator.cc | 2 +- extensions/authn/peer_authenticator.h | 2 +- extensions/authn/plugin.cc | 6 +- extensions/authn/plugin.h | 3 +- extensions/common/json_util.cc | 92 +++++--- extensions/common/json_util.h | 96 ++++++-- extensions/metadata_exchange/plugin.cc | 16 +- extensions/stats/plugin.cc | 55 ++--- src/envoy/http/jwt_auth/BUILD | 2 +- src/envoy/http/jwt_auth/jwt.cc | 285 ++++++++--------------- src/envoy/http/jwt_auth/jwt.h | 24 +- src/envoy/http/jwt_auth/jwt_test.cc | 68 +++--- 21 files changed, 364 insertions(+), 379 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index eb4fa576955..18bc63c5484 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -38,10 +38,10 @@ bind( # 1. Determine SHA256 `wget https://github.com/envoyproxy/envoy-wasm/archive/$COMMIT.tar.gz && sha256sum $COMMIT.tar.gz` # 2. Update .bazelversion, envoy.bazelrc and .bazelrc if needed. # -# Commit time: 5/19/20 -ENVOY_SHA = "ee990c97332793e29eff11fa2773996857a5f5d3" +# Commit time: 6/12/20 +ENVOY_SHA = "cc7b50f6fe65a42534cf8f0e6e8ff784fbfe104c" -ENVOY_SHA256 = "92b0d107d316371165c6ecd83ca24d0b1e791224a737ba386e9d58217d517209" +ENVOY_SHA256 = "53c947c8c7242c8f9f335a1765104d33814e0bc8ca830651e2a18dbffdd2c201" ENVOY_ORG = "envoyproxy" diff --git a/extensions/BUILD b/extensions/BUILD index 02aa31d00c8..e4f80188208 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -47,6 +47,35 @@ wasm_cc_binary( ], ) +wasm_cc_binary( + name = "authn.wasm", + srcs = [ + "//extensions/authn:authenticator_base.h", + "//extensions/authn:authenticator_base.cc", + # "//extensions/authn:authn_utils.h", + # "//extensions/authn:authn_utils.cc", + "//extensions/authn:filter_context.h", + "//extensions/authn:filter_context.cc", + # "//extensions/authn:origin_authenticator.h", + # "//extensions/authn:origin_authenticator.cc", + # "//extensions/authn:peer_authenticator.h", + # "//extensions/authn:peer_authenticator.cc", + "//extensions/authn:plugin.h", + "//extensions/authn:plugin.cc", + ], + copts = ["-UNULL_PLUGIN"], + deps = [ + "//external:authentication_policy_config_cc_proto", + "//src/istio/authn:context_proto_cc_wasm", + "//extensions/common:json_util_wasm", + "@com_google_absl_wasm//absl/strings", + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", + "@proxy_wasm_cpp_sdk//contrib:contrib_lib", + "@envoy//source/common/http:headers_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + wasm_cc_binary( name = "metadata_exchange.wasm", srcs = [ diff --git a/extensions/authn/BUILD b/extensions/authn/BUILD index 7553e2546dc..66ca3f471f4 100644 --- a/extensions/authn/BUILD +++ b/extensions/authn/BUILD @@ -25,49 +25,24 @@ load( ) envoy_cc_library( - name = "authenticator", + name = "authn_plugin", srcs = [ + "plugin.cc", "authenticator_base.cc", - "authn_utils.cc", "filter_context.cc", - "origin_authenticator.cc", - "peer_authenticator.cc", ], hdrs = [ + "plugin.h", "authenticator_base.h", - "authn_utils.h", "filter_context.h", - "origin_authenticator.h", - "peer_authenticator.h", ], repository = "@envoy", + visibility = ["//visibility:public"], deps = [ - "//external:authentication_policy_config_cc_proto", - "//src/envoy/http/jwt_auth:jwt_lib", - "//src/envoy/utils:filter_names_lib", - "//src/envoy/utils:utils_lib", - "//src/istio/authn:context_proto_cc_proto", - "@envoy//source/common/http:headers_lib", - ], -) - -envoy_cc_library( - name = "authn_plugin", - srcs = [ - "plugin.cc", - ], - hdrs = [ - "plugin.h", - ], - repository = "@envoy", - deps = [ - ":authenticator", - "//external:authentication_policy_config_cc_proto", - "//src/envoy/utils:authn_lib", - "//src/envoy/utils:filter_names_lib", - "//src/envoy/utils:utils_lib", - "//src/istio/authn:context_proto_cc_proto", - "@envoy//source/exe:envoy_common_lib", + "//extensions/common:context", + "//extensions/common:json_util", + "@proxy_wasm_cpp_host//:lib", + "@proxy_wasm_cpp_sdk//contrib:contrib_lib", ], ) diff --git a/extensions/authn/authenticator_base.cc b/extensions/authn/authenticator_base.cc index 7fff3586e2d..073eca0a826 100644 --- a/extensions/authn/authenticator_base.cc +++ b/extensions/authn/authenticator_base.cc @@ -14,11 +14,11 @@ */ #include "absl/strings/str_cat.h" -#include "src/envoy/http/authn/authenticator_base.h" +#include "extensions/authn/authenticator_base.h" #include "common/common/assert.h" #include "common/config/metadata.h" -#include "src/envoy/http/authn/authn_utils.h" +#include "extensions/authn/authn_utils.h" #include "src/envoy/utils/filter_names.h" #include "src/envoy/utils/utils.h" diff --git a/extensions/authn/authenticator_base.h b/extensions/authn/authenticator_base.h index 69b76c87705..b84ac21bbf6 100644 --- a/extensions/authn/authenticator_base.h +++ b/extensions/authn/authenticator_base.h @@ -16,7 +16,7 @@ #pragma once #include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn/filter_context.h" +#include "extensions/authn/filter_context.h" #include "src/istio/authn/context.pb.h" // WASM_PROLOG diff --git a/extensions/authn/authn_utils.h b/extensions/authn/authn_utils.h index 8e4b6547e7d..a446934fd55 100644 --- a/extensions/authn/authn_utils.h +++ b/extensions/authn/authn_utils.h @@ -18,7 +18,7 @@ #include "authentication/v1alpha1/policy.pb.h" #include "common/common/utility.h" #include "envoy/http/header_map.h" -#include "envoy/json/json_object.h" +#include "extensions/common/json_util.h" #include "src/istio/authn/context.pb.h" namespace iaapi = istio::authentication::v1alpha1; diff --git a/extensions/authn/filter_context.cc b/extensions/authn/filter_context.cc index 17a908baa93..0d1b16df753 100644 --- a/extensions/authn/filter_context.cc +++ b/extensions/authn/filter_context.cc @@ -14,7 +14,7 @@ */ #include "absl/strings/str_cat.h" -#include "src/envoy/http/authn/filter_context.h" +#include "extensions/authn/filter_context.h" #include "src/envoy/utils/filter_names.h" #include "src/envoy/utils/utils.h" diff --git a/extensions/authn/origin_authenticator.cc b/extensions/authn/origin_authenticator.cc index 8621d1369a6..fa2c503c866 100644 --- a/extensions/authn/origin_authenticator.cc +++ b/extensions/authn/origin_authenticator.cc @@ -13,14 +13,14 @@ * limitations under the License. */ -#include "src/envoy/http/authn/origin_authenticator.h" +#include "extensions/authn/origin_authenticator.h" #include "absl/strings/str_cat.h" #include "absl/strings/match.h" #include "authentication/v1alpha1/policy.pb.h" #include "common/http/headers.h" #include "common/http/utility.h" -#include "src/envoy/http/authn/authn_utils.h" +#include "extensions/authn/authn_utils.h" using istio::authn::Payload; diff --git a/extensions/authn/origin_authenticator.h b/extensions/authn/origin_authenticator.h index 5c7fb194f3d..f43e41526e3 100644 --- a/extensions/authn/origin_authenticator.h +++ b/extensions/authn/origin_authenticator.h @@ -16,7 +16,7 @@ #pragma once #include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn/authenticator_base.h" +#include "extensions/authn/authenticator_base.h" // WASM_PROLOG #ifndef NULL_PLUGIN diff --git a/extensions/authn/peer_authenticator.cc b/extensions/authn/peer_authenticator.cc index 20a95ee3d09..0c5debec494 100644 --- a/extensions/authn/peer_authenticator.cc +++ b/extensions/authn/peer_authenticator.cc @@ -14,7 +14,7 @@ */ #include "absl/strings/str_cat.h" -#include "src/envoy/http/authn/peer_authenticator.h" +#include "extensions/authn/peer_authenticator.h" #include "common/http/utility.h" #include "src/envoy/utils/utils.h" diff --git a/extensions/authn/peer_authenticator.h b/extensions/authn/peer_authenticator.h index 317adcce51d..4d0dff523c9 100644 --- a/extensions/authn/peer_authenticator.h +++ b/extensions/authn/peer_authenticator.h @@ -16,7 +16,7 @@ #pragma once #include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn/authenticator_base.h" +#include "extensions/authn/authenticator_base.h" // WASM_PROLOG #ifndef NULL_PLUGIN diff --git a/extensions/authn/plugin.cc b/extensions/authn/plugin.cc index c19fb5376bc..7328b8b6193 100644 --- a/extensions/authn/plugin.cc +++ b/extensions/authn/plugin.cc @@ -13,14 +13,10 @@ * limitations under the License. */ -#include "src/envoy/http/authn_wasm/filter.h" +#include "extensions/authn/plugin.h" #include "absl/strings/str_cat.h" #include "authentication/v1alpha1/policy.pb.h" -#include "google/protobuf/text_format.h" -#include "google/protobuf/util/json_util.h" -#include "src/envoy/http/authn_wasm/connection_context.h" -#include "src/envoy/http/authn_wasm/peer.h" #ifdef NULL_PLUGIN diff --git a/extensions/authn/plugin.h b/extensions/authn/plugin.h index b431e39d181..c0b99abba82 100644 --- a/extensions/authn/plugin.h +++ b/extensions/authn/plugin.h @@ -17,8 +17,7 @@ #include "absl/strings/string_view.h" #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "proxy_wasm_intrinsics.h" -#include "src/envoy/http/authn_wasm/base.h" +#include "extensions/authn/authenticator_base.h" #ifndef NULL_PLUGIN #include "proxy_wasm_intrinsics.h" diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index 8452a3f9658..5c0ad8e1cad 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -20,62 +20,87 @@ namespace Wasm { namespace Common { -::nlohmann::json JsonParse(absl::string_view str) { - return ::nlohmann::json::parse(str, nullptr, false); +void JsonParser::parse(absl::string_view str) { + reset(); + const auto result = JsonObject::parse(str, nullptr, false); + if (result.empty() || result.is_discarded()) { + detail_ = JsonParserResultDetail::PARSE_ERROR; + return; + } + detail_ = JsonParserResultDetail::OK; + object_ = result; + return; +} + +void JsonParser::reset() { + object_ = JsonObject{}; + detail_ = JsonParserResultDetail::EMPTY; } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j) { +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j) { if (j.is_number()) { - return j.get(); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } else if (j.is_string()) { int64_t result = 0; if (absl::SimpleAtoi(j.get_ref(), &result)) { - return result; + return std::make_pair(result, JsonParserResultDetail::OK); + } else { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::INVALID_VALUE); } } - return absl::nullopt; + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -absl::optional JsonValueAs( - const ::nlohmann::json& j) { +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j) { + if (j.is_number()) { + return std::make_pair(j.get(), JsonParserResultDetail::OK); + } else if (j.is_string()) { + uint64_t result = 0; + if (absl::SimpleAtoi(j.get_ref(), &result)) { + return std::make_pair(result, JsonParserResultDetail::OK); + } else { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::INVALID_VALUE); + } + } + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); +} + +template <> +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j) { if (j.is_string()) { - return absl::string_view(j.get_ref()); + return std::make_pair(absl::string_view(j.get_ref()), + JsonParserResultDetail::OK); } - return absl::nullopt; + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -absl::optional JsonValueAs( - const ::nlohmann::json& j) { +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j) { if (j.is_string()) { - return j.get(); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } - return absl::nullopt; + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } template <> -absl::optional JsonValueAs(const ::nlohmann::json& j) { +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j) { if (j.is_boolean()) { - return j.get(); + return std::make_pair(j.get(), JsonParserResultDetail::OK); } if (j.is_string()) { const std::string& v = j.get_ref(); if (v == "true") { - return true; + return std::make_pair(true, JsonParserResultDetail::OK); } else if (v == "false") { -<<<<<<< HEAD - return false; - } - } - return absl::nullopt; -} - -bool JsonArrayIterate( - const ::nlohmann::json& j, absl::string_view field, - const std::function& visitor) { -======= return std::make_pair(false, JsonParserResultDetail::OK); } else { return std::make_pair(absl::nullopt, @@ -88,7 +113,6 @@ bool JsonArrayIterate( bool JsonArrayIterate( const JsonObject& j, absl::string_view field, const std::function& visitor) { ->>>>>>> e598b67b... fix auto it = j.find(field); if (it == j.end()) { return true; @@ -104,7 +128,7 @@ bool JsonArrayIterate( return true; } -bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, +bool JsonObjectIterate(const JsonObject& j, absl::string_view field, const std::function& visitor) { auto it = j.find(field); if (it == j.end()) { @@ -114,11 +138,11 @@ bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, return false; } for (const auto& elt : it.value().items()) { - auto key = JsonValueAs(elt.key()); - if (!key.has_value()) { + auto json_value = JsonValueAs(elt.key()); + if (json_value.second != JsonParserResultDetail::OK) { return false; } - if (!visitor(key.value())) { + if (!visitor(json_value.first.value())) { return false; } } @@ -126,4 +150,4 @@ bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, } } // namespace Common -} // namespace Wasm +} // namespace Wasm \ No newline at end of file diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 0b11657dbb3..02dcb7e0d94 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -15,6 +15,9 @@ #pragma once +#include +#include + #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "extensions/common/nlohmann_json.hpp" @@ -25,54 +28,103 @@ namespace Wasm { namespace Common { -// Parse JSON. Returns the discarded value if fails. -::nlohmann::json JsonParse(absl::string_view str); +using JsonObject = ::nlohmann::json; +using JsonObjectValueType = ::nlohmann::detail::value_t; +using JsonParserException = ::nlohmann::detail::exception; +using JsonParserOutOfRangeException = ::nlohmann::detail::out_of_range; +using JsonParserTypeErrorException = ::nlohmann::detail::type_error; + +enum JsonParserResultDetail { + EMPTY, + OK, + OUT_OF_RANGE, + TYPE_ERROR, + PARSE_ERROR, + INVALID_VALUE, +}; + +class JsonParser { + public: + void parse(absl::string_view str); + JsonObject object() { return object_; }; + const JsonParserResultDetail& detail() { return detail_; } + + private: + void reset(); + + JsonParserResultDetail detail_{JsonParserResultDetail::EMPTY}; + JsonObject object_{}; +}; template -absl::optional JsonValueAs(const ::nlohmann::json& j); +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject&) { + static_assert(true, "Unsupported Type"); +} template <> -absl::optional JsonValueAs( - const ::nlohmann::json& j); +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j); +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j); +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j); template <> -absl::optional JsonValueAs(const ::nlohmann::json& j); +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j); -template -absl::optional JsonGetField(const ::nlohmann::json& j, - absl::string_view field) { +template <> +std::pair, JsonParserResultDetail> JsonValueAs( + const JsonObject& j); + +template +class JsonGetField { + public: + JsonGetField(const JsonObject& j, absl::string_view field); + const JsonParserResultDetail& detail() { return detail_; } + T fetch() { return object_; } + T fetch_or(T v) { + if (detail_ != JsonParserResultDetail::OK) + return v; + else + return object_; + }; + + private: + JsonParserResultDetail detail_; + T object_; +}; + +template +JsonGetField::JsonGetField(const JsonObject& j, absl::string_view field) { auto it = j.find(field); if (it == j.end()) { - return absl::nullopt; + detail_ = JsonParserResultDetail::OUT_OF_RANGE; + return; + } + auto value = JsonValueAs(it.value()); + detail_ = value.second; + if (value.first.has_value()) { + object_ = value.first.value(); } - return JsonValueAs(it.value()); } // Iterate over an optional array field. // Returns false if set and not an array, or any of the visitor calls returns // false. bool JsonArrayIterate( -<<<<<<< HEAD - const ::nlohmann::json& j, absl::string_view field, - const std::function& visitor); -======= const JsonObject& j, absl::string_view field, const std::function& visitor); -<<<<<<< HEAD ->>>>>>> e598b67b... fix -======= ->>>>>>> e598b67b... fix // Iterate over an optional object field key set. // Returns false if set and not an object, or any of the visitor calls returns // false. -bool JsonObjectIterate(const ::nlohmann::json& j, absl::string_view field, +bool JsonObjectIterate(const JsonObject& j, absl::string_view field, const std::function& visitor); } // namespace Common diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 7c49a85cd7b..824dd89f254 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -126,17 +126,25 @@ bool PluginRootContext::configure(size_t configuration_size) { auto configuration_data = getBufferBytes(WasmBufferType::PluginConfiguration, 0, configuration_size); // Parse configuration JSON string. - auto j = ::Wasm::Common::JsonParse(configuration_data->view()); + auto parser = ::Wasm::Common::JsonParser(); + parser.parse(configuration_data->view()); + if (parser.detail() != ::Wasm::Common::JsonParserResultDetail::OK) { + LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", + configuration_data->view())); + return false; + } + auto j = parser.object(); if (!j.is_object()) { LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", configuration_data->view(), j.dump())); return false; } - auto max_peer_cache_size = + auto max_peer_cache_size_field = ::Wasm::Common::JsonGetField(j, "max_peer_cache_size"); - if (max_peer_cache_size.has_value()) { - max_peer_cache_size_ = max_peer_cache_size.value(); + if (max_peer_cache_size_field.detail() == + Wasm::Common::JsonParserResultDetail::OK) { + max_peer_cache_size_ = max_peer_cache_size_field.fetch(); } return true; } diff --git a/extensions/stats/plugin.cc b/extensions/stats/plugin.cc index 08b4e518958..ca6ad40983a 100644 --- a/extensions/stats/plugin.cc +++ b/extensions/stats/plugin.cc @@ -264,21 +264,9 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Process the metric definitions (overriding existing). if (!JsonArrayIterate(j, "definitions", [&](const json& definition) -> bool { -<<<<<<< HEAD -<<<<<<< HEAD - auto name = JsonGetField(definition, "name").value_or(""); - auto value = - JsonGetField(definition, "value").value_or(""); -======= auto name = JsonGetField(definition, "name").fetch_or(""); auto value = JsonGetField(definition, "value").fetch_or(""); ->>>>>>> e598b67b... fix -======= - auto name = JsonGetField(definition, "name").fetch_or(""); - auto value = - JsonGetField(definition, "value").fetch_or(""); ->>>>>>> e598b67b... fix if (name.empty() || value.empty()) { LOG_WARN("empty name or value in 'definitions'"); return false; @@ -302,15 +290,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { }; factory.type = MetricType::Counter; auto type = -<<<<<<< HEAD -<<<<<<< HEAD - JsonGetField(definition, "type").value_or(""); -======= - JsonGetField(definition, "type").fetch_or(""); ->>>>>>> e598b67b... fix -======= JsonGetField(definition, "type").fetch_or(""); ->>>>>>> e598b67b... fix if (type == "GAUGE") { factory.type = MetricType::Gauge; } else if (type == "HISTOGRAM") { @@ -335,7 +315,7 @@ bool PluginRootContext::initializeDimensions(const json& j) { } std::sort(tags.begin(), tags.end()); - auto name = JsonGetField(metric, "name").value_or(""); + auto name = JsonGetField(metric, "name").fetch_or(""); for (const auto& factory_it : factories) { if (!name.empty() && name != factory_it.first) { continue; @@ -346,12 +326,13 @@ bool PluginRootContext::initializeDimensions(const json& j) { if (!JsonArrayIterate( metric, "tags_to_remove", [&](const json& tag) -> bool { auto tag_string = JsonValueAs(tag); - if (!tag_string.has_value()) { + if (tag_string.second != + Wasm::Common::JsonParserResultDetail::OK) { LOG_WARN( absl::StrCat("unexpected tag to remove", tag.dump())); return false; } - auto it = indexes.find(tag_string.value()); + auto it = indexes.find(tag_string.first.value()); if (it != indexes.end()) { it->second = {}; } @@ -365,11 +346,12 @@ bool PluginRootContext::initializeDimensions(const json& j) { for (const auto& tag : tags) { auto expr_string = JsonValueAs(metric["dimensions"][tag]); - if (!expr_string.has_value()) { + if (expr_string.second != + Wasm::Common::JsonParserResultDetail::OK) { LOG_WARN("failed to parse 'dimensions' value"); return false; } - auto expr_index = addStringExpression(expr_string.value()); + auto expr_index = addStringExpression(expr_string.first.value()); Optional value = {}; if (expr_index.has_value()) { value = count_standard_labels + expr_index.value(); @@ -399,11 +381,11 @@ bool PluginRootContext::initializeDimensions(const json& j) { // Instantiate stat factories using the new dimensions auto field_separator = JsonGetField(j, "field_separator") - .value_or(default_field_separator); + .fetch_or(default_field_separator); auto value_separator = JsonGetField(j, "value_separator") - .value_or(default_value_separator); + .fetch_or(default_value_separator); auto stat_prefix = - JsonGetField(j, "stat_prefix").value_or(default_stat_prefix); + JsonGetField(j, "stat_prefix").fetch_or(default_stat_prefix); // prepend "_" to opt out of automatic namespacing // If "_" is not prepended, envoy_ is automatically added by prometheus @@ -456,7 +438,9 @@ bool PluginRootContext::configure(size_t configuration_size) { outbound_ = ::Wasm::Common::TrafficDirection::Outbound == ::Wasm::Common::getTrafficDirection(); - auto j = ::Wasm::Common::JsonParse(configuration_data->view()); + auto json_parser = ::Wasm::Common::JsonParser(); + json_parser.parse(configuration_data->view()); + auto j = json_parser.object(); if (!j.is_object()) { LOG_WARN(absl::StrCat("cannot parse configuration as JSON: ", configuration_data->view())); @@ -471,24 +455,25 @@ bool PluginRootContext::configure(size_t configuration_size) { peer_metadata_key_ = ::Wasm::Common::kDownstreamMetadataKey; } - debug_ = JsonGetField(j, "debug").value_or(false); + debug_ = JsonGetField(j, "debug").fetch_or(false); use_host_header_fallback_ = - !JsonGetField(j, "disable_host_header_fallback").value_or(false); + !JsonGetField(j, "disable_host_header_fallback").fetch_or(false); if (!initializeDimensions(j)) { return false; } uint32_t tcp_report_duration_milis = kDefaultTCPReportDurationMilliseconds; - auto tcp_reporting_duration = + auto tcp_reporting_duration_field = JsonGetField(j, "tcp_reporting_duration"); absl::Duration duration; - if (tcp_reporting_duration.has_value()) { - if (absl::ParseDuration(tcp_reporting_duration.value(), &duration)) { + if (tcp_reporting_duration_field.detail() == + ::Wasm::Common::JsonParserResultDetail::OK) { + if (absl::ParseDuration(tcp_reporting_duration_field.fetch(), &duration)) { tcp_report_duration_milis = uint32_t(duration / absl::Milliseconds(1)); } else { LOG_WARN(absl::StrCat("failed to parse 'tcp_reporting_duration': ", - tcp_reporting_duration.value())); + tcp_reporting_duration_field.fetch())); } } proxy_set_tick_period_milliseconds(tcp_report_duration_milis); diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index 6d7f9a6b7b2..69d10e6fb97 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -33,7 +33,7 @@ envoy_cc_library( ], repository = "@envoy", deps = [ - "@com_google_protobuf//:protobuf", + "//extensions/common:json_util", "@envoy//source/exe:envoy_common_lib", ], ) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 24e59079817..23daec354a0 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -20,14 +20,13 @@ #include #include #include -#include #include #include #include "common/common/assert.h" #include "common/common/base64.h" #include "common/common/utility.h" -#include "google/protobuf/util/json_util.h" +#include "common/json/json_loader.h" #include "openssl/bn.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" @@ -238,75 +237,6 @@ class EvpPkeyGetter : public WithStatus { } }; -template -absl::optional> getProtoListValue(const ProtobufMapType &, - std::string) { - static_assert(true, "Unsupported Type"); -} - -template <> -absl::optional> getProtoListValue( - const ProtobufMapType &struct_value, std::string key) { - const auto field_iter = struct_value.find(key); - if (field_iter == struct_value.end()) { - return absl::nullopt; - } - std::vector list_values; - for (const auto &value : field_iter->second.list_value().values()) { - if (value.kind_case() != google::protobuf::Value::KindCase::kStringValue) { - return absl::nullopt; - } - list_values.emplace_back(value.string_value()); - } - return list_values; -} - -template <> -absl::optional> getProtoListValue( - const ProtobufMapType &struct_value, std::string key) { - const auto field_iter = struct_value.find(key); - if (field_iter == struct_value.end()) { - return absl::nullopt; - } - std::vector list_values; - for (const auto &value : field_iter->second.list_value().values()) { - if (value.kind_case() != google::protobuf::Value::KindCase::kStructValue) { - return absl::nullopt; - } - list_values.emplace_back(value.struct_value()); - } - return list_values; -} - -template -absl::optional getProtoMapValue(const ProtobufMapType &, std::string) { - static_assert(true, "Unsupported Type"); -} - -template <> -absl::optional getProtoMapValue( - const ProtobufMapType &struct_value, std::string key) { - const auto field_iter = struct_value.find(key); - if (field_iter == struct_value.end() || - field_iter->second.kind_case() != - google::protobuf::Value::KindCase::kStringValue) { - return absl::nullopt; - } - return field_iter->second.string_value(); -} - -template <> -absl::optional getProtoMapValue(const ProtobufMapType &struct_value, - std::string key) { - const auto field_iter = struct_value.find(key); - if (field_iter == struct_value.end() || - field_iter->second.kind_case() != - google::protobuf::Value::KindCase::kNumberValue) { - return absl::nullopt; - } - return field_iter->second.number_value(); -} - } // namespace Jwt::Jwt(const std::string &jwt) { @@ -322,32 +252,31 @@ Jwt::Jwt(const std::string &jwt) { } // Parse header json + auto parser = Wasm::Common::JsonParser(); header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); header_str_ = Base64UrlDecode(header_str_base64url_); - auto status = - google::protobuf::util::JsonStringToMessage(header_str_, &header_); - if (!status.ok()) { + parser.parse(header_str_); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); return; + } else { + header_ = parser.object(); } // Header should contain "alg". - const auto header_fields = header_.fields(); - const auto alg_field_iter = header_fields.find("alg"); - - if (alg_field_iter == header_fields.end()) { + if (header_.find("alg") == header_.end()) { UpdateStatus(Status::JWT_HEADER_NO_ALG); return; } - if (alg_field_iter->second.kind_case() != - google::protobuf::Value::kStringValue) { + auto alg_field = Wasm::Common::JsonGetField(header_, "alg"); + if (alg_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_HEADER_BAD_ALG); return; } + alg_ = alg_field.fetch(); - alg_ = alg_field_iter->second.string_value(); if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && alg_ != "RS512") { UpdateStatus(Status::ALG_NOT_IMPLEMENTED); @@ -355,46 +284,40 @@ Jwt::Jwt(const std::string &jwt) { } // Header may contain "kid", which should be a string if exists. - const auto kid_iter = header_fields.find("kid"); - if (kid_iter != header_fields.end() && - kid_iter->second.kind_case() != google::protobuf::Value::kStringValue) { - UpdateStatus(Status::JWT_HEADER_BAD_KID); - return; + auto kid_field = Wasm::Common::JsonGetField(header_, "kid"); + if (kid_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { + if (kid_field.detail() == + Wasm::Common::JsonParserResultDetail::TYPE_ERROR) { + UpdateStatus(Status::JWT_HEADER_BAD_KID); + return; + } else if (kid_field.detail() == + Wasm::Common::JsonParserResultDetail::OUT_OF_RANGE) { + kid_ = ""; + } else { + return; + } + } else { + kid_ = kid_field.fetch(); } - kid_ = kid_iter != header_fields.end() ? kid_iter->second.string_value() : ""; - // Parse payload json payload_str_base64url_ = std::string(jwt_split[1].begin(), jwt_split[1].end()); payload_str_ = Base64UrlDecode(payload_str_base64url_); - - status = google::protobuf::util::JsonStringToMessage(payload_str_, &payload_); - - if (!status.ok()) { + parser.parse(payload_str_); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); return; + } else { + payload_ = parser.object(); } - const auto payload_fields = payload_.fields(); - - iss_ = getProtoMapValue(payload_fields, "iss").value_or(""); - sub_ = getProtoMapValue(payload_fields, "sub").value_or(""); - exp_ = getProtoMapValue(payload_fields, "exp").value_or(0); + iss_ = Wasm::Common::JsonGetField(payload_, "iss").fetch_or(""); + sub_ = Wasm::Common::JsonGetField(payload_, "sub").fetch_or(""); + exp_ = Wasm::Common::JsonGetField(payload_, "exp").fetch_or(0); // "aud" can be either string array or string. // Try as string array, read it as empty array if doesn't exist. -<<<<<<< HEAD -<<<<<<< HEAD - auto actual_list_aud = getProtoListValue(payload_fields, "aud"); - if (actual_list_aud.has_value()) { - aud_ = actual_list_aud.value(); - } else { - auto actual_str_aud = getProtoMapValue(payload_fields, "aud"); - if (actual_str_aud.has_value()) aud_.emplace_back(actual_str_aud.value()); -======= -======= ->>>>>>> e598b67b... fix if (!Wasm::Common::JsonArrayIterate( payload_, "aud", [&](const Wasm::Common::JsonObject &obj) -> bool { auto str_obj_result = Wasm::Common::JsonValueAs(obj); @@ -411,7 +334,6 @@ Jwt::Jwt(const std::string &jwt) { return; } aud_.emplace_back(aud_field.fetch()); ->>>>>>> e598b67b... fix } // Set up signature @@ -538,7 +460,7 @@ bool Verifier::Verify(const Jwt &jwt, const Pubkeys &pubkeys) { } // Returns the parsed header. -google::protobuf::Struct &Jwt::Header() { return header_; } +Wasm::Common::JsonObject &Jwt::Header() { return header_; } const std::string &Jwt::HeaderStr() { return header_str_; } const std::string &Jwt::HeaderStrBase64Url() { return header_str_base64url_; } @@ -546,7 +468,7 @@ const std::string &Jwt::Alg() { return alg_; } const std::string &Jwt::Kid() { return kid_; } // Returns payload JSON. -google::protobuf::Struct &Jwt::Payload() { return payload_; } +Wasm::Common::JsonObject &Jwt::Payload() { return payload_; } const std::string &Jwt::PayloadStr() { return payload_str_; } const std::string &Jwt::PayloadStrBase64Url() { return payload_str_base64url_; } @@ -563,50 +485,42 @@ void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { key_ptr->pem_format_ = true; UpdateStatus(e.GetStatus()); if (e.GetStatus() == Status::OK) { - keys_.emplace_back(std::move(key_ptr)); + keys_.push_back(std::move(key_ptr)); } } void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { keys_.clear(); - google::protobuf::Struct jwks_object; - auto status = - google::protobuf::util::JsonStringToMessage(pkey_jwks, &jwks_object); - if (!status.ok()) { + auto parser = Wasm::Common::JsonParser(); + Wasm::Common::JsonObject jwks_json; + parser.parse(pkey_jwks); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWK_PARSE_ERROR); return; + } else { + jwks_json = parser.object(); } - const auto jwks_field = jwks_object.fields(); - const auto keys_iter = jwks_field.find("keys"); + std::vector> key_refs; - if (keys_iter == jwks_field.end()) { + if (jwks_json.find("keys") == jwks_json.end()) { UpdateStatus(Status::JWK_NO_KEYS); return; } -<<<<<<< HEAD -<<<<<<< HEAD - auto actual_keys = - getProtoListValue(jwks_field, "keys"); - if (!actual_keys.has_value()) { -======= -======= ->>>>>>> e598b67b... fix if (!Wasm::Common::JsonArrayIterate( jwks_json, "keys", [&](const Wasm::Common::JsonObject &obj) -> bool { key_refs.emplace_back( std::reference_wrapper(obj)); return true; })) { ->>>>>>> e598b67b... fix UpdateStatus(Status::JWK_BAD_KEYS); return; } - for (const auto &jwk_field : actual_keys.value()) { - if (!ExtractPubkeyFromJwk(jwk_field.fields())) { + for (auto &key_ref : key_refs) { + if (!ExtractPubkeyFromJwk(key_ref.get())) { continue; } } @@ -616,69 +530,68 @@ void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { } } -bool Pubkeys::ExtractPubkeyFromJwk(const ProtobufMapType &jwk_field) { +bool Pubkeys::ExtractPubkeyFromJwk(const Wasm::Common::JsonObject &jwk_json) { // Check "kty" parameter, it should exist. // https://tools.ietf.org/html/rfc7517#section-4.1 // If "kty" is missing, getString throws an exception. - auto kty = getProtoMapValue(jwk_field, "kty"); - if (!kty.has_value()) { + auto kty_field = Wasm::Common::JsonGetField(jwk_json, "kty"); + if (kty_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } // Extract public key according to "kty" value. // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty.value() == "EC") { - return ExtractPubkeyFromJwkEC(jwk_field); - } else if (kty.value() == "RSA") { - return ExtractPubkeyFromJwkRSA(jwk_field); + if (kty_field.fetch() == "EC") { + return ExtractPubkeyFromJwkEC(jwk_json); + } else if (kty_field.fetch() == "RSA") { + return ExtractPubkeyFromJwkRSA(jwk_json); } return false; } -bool Pubkeys::ExtractPubkeyFromJwkRSA(const ProtobufMapType &jwk_field) { +bool Pubkeys::ExtractPubkeyFromJwkRSA( + const Wasm::Common::JsonObject &jwk_json) { std::unique_ptr pubkey(new Pubkey()); + std::string n_str, e_str; // "kid" and "alg" are optional, if they do not exist, set them to "". // https://tools.ietf.org/html/rfc7517#page-8 - if (jwk_field.find("kid") != jwk_field.end()) { - auto actual_kid = getProtoMapValue(jwk_field, "kid"); - if (!actual_kid.has_value()) { - return false; - } - pubkey->kid_ = actual_kid.value(); + auto kid_field = Wasm::Common::JsonGetField(jwk_json, "kid"); + if (kid_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { + pubkey->kid_ = kid_field.fetch(); pubkey->kid_specified_ = true; } - if (jwk_field.find("alg") != jwk_field.end()) { - auto actual_alg = getProtoMapValue(jwk_field, "alg"); + auto alg_field = Wasm::Common::JsonGetField(jwk_json, "alg"); + if (alg_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { // Allow only "RS" prefixed algorithms. // https://tools.ietf.org/html/rfc7518#section-3.1 - if (!actual_alg.has_value() || - !(actual_alg.value() == "RS256" || actual_alg.value() == "RS384" || - actual_alg.value() == "RS512")) { + if (!(alg_field.fetch() == "RS256" || alg_field.fetch() == "RS384" || + alg_field.fetch() == "RS512")) { return false; } - pubkey->alg_ = actual_alg.value(); + pubkey->alg_ = alg_field.fetch(); pubkey->alg_specified_ = true; } - auto actual_kty = getProtoMapValue(jwk_field, "kty"); - assert(actual_kty.has_value()); - - pubkey->kty_ = actual_kty.value(); - - auto n_str = getProtoMapValue(jwk_field, "n"); - auto e_str = getProtoMapValue(jwk_field, "e"); - - if (!n_str.has_value() || !e_str.has_value()) { + auto pubkey_kty_field = + Wasm::Common::JsonGetField(jwk_json, "kty"); + assert(pubkey_kty_field.detail() == Wasm::Common::JsonParserResultDetail::OK); + pubkey->kty_ = pubkey_kty_field.fetch(); + auto n_str_field = Wasm::Common::JsonGetField(jwk_json, "n"); + auto e_str_field = Wasm::Common::JsonGetField(jwk_json, "e"); + if (n_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK || + e_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } + n_str = n_str_field.fetch(); + e_str = e_str_field.fetch(); EvpPkeyGetter e; - pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str.value(), e_str.value()); + pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); if (e.GetStatus() == Status::OK) { - keys_.emplace_back(std::move(pubkey)); + keys_.push_back(std::move(pubkey)); } else { UpdateStatus(e.GetStatus()); } @@ -686,44 +599,46 @@ bool Pubkeys::ExtractPubkeyFromJwkRSA(const ProtobufMapType &jwk_field) { return true; } -bool Pubkeys::ExtractPubkeyFromJwkEC(const ProtobufMapType &jwk_field) { +bool Pubkeys::ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject &jwk_json) { std::unique_ptr pubkey(new Pubkey()); + std::string x_str, y_str; - if (jwk_field.find("kid") != jwk_field.end()) { - auto actual_kid = getProtoMapValue(jwk_field, "kid"); - if (!actual_kid.has_value()) { - return false; - } - pubkey->kid_ = actual_kid.value(); + // "kid" and "alg" are optional, if they do not exist, set them to "". + // https://tools.ietf.org/html/rfc7517#page-8 + auto kid_field = Wasm::Common::JsonGetField(jwk_json, "kid"); + if (kid_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { + pubkey->kid_ = kid_field.fetch(); pubkey->kid_specified_ = true; } - if (jwk_field.find("alg") != jwk_field.end()) { - auto actual_alg = getProtoMapValue(jwk_field, "alg"); - if (!actual_alg.has_value() || actual_alg.value() != "ES256") { + auto alg_field = Wasm::Common::JsonGetField(jwk_json, "alg"); + if (alg_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { + // Allow only "RS" prefixed algorithms. + // https://tools.ietf.org/html/rfc7518#section-3.1 + if (alg_field.fetch() != "ES256") { return false; } - pubkey->alg_ = actual_alg.value(); + pubkey->alg_ = alg_field.fetch(); pubkey->alg_specified_ = true; } - auto actual_kty = getProtoMapValue(jwk_field, "kty"); - if (!actual_kty.has_value()) { - return false; - } - pubkey->kty_ = actual_kty.value(); - - auto x_str = getProtoMapValue(jwk_field, "x"); - auto y_str = getProtoMapValue(jwk_field, "y"); - - if (!x_str.has_value() || !y_str.has_value()) { + auto pubkey_kty_field = + Wasm::Common::JsonGetField(jwk_json, "kty"); + assert(pubkey_kty_field.detail() == Wasm::Common::JsonParserResultDetail::OK); + pubkey->kty_ = pubkey_kty_field.fetch(); + auto x_str_field = Wasm::Common::JsonGetField(jwk_json, "x"); + auto y_str_field = Wasm::Common::JsonGetField(jwk_json, "y"); + if (x_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK || + y_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } + x_str = x_str_field.fetch(); + y_str = y_str_field.fetch(); EvpPkeyGetter e; - pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str.value(), y_str.value()); + pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); if (e.GetStatus() == Status::OK) { - keys_.emplace_back(std::move(pubkey)); + keys_.push_back(std::move(pubkey)); } else { UpdateStatus(e.GetStatus()); } @@ -749,4 +664,4 @@ std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, } // namespace JwtAuth } // namespace Http -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt.h b/src/envoy/http/jwt_auth/jwt.h index 877ce8ae608..48bc23f10d8 100644 --- a/src/envoy/http/jwt_auth/jwt.h +++ b/src/envoy/http/jwt_auth/jwt.h @@ -15,12 +15,12 @@ #pragma once +#include #include #include #include -#include "google/protobuf/struct.pb.h" - +#include "extensions/common/json_util.h" #include "openssl/ec.h" #include "openssl/evp.h" @@ -134,10 +134,6 @@ class WithStatus { class Pubkeys; class Jwt; -using ProtobufMapType = - google::protobuf::Map; -using ProtobufListValueType = google::protobuf::ListValue; - // JWT Verifier class. // // Usage example: @@ -195,7 +191,7 @@ class Jwt : public WithStatus { // It returns a pointer to a JSON object of the header of the given JWT. // When the given JWT has a format error, it returns nullptr. // It returns the header JSON even if the signature is invalid. - Json::ObjectSharedPtr Header(); + Wasm::Common::JsonObject& Header(); // They return a string (or base64url-encoded string) of the header JSON of // the given JWT. @@ -212,7 +208,7 @@ class Jwt : public WithStatus { // It returns a pointer to a JSON object of the payload of the given JWT. // When the given jWT has a format error, it returns nullptr. // It returns the payload JSON even if the signature is invalid. - Json::ObjectSharedPtr Payload(); + Wasm::Common::JsonObject& Payload(); // They return a string (or base64url-encoded string) of the payload JSON of // the given JWT. @@ -236,10 +232,10 @@ class Jwt : public WithStatus { int64_t Exp(); private: - Json::ObjectSharedPtr header_; + Wasm::Common::JsonObject header_; std::string header_str_; std::string header_str_base64url_; - Json::ObjectSharedPtr payload_; + Wasm::Common::JsonObject payload_; std::string payload_str_; std::string payload_str_base64url_; std::string signature_; @@ -275,9 +271,9 @@ class Pubkeys : public WithStatus { void CreateFromPemCore(const std::string& pkey_pem); void CreateFromJwksCore(const std::string& pkey_jwks); // Extracts the public key from a jwk key (jkey) and sets it to keys_; - void ExtractPubkeyFromJwk(Json::ObjectSharedPtr jwk_json); - void ExtractPubkeyFromJwkRSA(Json::ObjectSharedPtr jwk_json); - void ExtractPubkeyFromJwkEC(Json::ObjectSharedPtr jwk_json); + bool ExtractPubkeyFromJwk(const Wasm::Common::JsonObject& jwk_json); + bool ExtractPubkeyFromJwkRSA(const Wasm::Common::JsonObject& jwk_json); + bool ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject& jwk_json); class Pubkey { public: @@ -291,7 +287,7 @@ class Pubkeys : public WithStatus { bool pem_format_ = false; std::string alg_; }; - std::vector> keys_; + std::vector > keys_; /* * TODO: try not to use friend function diff --git a/src/envoy/http/jwt_auth/jwt_test.cc b/src/envoy/http/jwt_auth/jwt_test.cc index 19e37b3ba87..13e6ad91d14 100644 --- a/src/envoy/http/jwt_auth/jwt_test.cc +++ b/src/envoy/http/jwt_auth/jwt_test.cc @@ -18,10 +18,6 @@ #include #include "common/common/utility.h" - -#include "google/protobuf/struct.pb.h" -#include "google/protobuf/util/json_util.h" - #include "test/test_common/utility.h" namespace Envoy { @@ -515,16 +511,15 @@ class DatasetJwk { namespace { -bool EqJson(const google::protobuf::Struct& p1, - const google::protobuf::Struct& p2) { - return p1.DebugString() == p2.DebugString(); +bool EqJson(Wasm::Common::JsonObject& p1, Wasm::Common::JsonObject& p2) { + return p1.dump() == p2.dump(); } } // namespace class JwtTest : public testing::Test { protected: void DoTest(std::string jwt_str, std::string pkey, std::string pkey_type, - bool verified, Status status, Json::ObjectSharedPtr payload) { + bool verified, Status status, Wasm::Common::JsonObject* payload) { Jwt jwt(jwt_str); Verifier v; std::unique_ptr key; @@ -538,10 +533,12 @@ class JwtTest : public testing::Test { EXPECT_EQ(verified, v.Verify(jwt, *key)); EXPECT_EQ(status, v.GetStatus()); if (verified) { - ASSERT_TRUE(jwt.Payload()); - EXPECT_TRUE(EqJson(payload, jwt.Payload())); + ASSERT_NE(0, jwt.Payload().size()); + EXPECT_TRUE(EqJson(*payload, jwt.Payload())); } } + + Wasm::Common::JsonParser parser_; }; // Test cases w/ PEM-formatted public key @@ -552,18 +549,21 @@ class JwtTestPem : public JwtTest { }; TEST_F(JwtTestPem, OK) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, payload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwt, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, OKWithAlgRs384) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, payload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwtRs384, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, OKWithAlgRs512) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, payload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwtRs512, ds.kPublicKey, "pem", true, Status::OK, &payload); } TEST_F(JwtTestPem, MultiAudiences) { @@ -677,12 +677,14 @@ class JwtTestJwks : public JwtTest { }; TEST_F(JwtTestJwks, OkNoKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); - DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, payload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); + DoTest(ds.kJwtNoKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"RS256\","; std::string pubkey_no_alg = ds.kPublicKeyRSA; @@ -691,7 +693,7 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, payload); + DoTest(ds.kJwtNoKid, pubkey_no_alg, "jwks", true, Status::OK, &payload); // Remove "kid" claim from public key. std::string kid_claim1 = @@ -703,19 +705,21 @@ TEST_F(JwtTestJwks, OkTokenJwkRSAPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, payload); + DoTest(ds.kJwtNoKid, pubkey_no_kid, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, OkNoKidLogExp) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadLongExp); + parser_.parse(ds.kJwtPayloadLongExp); + auto payload = parser_.object(); DoTest(ds.kJwtNoKidLongExp, ds.kPublicKeyRSA, "jwks", true, Status::OK, - payload); + &payload); } TEST_F(JwtTestJwks, OkCorrectKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayload); + parser_.parse(ds.kJwtPayload); + auto payload = parser_.object(); DoTest(ds.kJwtWithCorrectKid, ds.kPublicKeyRSA, "jwks", true, Status::OK, - payload); + &payload); } TEST_F(JwtTestJwks, IncorrectKid) { @@ -758,16 +762,18 @@ TEST_F(JwtTestJwks, JwkBadPublicKey) { } TEST_F(JwtTestJwks, OkTokenJwkEC) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadEC); + parser_.parse(ds.kJwtPayloadEC); + auto payload = parser_.object(); // ES256-signed token with kid specified. - DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, payload); + DoTest(ds.kTokenEC, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, &payload); // ES256-signed token without kid specified. DoTest(ds.kTokenECNoKid, ds.kPublicKeyJwkEC, "jwks", true, Status::OK, - payload); + &payload); } TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { - auto payload = Json::Factory::loadFromString(ds.kJwtPayloadEC); + parser_.parse(ds.kJwtPayloadEC); + auto payload = parser_.object(); // Remove "alg" claim from public key. std::string alg_claim = "\"alg\": \"ES256\","; std::string pubkey_no_alg = ds.kPublicKeyJwkEC; @@ -776,7 +782,7 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_alg.erase(alg_pos, alg_claim.length()); alg_pos = pubkey_no_alg.find(alg_claim); } - DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, payload); + DoTest(ds.kTokenEC, pubkey_no_alg, "jwks", true, Status::OK, &payload); // Remove "kid" claim from public key. std::string kid_claim1 = ",\"kid\": \"abc\""; @@ -786,7 +792,7 @@ TEST_F(JwtTestJwks, OkTokenJwkECPublicKeyOptionalAlgKid) { pubkey_no_kid.erase(kid_pos, kid_claim1.length()); kid_pos = pubkey_no_kid.find(kid_claim2); pubkey_no_kid.erase(kid_pos, kid_claim2.length()); - DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, payload); + DoTest(ds.kTokenEC, pubkey_no_kid, "jwks", true, Status::OK, &payload); } TEST_F(JwtTestJwks, NonExistKidEC) { From f0ab83c99f4ee6d2cf43609b859d419f0a4098fa Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 11:15:40 +0000 Subject: [PATCH 20/44] change CI config --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 013eb79559c..fc3251f35fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,9 @@ jobs: keys: - macos_fastbuild-bazel-cache-{{ checksum "WORKSPACE" }}-{{ checksum ".bazelrc" }} - run: rm ~/.gitconfig - - run: make build_envoy + - run: + command: make build_envoy + no_output_timeout: "60m" - save_cache: key: macos_fastbuild-bazel-cache-{{ checksum "WORKSPACE" }}-{{ checksum ".bazelrc" }} paths: From 7c38bab33a3fe42ccd181bddb6321aacf19d2394 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Thu, 18 Jun 2020 15:33:13 +0000 Subject: [PATCH 21/44] stash --- WORKSPACE | 12 ++++++++---- extensions/BUILD | 7 +++++-- extensions/authn/BUILD | 2 ++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 18bc63c5484..a413fc29d47 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -38,12 +38,12 @@ bind( # 1. Determine SHA256 `wget https://github.com/envoyproxy/envoy-wasm/archive/$COMMIT.tar.gz && sha256sum $COMMIT.tar.gz` # 2. Update .bazelversion, envoy.bazelrc and .bazelrc if needed. # -# Commit time: 6/12/20 -ENVOY_SHA = "cc7b50f6fe65a42534cf8f0e6e8ff784fbfe104c" +# Commit time: 6/17/20 +ENVOY_SHA = "5f51ecd39b2462ca3ec5947c5dbb0e492f36fa53" -ENVOY_SHA256 = "53c947c8c7242c8f9f335a1765104d33814e0bc8ca830651e2a18dbffdd2c201" +ENVOY_SHA256 = "0219971a5dd9e7f5d54ce9300dd9784f0090671770a7f557bbf68a622ea14e7e" -ENVOY_ORG = "envoyproxy" +ENVOY_ORG = "Shikugawa" ENVOY_REPO = "envoy-wasm" @@ -68,6 +68,10 @@ load("@envoy//bazel:repositories.bzl", "envoy_dependencies") envoy_dependencies() +load("@envoy//bazel:repositories_extra.bzl", "envoy_dependencies_extra") + +envoy_dependencies_extra() + load("@envoy//bazel:dependency_imports.bzl", "envoy_dependency_imports") envoy_dependency_imports() diff --git a/extensions/BUILD b/extensions/BUILD index e4f80188208..d20a2f563b0 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -52,8 +52,8 @@ wasm_cc_binary( srcs = [ "//extensions/authn:authenticator_base.h", "//extensions/authn:authenticator_base.cc", - # "//extensions/authn:authn_utils.h", - # "//extensions/authn:authn_utils.cc", + "//extensions/authn:authn_utils.h", + "//extensions/authn:authn_utils.cc", "//extensions/authn:filter_context.h", "//extensions/authn:filter_context.cc", # "//extensions/authn:origin_authenticator.h", @@ -67,11 +67,14 @@ wasm_cc_binary( deps = [ "//external:authentication_policy_config_cc_proto", "//src/istio/authn:context_proto_cc_wasm", + "//src/envoy/utils:filter_names_lib", "//extensions/common:json_util_wasm", "@com_google_absl_wasm//absl/strings", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", "@proxy_wasm_cpp_sdk//contrib:contrib_lib", + "@envoy//include/envoy/http:filter_interface", "@envoy//source/common/http:headers_lib", + "@envoy//source/extensions/filters/http:well_known_names", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) diff --git a/extensions/authn/BUILD b/extensions/authn/BUILD index 66ca3f471f4..030d59066b5 100644 --- a/extensions/authn/BUILD +++ b/extensions/authn/BUILD @@ -30,11 +30,13 @@ envoy_cc_library( "plugin.cc", "authenticator_base.cc", "filter_context.cc", + "authn_utils.cc", ], hdrs = [ "plugin.h", "authenticator_base.h", "filter_context.h", + "authn_utils.h", ], repository = "@envoy", visibility = ["//visibility:public"], From 93d5b0e4abfbef6bfa0c791a8abc2fc21509ae73 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 17 Jun 2020 11:15:40 +0000 Subject: [PATCH 22/44] change CI config --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 013eb79559c..fc3251f35fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,9 @@ jobs: keys: - macos_fastbuild-bazel-cache-{{ checksum "WORKSPACE" }}-{{ checksum ".bazelrc" }} - run: rm ~/.gitconfig - - run: make build_envoy + - run: + command: make build_envoy + no_output_timeout: "60m" - save_cache: key: macos_fastbuild-bazel-cache-{{ checksum "WORKSPACE" }}-{{ checksum ".bazelrc" }} paths: From 0712dec913996bea12550cd1b6880f3ac50b4126 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Fri, 19 Jun 2020 12:45:10 +0000 Subject: [PATCH 23/44] authn: relpace json processing strategy --- extensions/common/json_util.cc | 51 +++++++++++++- extensions/common/json_util.h | 11 +++ src/envoy/http/authn/authn_utils.cc | 101 ++++++++++++---------------- src/envoy/http/authn/authn_utils.h | 1 - 4 files changed, 102 insertions(+), 62 deletions(-) diff --git a/extensions/common/json_util.cc b/extensions/common/json_util.cc index d5d0193345c..3c600d7ad02 100644 --- a/extensions/common/json_util.cc +++ b/extensions/common/json_util.cc @@ -15,6 +15,8 @@ #include "extensions/common/json_util.h" +#include + #include "absl/strings/numbers.h" namespace Wasm { @@ -90,6 +92,32 @@ JsonValueAs(const JsonObject& j) { return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); } +template <> +std::pair>, JsonParserResultDetail> +JsonValueAs>(const JsonObject& j) { + if (j.is_array()) { + std::vector values; + for (const auto& elt : j) { + if (!elt.is_string()) { + return std::make_pair(absl::nullopt, + JsonParserResultDetail::TYPE_ERROR); + } + values.emplace_back(elt); + } + return std::make_pair(values, JsonParserResultDetail::OK); + } + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); +} + +template <> +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j) { + if (j.is_object()) { + return std::make_pair(j.get(), JsonParserResultDetail::OK); + } + return std::make_pair(absl::nullopt, JsonParserResultDetail::TYPE_ERROR); +} + template <> std::pair, JsonParserResultDetail> JsonValueAs( const JsonObject& j) { @@ -138,11 +166,28 @@ bool JsonObjectIterate(const JsonObject& j, absl::string_view field, return false; } for (const auto& elt : it.value().items()) { - auto json_value = JsonValueAs(elt.key()); - if (json_value.second != JsonParserResultDetail::OK) { + auto json_key = JsonValueAs(elt.key()); + if (json_key.second != JsonParserResultDetail::OK) { + return false; + } + if (!visitor(json_key.first.value())) { + return false; + } + } + return true; +} + +bool JsonObjectIterate(const JsonObject& j, + const std::function& visitor) { + if (!j.is_object()) { + return false; + } + for (const auto& elt : j.items()) { + auto json_key = JsonValueAs(elt.key()); + if (json_key.second != JsonParserResultDetail::OK) { return false; } - if (!visitor(json_value.first.value())) { + if (!visitor(json_key.first.value())) { return false; } } diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 02dcb7e0d94..278315d6909 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -82,6 +82,14 @@ template <> std::pair, JsonParserResultDetail> JsonValueAs( const JsonObject& j); +template <> +std::pair>, JsonParserResultDetail> +JsonValueAs>(const JsonObject& j); + +template <> +std::pair, JsonParserResultDetail> +JsonValueAs(const JsonObject& j); + template class JsonGetField { public: @@ -127,5 +135,8 @@ bool JsonArrayIterate( bool JsonObjectIterate(const JsonObject& j, absl::string_view field, const std::function& visitor); +bool JsonObjectIterate(const JsonObject& j, + const std::function& visitor); + } // namespace Common } // namespace Wasm diff --git a/src/envoy/http/authn/authn_utils.cc b/src/envoy/http/authn/authn_utils.cc index 1a4d4f1bd1f..5735528c5be 100644 --- a/src/envoy/http/authn/authn_utils.cc +++ b/src/envoy/http/authn/authn_utils.cc @@ -19,7 +19,7 @@ #include "absl/strings/match.h" #include "absl/strings/str_split.h" -#include "common/json/json_loader.h" +#include "extensions/common/json_util.h" #include "google/protobuf/struct.pb.h" #include "src/envoy/http/jwt_auth/jwt.h" @@ -35,61 +35,48 @@ static const std::string kJwtIssuerKey = "iss"; // The key name for the original claims in an exchanged token static const std::string kExchangedTokenOriginalPayload = "original_claims"; -// Extract JWT claim as a string list. -// This function only extracts string and string list claims. -// A string claim is extracted as a string list of 1 item. -// A string claim with whitespace is extracted as a string list with each -// sub-string delimited with the whitespace. -void ExtractStringList(const std::string& key, const Envoy::Json::Object& obj, - std::vector* list) { - // First, try as string - try { - // Try as string, will throw execption if object type is not string. - const std::vector keys = - absl::StrSplit(obj.getString(key), ' ', absl::SkipEmpty()); - for (auto key : keys) { - list->push_back(key); - } - } catch (Json::Exception& e) { - // Not convertable to string - } - // Next, try as string array - try { - std::vector vector = obj.getStringArray(key); - for (const std::string v : vector) { - list->push_back(v); - } - } catch (Json::Exception& e) { - // Not convertable to string array - } -} }; // namespace bool AuthnUtils::ProcessJwtPayload(const std::string& payload_str, istio::authn::JwtPayload* payload) { - Envoy::Json::ObjectSharedPtr json_obj; - try { - json_obj = Json::Factory::loadFromString(payload_str); - ENVOY_LOG(debug, "{}: json object is {}", __FUNCTION__, - json_obj->asJsonString()); - } catch (...) { + auto parser = Wasm::Common::JsonParser(); + parser.parse(payload_str); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } + auto json_obj = parser.object(); + ENVOY_LOG(debug, "{}: json object is {}", __FUNCTION__, json_obj.dump()); *payload->mutable_raw_claims() = payload_str; auto claims = payload->mutable_claims()->mutable_fields(); // Extract claims as string lists - json_obj->iterate([json_obj, claims](const std::string& key, - const Json::Object&) -> bool { - // In current implementation, only string/string list objects are extracted - std::vector list; - ExtractStringList(key, *json_obj, &list); - for (auto s : list) { - (*claims)[key].mutable_list_value()->add_values()->set_string_value(s); - } - return true; - }); + Wasm::Common::JsonObjectIterate( + json_obj, [&json_obj, &claims](const std::string& key) -> bool { + // In current implementation, only string/string list objects are + // extracted + std::vector list; + auto field_value = + Wasm::Common::JsonGetField>(json_obj, key); + if (field_value.detail() != Wasm::Common::JsonParserResultDetail::OK) { + auto str_field_value = + Wasm::Common::JsonGetField(json_obj, key); + if (str_field_value.detail() != + Wasm::Common::JsonParserResultDetail::OK) { + return true; + } + list = + absl::StrSplit(str_field_value.fetch(), ' ', absl::SkipEmpty()); + } else { + list = field_value.fetch(); + } + for (auto& s : list) { + (*claims)[key].mutable_list_value()->add_values()->set_string_value( + s); + } + return true; + }); + // Copy audience to the audience in context.proto if (claims->find(kJwtAudienceKey) != claims->end()) { for (const auto& v : (*claims)[kJwtAudienceKey].list_value().values()) { @@ -115,30 +102,28 @@ bool AuthnUtils::ProcessJwtPayload(const std::string& payload_str, bool AuthnUtils::ExtractOriginalPayload(const std::string& token, std::string* original_payload) { - Envoy::Json::ObjectSharedPtr json_obj; - try { - json_obj = Json::Factory::loadFromString(token); - } catch (...) { + auto parser = Wasm::Common::JsonParser(); + parser.parse(token); + if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { return false; } + auto json_obj = parser.object(); - if (json_obj->hasObject(kExchangedTokenOriginalPayload) == false) { + if (!json_obj.contains(kExchangedTokenOriginalPayload)) { return false; } - Envoy::Json::ObjectSharedPtr original_payload_obj; - try { - auto original_payload_obj = - json_obj->getObject(kExchangedTokenOriginalPayload); - *original_payload = original_payload_obj->asJsonString(); - ENVOY_LOG(debug, "{}: the original payload in exchanged token is {}", - __FUNCTION__, *original_payload); - } catch (...) { + auto original_payload_obj = + Wasm::Common::JsonGetField( + json_obj, kExchangedTokenOriginalPayload); + if (original_payload_obj.detail() != + Wasm::Common::JsonParserResultDetail::OK) { ENVOY_LOG(debug, "{}: original_payload in exchanged token is of invalid format.", __FUNCTION__); return false; } + *original_payload = original_payload_obj.fetch().dump(); return true; } diff --git a/src/envoy/http/authn/authn_utils.h b/src/envoy/http/authn/authn_utils.h index 0a93189cec9..b5df2e3a04b 100644 --- a/src/envoy/http/authn/authn_utils.h +++ b/src/envoy/http/authn/authn_utils.h @@ -19,7 +19,6 @@ #include "common/common/logger.h" #include "common/common/utility.h" #include "envoy/http/header_map.h" -#include "envoy/json/json_object.h" #include "src/istio/authn/context.pb.h" namespace iaapi = istio::authentication::v1alpha1; From 6e9ae545103e5f1981bb5fbfec4e2c4df7b9736d Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Fri, 19 Jun 2020 12:51:30 +0000 Subject: [PATCH 24/44] fix --- extensions/common/json_util.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 02dcb7e0d94..d9585321621 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -15,9 +15,6 @@ #pragma once -#include -#include - #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "extensions/common/nlohmann_json.hpp" From 8bc3b49fc6a5e007a2c629502f25379464c67e30 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Fri, 19 Jun 2020 12:51:30 +0000 Subject: [PATCH 25/44] fix --- extensions/common/json_util.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/common/json_util.h b/extensions/common/json_util.h index 278315d6909..0ee1460670e 100644 --- a/extensions/common/json_util.h +++ b/extensions/common/json_util.h @@ -15,9 +15,6 @@ #pragma once -#include -#include - #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "extensions/common/nlohmann_json.hpp" From 56927598168f8e03fea1d63ec27a77c76607695b Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sun, 21 Jun 2020 10:03:33 +0000 Subject: [PATCH 26/44] stash --- extensions/BUILD | 31 +- extensions/authn/authenticator_base.cc | 14 +- extensions/authn/authenticator_base.h | 4 +- extensions/authn/authn_utils.cc | 13 +- extensions/authn/authn_utils.h | 4 +- extensions/authn/filter_context.cc | 18 +- extensions/authn/filter_context.h | 9 +- extensions/authn/plugin.cc | 2 +- extensions/authn/plugin.h | 28 +- src/envoy/http/authn_wasm/BUILD | 54 ---- src/envoy/http/authn_wasm/base.cc | 297 ----------------- src/envoy/http/authn_wasm/base.h | 74 ----- src/envoy/http/authn_wasm/cert.h | 145 --------- .../http/authn_wasm/connection_context.cc | 42 --- .../http/authn_wasm/connection_context.h | 78 ----- src/envoy/http/authn_wasm/filter.cc | 69 ---- src/envoy/http/authn_wasm/filter.h | 145 --------- src/envoy/http/authn_wasm/filter_context.cc | 175 ---------- src/envoy/http/authn_wasm/filter_context.h | 135 -------- src/envoy/http/authn_wasm/json.cc | 0 src/envoy/http/authn_wasm/json.h | 43 --- src/envoy/http/authn_wasm/peer.cc | 83 ----- src/envoy/http/authn_wasm/peer.h | 62 ---- src/envoy/http/authn_wasm/request.cc | 117 ------- src/envoy/http/authn_wasm/request.h | 60 ---- src/envoy/http/jwt_auth/BUILD | 16 +- src/envoy/http/jwt_auth/jwt.cc | 303 +++++++++--------- src/envoy/utils/BUILD | 18 ++ 28 files changed, 253 insertions(+), 1786 deletions(-) delete mode 100644 src/envoy/http/authn_wasm/BUILD delete mode 100644 src/envoy/http/authn_wasm/base.cc delete mode 100644 src/envoy/http/authn_wasm/base.h delete mode 100644 src/envoy/http/authn_wasm/cert.h delete mode 100644 src/envoy/http/authn_wasm/connection_context.cc delete mode 100644 src/envoy/http/authn_wasm/connection_context.h delete mode 100644 src/envoy/http/authn_wasm/filter.cc delete mode 100644 src/envoy/http/authn_wasm/filter.h delete mode 100644 src/envoy/http/authn_wasm/filter_context.cc delete mode 100644 src/envoy/http/authn_wasm/filter_context.h delete mode 100644 src/envoy/http/authn_wasm/json.cc delete mode 100644 src/envoy/http/authn_wasm/json.h delete mode 100644 src/envoy/http/authn_wasm/peer.cc delete mode 100644 src/envoy/http/authn_wasm/peer.h delete mode 100644 src/envoy/http/authn_wasm/request.cc delete mode 100644 src/envoy/http/authn_wasm/request.h diff --git a/extensions/BUILD b/extensions/BUILD index 6e421b602cf..b2d4adcc49d 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -50,33 +50,40 @@ wasm_cc_binary( wasm_cc_binary( name = "authn.wasm", srcs = [ - "//extensions/authn:authenticator_base.h", - "//extensions/authn:authenticator_base.cc", + # "//extensions/authn:authenticator_base.h", + # "//extensions/authn:authenticator_base.cc", "//extensions/authn:authn_utils.h", "//extensions/authn:authn_utils.cc", - "//extensions/authn:filter_context.h", - "//extensions/authn:filter_context.cc", + # "//extensions/authn:filter_context.h", + # "//extensions/authn:filter_context.cc", # "//extensions/authn:origin_authenticator.h", # "//extensions/authn:origin_authenticator.cc", # "//extensions/authn:peer_authenticator.h", # "//extensions/authn:peer_authenticator.cc", - "//extensions/authn:plugin.h", - "//extensions/authn:plugin.cc", + # "//extensions/authn:plugin.h", + # "//extensions/authn:plugin.cc", ], copts = ["-UNULL_PLUGIN"], deps = [ "//external:authentication_policy_config_cc_proto", "//src/istio/authn:context_proto_cc_wasm", - "//src/envoy/utils:filter_names_lib", - "//src/envoy/http/jwt_auth:jwt_lib", + # "//src/envoy/utils:filter_names_lib", + # "//src/envoy/utils:utils_wasm_lib", + "//src/envoy/http/jwt_auth:jwt_lib", # こいつのせい "//extensions/common:json_util_wasm", + # -------- "@com_google_absl_wasm//absl/strings", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", "@proxy_wasm_cpp_sdk//contrib:contrib_lib", - "@envoy//include/envoy/http:filter_interface", - "@envoy//source/common/http:headers_lib", - "@envoy//source/extensions/filters/http:well_known_names", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + # -------- + # "@envoy//include/envoy/http:filter_interface", + # ------- + # "@envoy//source/common/common:utility_lib", + # "@envoy//source/common/http:headers_lib", + # ------------ + # "@envoy//source/extensions/filters/http:well_known_names", + # "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + # "@envoy_api//envoy/api/v2/core:pkg_cc_proto" ], ) diff --git a/extensions/authn/authenticator_base.cc b/extensions/authn/authenticator_base.cc index 073eca0a826..6ee132a458c 100644 --- a/extensions/authn/authenticator_base.cc +++ b/extensions/authn/authenticator_base.cc @@ -46,6 +46,10 @@ using proxy_wasm::null_plugin::logWarn; #endif // NULL_PLUGIN +using Envoy::Http::LowerCaseString; +using Envoy::Utils::GetTrustDomain; +using Envoy::Utils::GetPrincipal; + namespace { // The default header name for an exchanged token static const std::string kExchangedTokenHeaderName = "ingress-authorization"; @@ -65,15 +69,15 @@ AuthenticatorBase::AuthenticatorBase(FilterContext* filter_context) AuthenticatorBase::~AuthenticatorBase() {} bool AuthenticatorBase::validateTrustDomain( - const Network::Connection* connection) const { + const Connection* connection) const { std::string peer_trust_domain; - if (!Utils::GetTrustDomain(connection, true, &peer_trust_domain)) { + if (!GetTrustDomain(connection, true, &peer_trust_domain)) { logError("trust domain validation failed: cannot get peer trust domain"); return false; } std::string local_trust_domain; - if (!Utils::GetTrustDomain(connection, false, &local_trust_domain)) { + if (!GetTrustDomain(connection, false, &local_trust_domain)) { logError("trust domain validation failed: cannot get local trust domain"); return false; } @@ -89,7 +93,7 @@ bool AuthenticatorBase::validateTrustDomain( bool AuthenticatorBase::validateX509(const iaapi::MutualTls& mtls, Payload* payload) const { - const Network::Connection* connection = filter_context_.connection(); + const Connection* connection = filter_context_.connection(); if (connection == nullptr) { // It's wrong if connection does not exist. logError("validateX509 failed: null connection."); @@ -99,7 +103,7 @@ bool AuthenticatorBase::validateX509(const iaapi::MutualTls& mtls, const bool has_user = connection->ssl() != nullptr && connection->ssl()->peerCertificatePresented() && - Utils::GetPrincipal(connection, true, + GetPrincipal(connection, true, payload->mutable_x509()->mutable_user()); logDebug(absl::StrCat("validateX509 mode {}: ssl={}, has_user={}", iaapi::MutualTls::Mode_Name(mtls.mode()), connection->ssl() != nullptr, has_user)); diff --git a/extensions/authn/authenticator_base.h b/extensions/authn/authenticator_base.h index b84ac21bbf6..d16fc21509a 100644 --- a/extensions/authn/authenticator_base.h +++ b/extensions/authn/authenticator_base.h @@ -34,6 +34,8 @@ namespace AuthN { #endif // NULL_PLUGIN +using Envoy::Network::Connection; + // AuthenticatorBase is the base class for authenticator. It provides functions // to perform individual authentication methods, which can be used to construct // compound authentication flow. @@ -65,7 +67,7 @@ class AuthenticatorBase { // Pointer to filter state. Do not own. FilterContext& filter_context_; - bool validateTrustDomain(const Network::Connection* connection) const; + bool validateTrustDomain(const Connection* connection) const; }; #ifdef NULL_PLUGIN diff --git a/extensions/authn/authn_utils.cc b/extensions/authn/authn_utils.cc index 334b7708198..249b0af34cb 100644 --- a/extensions/authn/authn_utils.cc +++ b/extensions/authn/authn_utils.cc @@ -20,9 +20,10 @@ #include "absl/strings/str_cat.h" #include "absl/strings/match.h" #include "absl/strings/str_split.h" + #include "extensions/common/json_util.h" #include "google/protobuf/struct.pb.h" -#include "src/envoy/http/jwt_auth/jwt.h" +// #include "src/envoy/http/jwt_auth/jwt.h" // WASM_PROLOG #ifndef NULL_PLUGIN @@ -62,7 +63,7 @@ bool AuthnUtils::ProcessJwtPayload(const std::string& payload_str, return false; } auto json_obj = parser.object(); - ENVOY_LOG(debug, "{}: json object is {}", __FUNCTION__, json_obj.dump()); + logDebug(absl::StrCat(__FUNCTION__, ": json object is ", json_obj.dump())); *payload->mutable_raw_claims() = payload_str; @@ -134,10 +135,10 @@ bool AuthnUtils::ExtractOriginalPayload(const std::string& token, Wasm::Common::JsonGetField( json_obj, kExchangedTokenOriginalPayload); if (original_payload_obj.detail() != - Wasm::Common::JsonParserResultDetail::OK) { - ENVOY_LOG(debug, - "{}: original_payload in exchanged token is of invalid format.", - __FUNCTION__); + Wasm::Common::JsonParserResultDetail::OK) { + + logDebug(absl::StrCat(__FUNCTION__, ": original_payload in exchanged token is of invalid format.")); + return false; } *original_payload = original_payload_obj.fetch().dump(); diff --git a/extensions/authn/authn_utils.h b/extensions/authn/authn_utils.h index 4d09bf2a0f9..ab9c23c997f 100644 --- a/extensions/authn/authn_utils.h +++ b/extensions/authn/authn_utils.h @@ -15,9 +15,9 @@ #pragma once +#include "absl/strings/string_view.h" + #include "authentication/v1alpha1/policy.pb.h" -#include "common/common/utility.h" -#include "envoy/http/header_map.h" #include "src/istio/authn/context.pb.h" namespace iaapi = istio::authentication::v1alpha1; diff --git a/extensions/authn/filter_context.cc b/extensions/authn/filter_context.cc index 0d1b16df753..ed5ff2ddd8c 100644 --- a/extensions/authn/filter_context.cc +++ b/extensions/authn/filter_context.cc @@ -17,7 +17,6 @@ #include "extensions/authn/filter_context.h" #include "src/envoy/utils/filter_names.h" -#include "src/envoy/utils/utils.h" using istio::authn::Payload; using istio::authn::Result; @@ -44,6 +43,10 @@ using proxy_wasm::null_plugin::logWarn; #endif // NULL_PLUGIN +using google::protobuf::util::MessageToJsonString; +using Envoy::Utils::IstioFilterName; +using Envoy::Extensions::HttpFilters::HttpFilterNames; + void FilterContext::setPeerResult(const Payload* payload) { if (payload != nullptr) { switch (payload->payload_case()) { @@ -84,7 +87,7 @@ void FilterContext::setPrincipal(const iaapi::PrincipalBinding& binding) { return; default: // Should never come here. - logError("Invalid binding value {}", binding); + logError(absl::StrCat("Invalid binding value ", binding)); return; } } @@ -101,10 +104,10 @@ bool FilterContext::getJwtPayloadFromEnvoyJwtFilter( const std::string& issuer, std::string* payload) const { // Try getting the Jwt payload from Envoy jwt_authn filter. auto filter_it = dynamic_metadata_.filter_metadata().find( - Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn); + HttpFilterNames::get().JwtAuthn); if (filter_it == dynamic_metadata_.filter_metadata().end()) { logDebug(absl::StrCat("No dynamic_metadata found for filter {}", - Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn)); + HttpFilterNames::get().JwtAuthn)); return false; } @@ -124,7 +127,7 @@ bool FilterContext::getJwtPayloadFromEnvoyJwtFilter( // TODO (pitlv2109): Return protobuf Struct instead of string, once Istio jwt // filter is removed. Also need to change how Istio authn filter processes the // jwt payload. - Protobuf::util::MessageToJsonString(entry_it->second.struct_value(), payload); + MessageToJsonString(entry_it->second.struct_value(), payload); return true; } @@ -132,10 +135,9 @@ bool FilterContext::getJwtPayloadFromIstioJwtFilter( const std::string& issuer, std::string* payload) const { // Try getting the Jwt payload from Istio jwt-auth filter. auto filter_it = - dynamic_metadata_.filter_metadata().find(Utils::IstioFilterName::kJwt); + dynamic_metadata_.filter_metadata().find(IstioFilterName::kJwt); if (filter_it == dynamic_metadata_.filter_metadata().end()) { - logDebug("No dynamic_metadata found for filter {}", - Utils::IstioFilterName::kJwt); + logDebug(absl::StrCat("No dynamic_metadata found for filter ", IstioFilterName::kJwt)); return false; } diff --git a/extensions/authn/filter_context.h b/extensions/authn/filter_context.h index 63f3e09a94e..c6c0890fd0f 100644 --- a/extensions/authn/filter_context.h +++ b/extensions/authn/filter_context.h @@ -38,13 +38,16 @@ namespace AuthN { #endif // NULL_PLUGIN +using Envoy::Http::RequestHeaderMap; +using Envoy::Network::Connection; + // FilterContext holds inputs, such as request dynamic metadata and connection // and result data for authentication process. class FilterContext { public: FilterContext( const envoy::config::core::v3::Metadata& dynamic_metadata, - const RequestHeaderMap& header_map, const Network::Connection* connection, + const RequestHeaderMap& header_map, const Connection* connection, const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filter_config) : dynamic_metadata_(dynamic_metadata), @@ -70,7 +73,7 @@ class FilterContext { const istio::authn::Result& authenticationResult() { return result_; } // Accessor to connection - const Network::Connection* connection() { return connection_; } + const Connection* connection() { return connection_; } // Accessor to the filter config const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& filter_config() const { @@ -103,7 +106,7 @@ class FilterContext { const RequestHeaderMap& header_map_; // Pointer to network connection of the request. - const Network::Connection* connection_; + const Connection* connection_; // Holds authentication attribute outputs. istio::authn::Result result_; diff --git a/extensions/authn/plugin.cc b/extensions/authn/plugin.cc index 7328b8b6193..43ab3057fee 100644 --- a/extensions/authn/plugin.cc +++ b/extensions/authn/plugin.cc @@ -26,7 +26,7 @@ namespace AuthN { #endif -FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { +FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t, bool) { return FilterHeadersStatus::Continue; } diff --git a/extensions/authn/plugin.h b/extensions/authn/plugin.h index c0b99abba82..99f1d7a93e6 100644 --- a/extensions/authn/plugin.h +++ b/extensions/authn/plugin.h @@ -17,7 +17,9 @@ #include "absl/strings/string_view.h" #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "extensions/authn/authenticator_base.h" +// #include "extensions/authn/authenticator_base.h" + +#include "envoy/config/core/v3/base.pb.h" #ifndef NULL_PLUGIN #include "proxy_wasm_intrinsics.h" @@ -63,10 +65,10 @@ class AuthnRootContext : public RootContext { void onGrpcReceive(uint32_t token, size_t body_size) override {} void onGrpcClose(uint32_t token, GrpcStatus status) override {} - const FilterConfig& filterConfig() { return filter_config_; }; + // const FilterConfig& filterConfig() { return filter_config_; }; - private: - FilterConfig filter_config_; +// private: + // FilterConfig filter_config_; }; // Per-stream context. @@ -87,7 +89,7 @@ class AuthnContext : public Context { } void onDownstreamConnectionClose(PeerType) override {} void onUpstreamConnectionClose(PeerType) override {} - FilterHeadersStatus onRequestHeaders(uint32_t) override; + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; FilterMetadataStatus onRequestMetadata(uint32_t) override { return FilterMetadataStatus::Continue; } @@ -97,7 +99,7 @@ class AuthnContext : public Context { FilterTrailersStatus onRequestTrailers(uint32_t) override { return FilterTrailersStatus::Continue; } - FilterHeadersStatus onResponseHeaders(uint32_t) override { + FilterHeadersStatus onResponseHeaders(uint32_t, bool) override { return FilterHeadersStatus::Continue; } FilterMetadataStatus onResponseMetadata(uint32_t) override { @@ -112,22 +114,22 @@ class AuthnContext : public Context { void onDone() override {} void onLog() override {} - const FilterConfig& filterConfig() { return rootContext()->filterConfig(); }; + // const FilterConfig& filterConfig() { return rootContext()->filterConfig(); }; private: - std::unique_ptr createPeerAuthenticator( - FilterContextPtr filter_context); + // std::unique_ptr createPeerAuthenticator( + // FilterContext* filter_context); // TODO(shikugawa): origin authenticator implementation. // std::unique_ptr createOriginAuthenticator( // istio::AuthN::FilterContext* filter_context); - inline AuthnRootContext* rootContext() { - return dynamic_cast(this->root()); - }; + // inline AuthnRootContext* rootContext() { + // return dynamic_cast(this->root()); + // }; // Context for authentication process. Created in decodeHeader to start // authentication process. - FilterContextPtr filter_context_; + // FilterContext* filter_context_; }; #ifdef NULL_PLUGIN diff --git a/src/envoy/http/authn_wasm/BUILD b/src/envoy/http/authn_wasm/BUILD deleted file mode 100644 index 49557a4671f..00000000000 --- a/src/envoy/http/authn_wasm/BUILD +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2020 Istio Authors. All Rights Reserved. -# -# Licensed 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. -# -################################################################################ -# - -package(default_visibility = ["//visibility:public"]) - -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_library", -) -load( - "@envoy//bazel/wasm:wasm.bzl", - "wasm_cc_binary", -) - -wasm_cc_binary( - name = "authn_filter.wasm", - srcs = [ - "base.cc", - "base.h", - "cert.h", - "connection_context.cc", - "connection_context.h", - "filter.cc", - "filter.h", - "filter_context.cc", - "filter_context.h", - "peer.cc", - "peer.h", - ], - copts = ["-UNULL_PLUGIN"], - deps = [ - "//external:authentication_policy_config_cc_proto", - "//src/envoy/utils:filter_names_lib", - "//src/istio/authn:context_proto_cc_wasm", - "@com_google_absl_wasm//absl/strings", - "@com_google_absl_wasm//absl/types:optional", - "@com_google_protobuf//:protobuf", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", - ], -) diff --git a/src/envoy/http/authn_wasm/base.cc b/src/envoy/http/authn_wasm/base.cc deleted file mode 100644 index ded1a31c111..00000000000 --- a/src/envoy/http/authn_wasm/base.cc +++ /dev/null @@ -1,297 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "src/envoy/http/authn_wasm/base.h" - -#include - -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" - -#ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics.h" -#else -#include "include/proxy-wasm/null_plugin.h" - -using proxy_wasm::null_plugin::logDebug; -using proxy_wasm::null_plugin::logError; - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -namespace { -// The default header name for an exchanged token -static constexpr absl::string_view kExchangedTokenHeaderName = - "ingress-authorization"; - -// Returns whether the header for an exchanged token is found -bool FindHeaderOfExchangedToken( - const istio::authentication::v1alpha1::Jwt& jwt) { - return (jwt.jwt_headers_size() == 1 && - Envoy::Http::LowerCaseString(kExchangedTokenHeaderName.data()) == - Envoy::Http::LowerCaseString(jwt.jwt_headers(0))); -} - -// The JWT audience key name -static constexpr absl::string_view kJwtAudienceKey = "aud"; -// The JWT issuer key name -static constexpr absl::string_view kJwtIssuerKey = "iss"; -// The key name for the original claims in an exchanged token -static constexpr absl::string_view kExchangedTokenOriginalPayload = - "original_claims"; - -bool ExtractOriginalPayload(const std::string& token, - std::string* original_payload) { - Envoy::Json::ObjectSharedPtr json_obj; - try { - json_obj = Json::Factory::loadFromString(token); - } catch (...) { - return false; - } - - if (json_obj->hasObject(kExchangedTokenOriginalPayload) == false) { - return false; - } - - Envoy::Json::ObjectSharedPtr original_payload_obj; - try { - auto original_payload_obj = - json_obj->getObject(kExchangedTokenOriginalPayload); - *original_payload = original_payload_obj->asJsonString(); - logDebug(absl::StrCat(__FUNCTION__, - ": the original payload in exchanged token is ", - *original_payload)); - } catch (...) { - logDebug(absl::StrCat( - __FUNCTION__, - ": original_payload in exchanged token is of invalid format.")); - return false; - } - - return true; -} - -bool ProcessJwtPayload(const std::string& payload_str, - istio::authn::JwtPayload* payload) { - Envoy::Json::ObjectSharedPtr json_obj; - try { - json_obj = Json::Factory::loadFromString(payload_str); - ENVOY_LOG(debug, "{}: json object is {}", __FUNCTION__, - json_obj->asJsonString()); - } catch (...) { - return false; - } - - *payload->mutable_raw_claims() = payload_str; - - auto claims = payload->mutable_claims()->mutable_fields(); - // Extract claims as string lists - json_obj->iterate([json_obj, claims](const std::string& key, - const Json::Object&) -> bool { - // In current implementation, only string/string list objects are extracted - std::vector list; - ExtractStringList(key, *json_obj, &list); - for (auto s : list) { - (*claims)[key].mutable_list_value()->add_values()->set_string_value(s); - } - return true; - }); - // Copy audience to the audience in context.proto - if (claims->find(kJwtAudienceKey) != claims->end()) { - for (const auto& v : (*claims)[kJwtAudienceKey].list_value().values()) { - payload->add_audiences(v.string_value()); - } - } - - // Build user - if (claims->find(kJwtIssuerKey) != claims->end() && - claims->find("sub") != claims->end()) { - payload->set_user( - (*claims)[kJwtIssuerKey].list_value().values().Get(0).string_value() + - "/" + (*claims)["sub"].list_value().values().Get(0).string_value()); - } - // Build authorized presenter (azp) - if (claims->find("azp") != claims->end()) { - payload->set_presenter( - (*claims)["azp"].list_value().values().Get(0).string_value()); - } - - return true; -} - -} // namespace - -AuthenticatorBase::AuthenticatorBase(FilterContextPtr filter_context) - : filter_context_(filter_context) {} - -AuthenticatorBase::~AuthenticatorBase() {} - -bool AuthenticatorBase::validateTrustDomain() const { - const auto& conn_context = filter_context_->connectionContext(); - if (conn_context.peerCertificateInfo() == nullptr) { - logError( - "trust domain validation failed: failed to extract peer certificate " - "info"); - return false; - } - - const auto& peer_trust_domain = - conn_context.peerCertificateInfo()->getTrustDomain(); - if (!peer_trust_domain.has_value()) { - logError("trust domain validation failed: cannot get peer trust domain"); - return false; - } - - if (conn_context.localCertificateInfo() == nullptr) { - logError( - "trust domain validation failed: failed to extract local certificate " - "info"); - return false; - } - - const auto& local_trust_domain = - conn_context.localCertificateInfo()->getTrustDomain(); - if (!local_trust_domain.has_value()) { - logError("trust domain validation failed: cannot get local trust domain"); - return false; - } - - if (peer_trust_domain.value() != local_trust_domain.value()) { - logError(absl::StrCat("trust domain validation failed: peer trust domain", - peer_trust_domain.value())); - logError(absl::StrCat("different from local trust domain ", - local_trust_domain.value())); - return false; - } - - logDebug("trust domain validation succeeded"); - return true; -} - -bool AuthenticatorBase::validateX509( - const istio::authentication::v1alpha1::MutualTls& mtls, - istio::authn::Payload* payload) const { - bool has_user; - const auto& conn_context = filter_context_->connectionContext(); - const bool presented = conn_context.peerCertificateInfo() != nullptr && - conn_context.peerCertificateInfo()->presented(); - - if (conn_context.peerCertificateInfo() != nullptr) { - const auto principal = conn_context.peerCertificateInfo()->getPrincipal(); - if (principal.has_value()) { - *(payload->mutable_x509()->mutable_user()) = principal.value(); - } - has_user = presented && principal.has_value(); - } - - logDebug(absl::StrCat( - "validateX509 mode: ", - istio::authentication::v1alpha1::MutualTls::Mode_Name(mtls.mode()))); - logDebug(absl::StrCat("validateX509 ssl: ", conn_context.isTls())); - logDebug(absl::StrCat("validateX509 has_user: ", has_user)); - - if (!has_user) { - // For plaintext connection, return value depend on mode: - // - PERMISSIVE: always true. - // - STRICT: always false. - switch (mtls.mode()) { - case istio::authentication::v1alpha1::MutualTls::PERMISSIVE: - return true; - case istio::authentication::v1alpha1::MutualTls::STRICT: - return false; - default: - logError("should not be reached to this section."); - abort(); - } - } - - if (filter_context_->filterConfig().skip_validate_trust_domain()) { - logDebug("trust domain validation skipped"); - return true; - } - - // For TLS connection with valid certificate, validate trust domain for both - // PERMISSIVE and STRICT mode. - return validateTrustDomain(conn_context); -} - -bool AuthenticatorBase::validateJwt( - const istio::authentication::v1alpha1::Jwt& jwt, - istio::authn::Payload* payload) { - auto jwt_payload = filterContext()->getJwtPayload(jwt.issuer()); - - if (jwt_payload.has_value()) { - std::string payload_to_process = jwt_payload; - std::string original_payload; - - if (FindHeaderOfExchangedToken(jwt)) { - if (ExtractOriginalPayload(jwt_payload, &original_payload)) { - // When the header of an exchanged token is found and the token - // contains the claim of the original payload, the original payload - // is extracted and used as the token payload. - payload_to_process = original_payload; - } else { - // When the header of an exchanged token is found but the token - // does not contain the claim of the original payload, it - // is regarded as an invalid exchanged token. - logError(absl::StrCat( - "Expect exchanged-token with original payload claim. Received: ", - jwt_payload)); - return false; - } - } - ProcessJwtPayload(payload_to_process, payload->mutable_jwt()); - } - return false; -} - -bool AuthenticatorBase::validateTrustDomain( - const ConnectionContext& connection) const { - auto peer_trust_domain = connection.peerCertificateInfo()->getTrustDomain(); - if (!peer_trust_domain.has_value()) { - logError("trust domain validation failed: cannot get peer trust domain"); - return false; - } - - auto local_trust_domain = connection.localCertificateInfo()->getTrustDomain(); - if (!local_trust_domain.has_value()) { - logError("trust domain validation failed: cannot get local trust domain"); - return false; - } - - if (peer_trust_domain.value() != local_trust_domain.value()) { - logError(absl::StrCat("trust domain validation failed: peer trust domain ", - peer_trust_domain.value(), - " different from local trust domain ", - local_trust_domain.value())); - return false; - } - - logDebug("trust domain validation succeeded"); - return true; -} - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/base.h b/src/envoy/http/authn_wasm/base.h deleted file mode 100644 index 6d8296e7ab8..00000000000 --- a/src/envoy/http/authn_wasm/base.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include - -#include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn_wasm/filter_context.h" -#include "src/istio/authn/context.pb.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -// AuthenticatorBase is the base class for authenticator. It provides functions -// to perform individual authentication methods, which can be used to construct -// compound authentication flow. -class AuthenticatorBase { - public: - AuthenticatorBase(FilterContextPtr filter_context); - virtual ~AuthenticatorBase(); - - // Perform authentication. - virtual bool run(istio::authn::Payload*) = 0; - - // Validate TLS/MTLS connection and extract authenticated attributes (just - // source user identity for now). Unlike mTLS, TLS connection does not require - // a client certificate. - virtual bool validateX509( - const istio::authentication::v1alpha1::MutualTls& params, - istio::authn::Payload* payload) const; - - // Validates JWT given the jwt params. If JWT is validated, it will extract - // attributes and claims (JwtPayload), returns status SUCCESS. - // Otherwise, returns status FAILED. - virtual bool validateJwt(const istio::authentication::v1alpha1::Jwt& params, - istio::authn::Payload* payload); - - // Mutable accessor to filter context. - FilterContextPtr filterContext() { return filter_context_; } - - private: - bool validateTrustDomain(const ConnectionContext& connection) const; - - // Pointer to filter state. Do not own. - FilterContextPtr filter_context_; -}; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/cert.h b/src/envoy/http/authn_wasm/cert.h deleted file mode 100644 index 8519576018d..00000000000 --- a/src/envoy/http/authn_wasm/cert.h +++ /dev/null @@ -1,145 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include -#include - -#include "absl/strings/match.h" -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -constexpr absl::string_view kSPIFFEPrefix = "spiffe://"; - -template -class TlsCertificateInfo { - public: - absl::optional getCertSans() { - const auto& uri_sans = static_cast(*this).uriSans(); - for (const auto& uri_san : uri_sans) { - if (absl::StartsWith(uri_san, kSPIFFEPrefix)) { - return uri_san; - } - } - if (!uri_sans.empty()) { - return uri_sans[0]; - } - return absl::nullopt; - } - - absl::optional getPrincipal() { - const auto cert_sans = getCertSans(); - if (cert_sans.has_value()) { - if (absl::StartsWith(cert_sans.value(), kSPIFFEPrefix)) { - return cert_sans.value().substr(kSPIFFEPrefix.size()); - } - return cert_sans.value(); - } - return absl::nullopt; - } - - absl::optional getTrustDomain() { - const auto cert_san = getCertSans(); - if (!cert_san.has_value() || - !absl::StartsWith(cert_san.value(), kSPIFFEPrefix)) { - return absl::nullopt; - } - - // Skip the prefix "spiffe://" before getting trust domain. - size_t slash = cert_san.value().find('/', kSPIFFEPrefix.size()); - if (slash == std::string::npos) { - return absl::nullopt; - } - - size_t len = slash - kSPIFFEPrefix.size(); - return cert_san.value().substr(kSPIFFEPrefix.size(), len); - } -}; - -class TlsPeerCertificateInfo - : public TlsCertificateInfo { - public: - explicit TlsPeerCertificateInfo(std::string&& serial_number, - std::string&& issuer, std::string&& subject, - std::string&& sha256_digest, - std::vector&& uri_sans, - std::vector&& dns_sans, - bool validated, bool presented) - : serial_number_(std::move(serial_number)), - issuer_(std::move(issuer)), - subject_(std::move(subject)), - sha256_digest_(std::move(sha256_digest)), - uri_sans_(std::move(uri_sans)), - dns_sans_(std::move(dns_sans)), - validated_(validated), - presented_(presented) {} - const std::string& serialNumber() const { return serial_number_; } - const std::string& issuer() const { return issuer_; } - const std::string& subject() const { return subject_; } - const std::string& sha256Digest() const { return sha256_digest_; } - const std::vector& uriSans() const { return uri_sans_; } - const std::vector& dnsSans() const { return dns_sans_; } - const bool& validated() const { return validated_; } - const bool& presented() const { return presented_; } - - private: - const std::string serial_number_; - const std::string issuer_; - const std::string subject_; - const std::string sha256_digest_; - const std::vector uri_sans_; - const std::vector dns_sans_; - const bool validated_; - const bool presented_; -}; - -class TlsLocalCertificateInfo - : public TlsCertificateInfo { - public: - explicit TlsLocalCertificateInfo(std::string&& subject, - std::vector&& uri_sans, - std::vector&& dns_sans) - : subject_(std::move(subject)), - uri_sans_(std::move(uri_sans)), - dns_sans_(std::move(dns_sans)) {} - const std::string& subject() const { return subject_; } - const std::vector& uriSans() const { return uri_sans_; } - const std::vector& dnsSans() const { return dns_sans_; } - - private: - std::string subject_; - std::vector uri_sans_; - std::vector dns_sans_; -}; - -using TlsPeerCertificateInfoPtr = std::unique_ptr; -using TlsLocalCertificateInfoPtr = std::unique_ptr; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/connection_context.cc b/src/envoy/http/authn_wasm/connection_context.cc deleted file mode 100644 index 04f87fe7540..00000000000 --- a/src/envoy/http/authn_wasm/connection_context.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif -// ConnectionContext::ConnectionContext() { -// if (isTls()) { -// peer_cert_info_ = std::make_unique(); -// peer_cert_info_->uriSans() = getProperty({Connection, -// UriSanPeerCertificate}); local_cert_info_ = -// std::make_unique(); local_cert_info_->uriSans() -// = getProperty({Connection, UriSanLocalCertificate}); mtls_ = -// getProperty({Connection, Mtls}).value_or(false); -// } -// } - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/connection_context.h b/src/envoy/http/authn_wasm/connection_context.h deleted file mode 100644 index c7e13782540..00000000000 --- a/src/envoy/http/authn_wasm/connection_context.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include - -#include "absl/strings/string_view.h" -#include "src/envoy/http/authn_wasm/cert.h" - -#ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics.h" -#else -#include "include/proxy-wasm/null_plugin.h" - -using proxy_wasm::null_plugin::getProperty; -using proxy_wasm::null_plugin::logDebug; - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -constexpr absl::string_view Connection = "connection"; -constexpr absl::string_view TlsVersion = "tls_version"; -constexpr absl::string_view UriSanPeerCertificate = "uri_san_peer_certificate"; -constexpr absl::string_view LocalSanPeerCertificate = - "uri_san_local_certificate"; -constexpr absl::string_view Mtls = "mtls"; - -class ConnectionContext { - public: - ConnectionContext() = default; - - bool isMtls() const { return mtls_; } - - // Regard this connection as TLS when we can extract tls version. - bool isTls() const { - return getProperty({Connection, TlsVersion}).has_value(); - } - - const TlsPeerCertificateInfoPtr& peerCertificateInfo() const { - return peer_cert_info_; - } - - const TlsLocalCertificateInfoPtr& localCertificateInfo() const { - return local_cert_info_; - } - - private: - TlsPeerCertificateInfoPtr peer_cert_info_; - TlsLocalCertificateInfoPtr local_cert_info_; - - bool mtls_; -}; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter.cc b/src/envoy/http/authn_wasm/filter.cc deleted file mode 100644 index 0b72dd87aa4..00000000000 --- a/src/envoy/http/authn_wasm/filter.cc +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "src/envoy/http/authn_wasm/filter.h" - -#include "absl/strings/str_cat.h" -#include "authentication/v1alpha1/policy.pb.h" -#include "google/protobuf/text_format.h" -#include "google/protobuf/util/json_util.h" -#include "src/envoy/http/authn_wasm/connection_context.h" -#include "src/envoy/http/authn_wasm/peer.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -FilterHeadersStatus AuthnContext::onRequestHeaders(uint32_t) { - std::string metadata_bytes; - - if (!getValue({"metadata"}, &metadata_bytes)) { - logError("Failed to read metadata"); - return FilterHeadersStatus::StopIteration; - } - - istio::authn::Metadata metadata; - metadata.ParseFromString(metadata_bytes); - const auto request_headers = getRequestHeaderPairs()->pairs(); - - filter_context_.reset(new FilterContext(ConnectionContext(), request_headers, - metadata, filterConfig())); - - istio::authn::Payload payload; - - if (!PeerAuthenticator::create(filter_context_)->run(&payload) && - !filterConfig().policy().peer_is_optional()) { - logError("Peer authentication failed."); - return FilterHeadersStatus::StopIteration; - } - - // if (!) - - return FilterHeadersStatus::Continue; -} - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter.h b/src/envoy/http/authn_wasm/filter.h deleted file mode 100644 index 98d2c68127c..00000000000 --- a/src/envoy/http/authn_wasm/filter.h +++ /dev/null @@ -1,145 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include "absl/strings/string_view.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "proxy_wasm_intrinsics.h" -#include "src/envoy/http/authn_wasm/base.h" - -#ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics.h" -#else -#include "include/proxy-wasm/null_plugin.h" - -using proxy_wasm::null_plugin::logDebug; -using proxy_wasm::null_plugin::logError; - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -using istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig; -using StringView = absl::string_view; - -// AuthnRootContext is the root context for all streams processed by the -// thread. It has the same lifetime as the worker thread and acts as target for -// interactions that outlives individual stream, e.g. timer, async calls. -class AuthnRootContext : public RootContext { - public: - AuthnRootContext(uint32_t id, absl::string_view root_id) - : RootContext(id, root_id) {} - ~AuthnRootContext() {} - - // RootContext - bool validateConfiguration(size_t) override { return true; } - bool onConfigure(size_t) override { return true; }; - bool onStart(size_t) override { return true; } - void onTick() override {} - void onQueueReady(uint32_t) override {} - bool onDone() override { return true; } - - // Low level HTTP/gRPC interface. - void onHttpCallResponse(uint32_t token, uint32_t headers, size_t body_size, - uint32_t trailers) override {} - void onGrpcReceiveInitialMetadata(uint32_t token, uint32_t headers) override { - } - void onGrpcReceiveTrailingMetadata(uint32_t token, - uint32_t trailers) override {} - void onGrpcReceive(uint32_t token, size_t body_size) override {} - void onGrpcClose(uint32_t token, GrpcStatus status) override {} - - const FilterConfig& filterConfig() { return filter_config_; }; - - private: - FilterConfig filter_config_; -}; - -// Per-stream context. -class AuthnContext : public Context { - public: - explicit AuthnContext(uint32_t id, RootContext* root) : Context(id, root) {} - ~AuthnContext() = default; - - void onCreate() override {} - - // Context - FilterStatus onNewConnection() override { return FilterStatus::Continue; } - FilterStatus onDownstreamData(size_t, bool) override { - return FilterStatus::Continue; - } - FilterStatus onUpstreamData(size_t, bool) override { - return FilterStatus::Continue; - } - void onDownstreamConnectionClose(PeerType) override {} - void onUpstreamConnectionClose(PeerType) override {} - FilterHeadersStatus onRequestHeaders(uint32_t) override; - FilterMetadataStatus onRequestMetadata(uint32_t) override { - return FilterMetadataStatus::Continue; - } - FilterDataStatus onRequestBody(size_t, bool) override { - return FilterDataStatus::Continue; - } - FilterTrailersStatus onRequestTrailers(uint32_t) override { - return FilterTrailersStatus::Continue; - } - FilterHeadersStatus onResponseHeaders(uint32_t) override { - return FilterHeadersStatus::Continue; - } - FilterMetadataStatus onResponseMetadata(uint32_t) override { - return FilterMetadataStatus::Continue; - } - FilterDataStatus onResponseBody(size_t, bool) override { - return FilterDataStatus::Continue; - } - FilterTrailersStatus onResponseTrailers(uint32_t) override { - return FilterTrailersStatus::Continue; - } - void onDone() override {} - void onLog() override {} - - const FilterConfig& filterConfig() { return rootContext()->filterConfig(); }; - - private: - std::unique_ptr createPeerAuthenticator( - FilterContextPtr filter_context); - // TODO(shikugawa): origin authenticator implementation. - // std::unique_ptr createOriginAuthenticator( - // istio::AuthN::FilterContext* filter_context); - - inline AuthnRootContext* rootContext() { - return dynamic_cast(this->root()); - }; - - // Context for authentication process. Created in decodeHeader to start - // authentication process. - FilterContextPtr filter_context_; -}; - -static RegisterContextFactory register_AuthnWasm( - CONTEXT_FACTORY(AuthnContext), ROOT_FACTORY(AuthnRootContext)); - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.cc b/src/envoy/http/authn_wasm/filter_context.cc deleted file mode 100644 index e31210dd452..00000000000 --- a/src/envoy/http/authn_wasm/filter_context.cc +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "src/envoy/http/authn_wasm/filter_context.h" - -#include - -#include "absl/strings/str_cat.h" -#include "src/envoy/utils/filter_names.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -FilterContext::FilterContext( - ConnectionContext&& connection_context, const RawHeaderMap& raw_header_map, - const istio::authn::Metadata& dynamic_metadata, - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config) - : connection_context_(std::move(connection_context)), - filter_config_(filter_config), - dynamic_metadata_(dynamic_metadata) { - createHeaderMap(std::move(raw_header_map)); -} - -void FilterContext::setPeerResult(const istio::authn::Payload* payload) { - if (payload != nullptr) { - switch (payload->payload_case()) { - case istio::authn::Payload::kX509: - logDebug(absl::StrCat("Set peer from X509: ", payload->x509().user())); - result_.set_peer_user(payload->x509().user()); - break; - case istio::authn::Payload::kJwt: - logDebug(absl::StrCat("Set peer from JWT: ", payload->jwt().user())); - result_.set_peer_user(payload->jwt().user()); - break; - default: - logDebug("Payload has not peer authentication data"); - break; - } - } -} - -void FilterContext::setOriginResult(const istio::authn::Payload* payload) { - // Authentication pass, look at the return payload and store to the context - // output. Set filter to continueDecoding when done. - // At the moment, only JWT can be used for origin authentication, so - // it's ok just to check jwt payload. - if (payload != nullptr && payload->has_jwt()) { - *result_.mutable_origin() = payload->jwt(); - } -} - -void FilterContext::setPrincipal( - const istio::authentication::v1alpha1::PrincipalBinding& binding) { - switch (binding) { - case istio::authentication::v1alpha1::PrincipalBinding::USE_PEER: - logDebug(absl::StrCat("Set principal from peer: ", result_.peer_user())); - result_.set_principal(result_.peer_user()); - return; - case istio::authentication::v1alpha1::PrincipalBinding::USE_ORIGIN: - logDebug( - absl::StrCat("Set principal from origin: ", result_.origin().user())); - result_.set_principal(result_.origin().user()); - return; - default: - // Should never come here. - logDebug("Invalid binding value"); - return; - } -} - -void FilterContext::createHeaderMap(const RawHeaderMap& raw_header_map) { - for (const auto& header : raw_header_map) { - header_map_.emplace(header.first.data(), header.second.data()); - } -} - -absl::optional getJwtPayload(const std::string& issuer) const { - auto jwt_payload = getJwtPayloadFromEnvoyJwtFilter(issuer); - if (jwt_payload.has_value()) { - return jwt_payload; - } - - jwt_payload = getJwtPayloadFromIstioJwtFilter(issuer); - if (jwt_payload.has_value()) { - return jwt_payload; - } - - return absl::nullopt; -} - -absl::optional FilterContext::getJwtPayloadFromEnvoyJwtFilter( - const std::string& issuer) const { - // Try getting the Jwt payload from Envoy jwt_authn filter. - auto filter_it = dynamic_metadata_.filter_metadata().find( - Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn); - if (filter_it == dynamic_metadata_.filter_metadata().end()) { - logDebug("No dynamic_metadata found for filter ", - Extensions::HttpFilters::HttpFilterNames::get().JwtAuthn); - return absl::nullopt; - } - - const auto& data_struct = filter_it->second; - - const auto entry_it = data_struct.fields().find(issuer); - if (entry_it == data_struct.fields().end()) { - return absl::nullopt; - } - - if (entry_it->second.struct_value().fields().empty()) { - return absl::nullopt; - } - - std::string payload; - // Serialize the payload from Envoy jwt filter first before writing it to - // |payload|. - // TODO (pitlv2109): Return protobuf Struct instead of string, once Istio jwt - // filter is removed. Also need to change how Istio authn filter processes the - // jwt payload. - Protobuf::util::MessageToJsonString(entry_it->second.struct_value(), - &payload); - return payload; -} - -absl::optional FilterContext::getJwtPayloadFromIstioJwtFilter( - const std::string& issuer) const { - // Try getting the Jwt payload from Istio jwt-auth filter. - auto filter_it = - dynamic_metadata_.filter_metadata().find(Utils::IstioFilterName::kJwt); - if (filter_it == dynamic_metadata_.filter_metadata().end()) { - logDebug("No dynamic_metadata found for filter ", - Utils::IstioFilterName::kJwt); - return absl::nullopt; - } - - const auto& data_struct = filter_it->second; - - const auto entry_it = data_struct.fields().find(issuer); - if (entry_it == data_struct.fields().end()) { - return absl::nullopt; - } - - if (entry_it->second.string_value().empty()) { - return absl::nullopt; - } - - return entry_it->second.string_value(); -} - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/filter_context.h b/src/envoy/http/authn_wasm/filter_context.h deleted file mode 100644 index ec49aa21254..00000000000 --- a/src/envoy/http/authn_wasm/filter_context.h +++ /dev/null @@ -1,135 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include -#include -#include -#include - -#include "absl/strings/string_view.h" -#include "authentication/v1alpha1/policy.pb.h" -#include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" -#include "src/envoy/http/authn_wasm/connection_context.h" -#include "src/istio/authn/context.pb.h" - -#ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics.h" -#else -#include "include/proxy-wasm/null_plugin.h" - -using proxy_wasm::null_plugin::getProperty; -using proxy_wasm::null_plugin::logDebug; - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -using RawHeaderMap = - std::vector>; -// TODO(shikugawa): use Envoy::Http::HeaderMapImpl. Because which is optimized -// to process headers. -using HeaderMap = std::unordered_map; - -// FilterContext holds inputs, such as request dynamic metadata and -// connection and result data for authentication process. -class FilterContext { - public: - FilterContext( - ConnectionContext&& connection_context, - const RawHeaderMap& raw_header_map, - const istio::authn::Metadata& dynamic_metadata, - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config); - - // Sets peer result based on authenticated payload. Input payload can be - // null, which basically changes nothing. - void setPeerResult(const istio::authn::Payload* payload); - - // Sets origin result based on authenticated payload. Input payload can be - // null, which basically changes nothing. - void setOriginResult(const istio::authn::Payload* payload); - - // Sets principal based on binding rule, and the existing peer and origin - // result. - void setPrincipal( - const istio::authentication::v1alpha1::PrincipalBinding& binding); - - // Returns the authentication result. - const istio::authn::Result& authenticationResult() { return result_; } - - // Accessor to the filter config - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filterConfig() const { - return filter_config_; - } - - // Gets JWT payload (output from JWT filter) for given issuer. If non-empty - // payload found, returns true and set the output payload string. Otherwise, - // returns false. - absl::optional getJwtPayload(const std::string& issuer) const; - - // Return header map. - const HeaderMap& requestHeader() { return header_map_; } - - const ConnectionContext& connectionContext() const { - return connection_context_; - } - - private: - void createHeaderMap(const RawHeaderMap& raw_header_map); - - // Helper function for getJwtPayload(). It gets the jwt payload from Envoy jwt - // filter metadata and write to |payload|. - absl::optional getJwtPayloadFromEnvoyJwtFilter( - const std::string& issuer) const; - - // Helper function for getJwtPayload(). It gets the jwt payload from Istio jwt - // filter metadata and write to |payload|. - absl::optional getJwtPayloadFromIstioJwtFilter( - const std::string& issuer) const; - - // http request header - HeaderMap header_map_; - - // context of established connection - const ConnectionContext& connection_context_; - - // Holds authentication attribute outputs. - istio::authn::Result result_; - - // Store the Istio authn filter config. - const istio::envoy::config::filter::http::authn::v2alpha1::FilterConfig& - filter_config_; - - // Const reference to request info dynamic metadata. This provides data that - // output from other filters, e.g JWT. - const istio::authn::Metadata& dynamic_metadata_; -}; - -using FilterContextPtr = std::shared_ptr; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/json.cc b/src/envoy/http/authn_wasm/json.cc deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/envoy/http/authn_wasm/json.h b/src/envoy/http/authn_wasm/json.h deleted file mode 100644 index 2ed9e612261..00000000000 --- a/src/envoy/http/authn_wasm/json.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics.h" -#else -#include "include/proxy-wasm/null_plugin.h" - -using proxy_wasm::null_plugin::logDebug; -using proxy_wasm::null_plugin::logError; - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -class JsonObject { - public: - JsonObject(std::string& json_str); -}; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/peer.cc b/src/envoy/http/authn_wasm/peer.cc deleted file mode 100644 index 89589164c65..00000000000 --- a/src/envoy/http/authn_wasm/peer.cc +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "src/envoy/http/authn_wasm/peer.h" - -#include "absl/strings/str_cat.h" - -#ifndef NULL_PLUGIN -#include "proxy_wasm_intrinsics.h" -#else -#include "include/proxy-wasm/null_plugin.h" - -using proxy_wasm::null_plugin::logDebug; -using proxy_wasm::null_plugin::logError; - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -PeerAuthenticator::PeerAuthenticator( - FilterContextPtr filter_context, - const istio::authentication::v1alpha1::Policy& policy) - : AuthenticatorBase(filter_context), policy_(policy) {} - -bool PeerAuthenticator::run(istio::authn::Payload* payload) { - bool success = false; - if (policy_.peers_size() == 0) { - logDebug("No method defined. Skip source authentication."); - success = true; - return success; - } - for (const auto& method : policy_.peers()) { - switch (method.params_case()) { - case istio::authentication::v1alpha1::PeerAuthenticationMethod:: - ParamsCase::kMtls: - success = validateX509(method.mtls(), payload); - break; - case istio::authentication::v1alpha1::PeerAuthenticationMethod:: - ParamsCase::kJwt: // This is deprecated. - success = validateJwt(method.jwt(), payload); - break; - default: - logError(absl::StrCat("Unknown peer authentication param ", - method.DebugString())); - success = false; - break; - } - - if (success) { - break; - } - } - - if (success) { - filterContext()->setPeerResult(payload); - } - - return success; -} - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/peer.h b/src/envoy/http/authn_wasm/peer.h deleted file mode 100644 index 3d20466e328..00000000000 --- a/src/envoy/http/authn_wasm/peer.h +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include - -#include "src/envoy/http/authn_wasm/base.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -// PeerAuthenticator performs peer authentication for given policy. -// This is only to use connection level authentication. -class PeerAuthenticator : public AuthenticatorBase { - public: - static std::unique_ptr create( - FilterContextPtr filter_context) { - return std::make_unique( - filter_context, filter_context->filterConfig().policy()); - } - - bool run(istio::authn::Payload*) override; - - explicit PeerAuthenticator( - FilterContextPtr filter_context, - const istio::authentication::v1alpha1::Policy& policy); - - private: - // Reference to the authentication policy that the authenticator should - // enforce. Typically, the actual object is owned by filter. - const istio::authentication::v1alpha1::Policy& policy_; -}; - -using PeerAuthenticatorPtr = std::unique_ptr; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/request.cc b/src/envoy/http/authn_wasm/request.cc deleted file mode 100644 index 17445363f88..00000000000 --- a/src/envoy/http/authn_wasm/request.cc +++ /dev/null @@ -1,117 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "src/envoy/http/authn_wasm/request.h" - -#include "absl/strings/str_cat.h" -#include "authentication/v1alpha1/policy.pb.h" -#include "common/http/headers.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -RequestAuthenticator::RequestAuthenticator( - FilterContextPtr filter_context, - const istio::authentication::v1alpha1::Policy& policy) - : AuthenticatorBase(filter_context), policy_(policy) {} - -RequestAuthenticator::run(istio::authn::Payload* payload) { - if (policy_.origins_size() == 0 && - policy_.principal_binding() == - istio::authentication::v1alpha1::PrincipalBinding::USE_ORIGIN) { - // Validation should reject policy that have rule to USE_ORIGIN but - // does not provide any origin method so this code should - // never reach. However, it's ok to treat it as authentication - // fails. - logWarn(absl::StrCat( - "Principal is binded to origin, but no method specified in policy ", - policy_.DebugString())); - return false; - } - - constexpr auto isCorsPreflightRequest = - [](const Http::RequestHeaderMap& headers) -> bool { - return headers.Method() && - headers.Method()->value().getStringView() == - Http::Headers::get().MethodValues.Options && - headers.Origin() && !headers.Origin()->value().empty() && - headers.AccessControlRequestMethod() && - !headers.AccessControlRequestMethod()->value().empty(); - }; - - if (isCorsPreflightRequest(filterContext()->headerMap())) { - // The CORS preflight doesn't include user credentials, allow regardless of - // JWT policy. See - // http://www.w3.org/TR/cors/#cross-origin-request-with-preflight. - logDebug("CORS preflight request allowed regardless of JWT policy"); - return true; - } - - absl::string_view path; - if (filterContext()->headerMap().find(":path") != filterContext()->headerMap().end()) { - path = filterContext()->headerMap().at(":path"); - - size_t offset = path.find_first_of("?#"); - if (offset != absl::string_view::npos) { - path.remove_suffix(path.length() - offset); - } - logTrace(absl::StrCat("Got request path {}", path)); - } else { - logError(absl::StrCat("Failed to get request path, JWT will always be used for validation")); - } - - bool triggered = false; - bool triggered_success = false; - for (const auto& method : policy_.origins()) { - const auto& jwt = method.jwt(); - - if (AuthnUtils::ShouldValidateJwtPerPath(path, jwt)) { - logDebug("Validating request path ", path, " for jwt ", jwt.DebugString()); - // set triggered to true if any of the jwt trigger rule matched. - triggered = true; - if (validateJwt(jwt, payload)) { - ENVOY_LOG(debug, "JWT validation succeeded"); - triggered_success = true; - break; - } - } - } - - // returns true if no jwt was triggered, or triggered and success. - if (!triggered || triggered_success) { - filterContext()->setOriginResult(payload); - filterContext()->setPrincipal(policy_.principal_binding()); - logDebug("Origin authenticator succeeded"); - return true; - } - - logDebug("Origin authenticator failed"); - return false; -} - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/authn_wasm/request.h b/src/envoy/http/authn_wasm/request.h deleted file mode 100644 index 27c5e5d9855..00000000000 --- a/src/envoy/http/authn_wasm/request.h +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2020 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include "authentication/v1alpha1/policy.pb.h" -#include "src/envoy/http/authn_wasm/base.h" - -#ifdef NULL_PLUGIN - -namespace proxy_wasm { -namespace null_plugin { -namespace Http { -namespace AuthN { - -#endif - -// This authenticator performs to authenticate on request level. -class RequestAuthenticator : public AuthenticatorBase { - public: - static std::unique_ptr create( - FilterContextPtr filter_context) { - return std::make_unique( - filter_context, filter_context->filterConfig().policy()); - } - - bool run(istio::authn::Payload*) override; - - explicit RequestAuthenticator( - FilterContextPtr filter_context, - const istio::authentication::v1alpha1::Policy& policy); - - private: - // Reference to the authentication policy that the authenticator should - // enforce. Typically, the actual object is owned by filter. - const istio::authentication::v1alpha1::Policy& policy_; -}; - -using RequestAuthenticatorPtr = std::unique_ptr; - -#ifdef NULL_PLUGIN - -} // namespace AuthN -} // namespace Http -} // namespace null_plugin -} // namespace proxy_wasm - -#endif \ No newline at end of file diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index 3616609aa15..34e181aa8dc 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -24,18 +24,20 @@ load( "envoy_cc_test", ) -envoy_cc_library( +cc_library( name = "jwt_lib", srcs = ["jwt.cc"], hdrs = ["jwt.h"], - external_deps = [ - "ssl", - ], - repository = "@envoy", + # external_deps = [ + # "ssl", + # ], + # repository = "@envoy", deps = [ + "//external:ssl", "//extensions/common:json_util_wasm", - "@envoy//source/common/common:base64_lib", - "@envoy//source/common/common:assert_lib", + # "@envoy//source/common/common:base64_lib", + # "@envoy//source/common/common:assert_lib", + # "@envoy//source/common/common:utility_lib", ], ) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 23daec354a0..36749554fd5 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -23,10 +23,11 @@ #include #include -#include "common/common/assert.h" +#include +#include "absl/strings/str_split.h" +// #include "common/common/assert.h" #include "common/common/base64.h" -#include "common/common/utility.h" -#include "common/json/json_loader.h" +// #include "common/common/utility.h" #include "openssl/bn.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" @@ -75,68 +76,68 @@ namespace { // https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c // // and modified the position of 62 ('+' to '-') and 63 ('/' to '_') -const uint8_t kReverseLookupTableBase64Url[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, - 63, 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64}; - -bool IsNotBase64UrlChar(int8_t c) { - return kReverseLookupTableBase64Url[static_cast(c)] & 64; -} +// const uint8_t kReverseLookupTableBase64Url[256] = { +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, +// 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, +// 63, 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, +// 64, 64, 64, 64, 64, 64, 64, 64, 64}; + +// bool IsNotBase64UrlChar(int8_t c) { +// return kReverseLookupTableBase64Url[static_cast(c)] & 64; +// } } // namespace -std::string Base64UrlDecode(std::string input) { - // allow at most 2 padding letters at the end of the input, only if input - // length is divisible by 4 - int len = input.length(); - if (len % 4 == 0) { - if (input[len - 1] == '=') { - input.pop_back(); - if (input[len - 2] == '=') { - input.pop_back(); - } - } - } - // if input contains non-base64url character, return empty string - // Note: padding letter must not be contained - if (std::find_if(input.begin(), input.end(), IsNotBase64UrlChar) != - input.end()) { - return ""; - } - - // base64url is using '-', '_' instead of '+', '/' in base64 string. - std::replace(input.begin(), input.end(), '-', '+'); - std::replace(input.begin(), input.end(), '_', '/'); - - // base64 string should be padded with '=' so as to the length of the string - // is divisible by 4. - switch (input.length() % 4) { - case 0: - break; - case 2: - input += "=="; - break; - case 3: - input += "="; - break; - default: - // * an invalid base64url input. return empty string. - return ""; - } - return Base64::decode(input); -} +// std::string Base64UrlDecode(std::string input) { +// // allow at most 2 padding letters at the end of the input, only if input +// // length is divisible by 4 +// int len = input.length(); +// if (len % 4 == 0) { +// if (input[len - 1] == '=') { +// input.pop_back(); +// if (input[len - 2] == '=') { +// input.pop_back(); +// } +// } +// } +// // if input contains non-base64url character, return empty string +// // Note: padding letter must not be contained +// if (std::find_if(input.begin(), input.end(), IsNotBase64UrlChar) != +// input.end()) { +// return ""; +// } + +// // base64url is using '-', '_' instead of '+', '/' in base64 string. +// std::replace(input.begin(), input.end(), '-', '+'); +// std::replace(input.begin(), input.end(), '_', '/'); + +// // base64 string should be padded with '=' so as to the length of the string +// // is divisible by 4. +// switch (input.length() % 4) { +// case 0: +// break; +// case 2: +// input += "=="; +// break; +// case 3: +// input += "="; +// break; +// default: +// // * an invalid base64url input. return empty string. +// return ""; +// } +// return Base64::decode(input); +// } namespace { @@ -157,84 +158,84 @@ class EvpPkeyGetter : public WithStatus { public: EvpPkeyGetter() {} - bssl::UniquePtr EvpPkeyFromStr(const std::string &pkey_pem) { - std::string pkey_der = Base64::decode(pkey_pem); - if (pkey_der == "") { - UpdateStatus(Status::PEM_PUBKEY_BAD_BASE64); - return nullptr; - } - auto rsa = bssl::UniquePtr( - RSA_public_key_from_bytes(CastToUChar(pkey_der), pkey_der.length())); - if (!rsa) { - UpdateStatus(Status::PEM_PUBKEY_PARSE_ERROR); - } - return EvpPkeyFromRsa(rsa.get()); - } - - bssl::UniquePtr EvpPkeyFromJwkRSA(const std::string &n, - const std::string &e) { - return EvpPkeyFromRsa(RsaFromJwk(n, e).get()); - } - - bssl::UniquePtr EcKeyFromJwkEC(const std::string &x, - const std::string &y) { - bssl::UniquePtr ec_key( - EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); - if (!ec_key) { - UpdateStatus(Status::FAILED_CREATE_EC_KEY); - return nullptr; - } - bssl::UniquePtr bn_x = BigNumFromBase64UrlString(x); - bssl::UniquePtr bn_y = BigNumFromBase64UrlString(y); - if (!bn_x || !bn_y) { - // EC public key field is missing or has parse error. - UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); - return nullptr; - } - - if (EC_KEY_set_public_key_affine_coordinates(ec_key.get(), bn_x.get(), - bn_y.get()) == 0) { - UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); - return nullptr; - } - return ec_key; - } - - private: - // In the case where rsa is nullptr, UpdateStatus() should be called - // appropriately elsewhere. - bssl::UniquePtr EvpPkeyFromRsa(RSA *rsa) { - if (!rsa) { - return nullptr; - } - bssl::UniquePtr key(EVP_PKEY_new()); - EVP_PKEY_set1_RSA(key.get(), rsa); - return key; - } - - bssl::UniquePtr BigNumFromBase64UrlString(const std::string &s) { - std::string s_decoded = Base64UrlDecode(s); - if (s_decoded == "") { - return nullptr; - } - return bssl::UniquePtr( - BN_bin2bn(CastToUChar(s_decoded), s_decoded.length(), NULL)); - }; - - bssl::UniquePtr RsaFromJwk(const std::string &n, const std::string &e) { - bssl::UniquePtr rsa(RSA_new()); - // It crash if RSA object couldn't be created. - assert(rsa); - - rsa->n = BigNumFromBase64UrlString(n).release(); - rsa->e = BigNumFromBase64UrlString(e).release(); - if (!rsa->n || !rsa->e) { - // RSA public key field is missing or has parse error. - UpdateStatus(Status::JWK_RSA_PUBKEY_PARSE_ERROR); - return nullptr; - } - return rsa; - } +// bssl::UniquePtr EvpPkeyFromStr(const std::string &pkey_pem) { +// std::string pkey_der = Base64::decode(pkey_pem); +// if (pkey_der == "") { +// UpdateStatus(Status::PEM_PUBKEY_BAD_BASE64); +// return nullptr; +// } +// auto rsa = bssl::UniquePtr( +// RSA_public_key_from_bytes(CastToUChar(pkey_der), pkey_der.length())); +// if (!rsa) { +// UpdateStatus(Status::PEM_PUBKEY_PARSE_ERROR); +// } +// return EvpPkeyFromRsa(rsa.get()); +// } + +// bssl::UniquePtr EvpPkeyFromJwkRSA(const std::string &n, +// const std::string &e) { +// return EvpPkeyFromRsa(RsaFromJwk(n, e).get()); +// } + +// bssl::UniquePtr EcKeyFromJwkEC(const std::string &x, +// const std::string &y) { +// bssl::UniquePtr ec_key( +// EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); +// if (!ec_key) { +// UpdateStatus(Status::FAILED_CREATE_EC_KEY); +// return nullptr; +// } +// bssl::UniquePtr bn_x = BigNumFromBase64UrlString(x); +// bssl::UniquePtr bn_y = BigNumFromBase64UrlString(y); +// if (!bn_x || !bn_y) { +// // EC public key field is missing or has parse error. +// UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); +// return nullptr; +// } + +// if (EC_KEY_set_public_key_affine_coordinates(ec_key.get(), bn_x.get(), +// bn_y.get()) == 0) { +// UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); +// return nullptr; +// } +// return ec_key; +// } + +// private: +// // In the case where rsa is nullptr, UpdateStatus() should be called +// // appropriately elsewhere. +// bssl::UniquePtr EvpPkeyFromRsa(RSA *rsa) { +// if (!rsa) { +// return nullptr; +// } +// bssl::UniquePtr key(EVP_PKEY_new()); +// EVP_PKEY_set1_RSA(key.get(), rsa); +// return key; +// } + +// bssl::UniquePtr BigNumFromBase64UrlString(const std::string &s) { +// std::string s_decoded = Base64UrlDecode(s); +// if (s_decoded == "") { +// return nullptr; +// } +// return bssl::UniquePtr( +// BN_bin2bn(CastToUChar(s_decoded), s_decoded.length(), NULL)); +// }; + +// bssl::UniquePtr RsaFromJwk(const std::string &n, const std::string &e) { +// bssl::UniquePtr rsa(RSA_new()); +// // It crash if RSA object couldn't be created. +// assert(rsa); + +// rsa->n = BigNumFromBase64UrlString(n).release(); +// rsa->e = BigNumFromBase64UrlString(e).release(); +// if (!rsa->n || !rsa->e) { +// // RSA public key field is missing or has parse error. +// UpdateStatus(Status::JWK_RSA_PUBKEY_PARSE_ERROR); +// return nullptr; +// } +// return rsa; +// } }; } // namespace @@ -245,7 +246,8 @@ Jwt::Jwt(const std::string &jwt) { UpdateStatus(Status::JWT_BAD_FORMAT); return; } - auto jwt_split = StringUtil::splitToken(jwt, "."); + std::vector jwt_split = absl::StrSplit(jwt, "."); + // auto jwt_split = StringUtil::splitToken(jwt, "."); if (jwt_split.size() != 3) { UpdateStatus(Status::JWT_BAD_FORMAT); return; @@ -254,7 +256,8 @@ Jwt::Jwt(const std::string &jwt) { // Parse header json auto parser = Wasm::Common::JsonParser(); header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); - header_str_ = Base64UrlDecode(header_str_base64url_); + // header_str_ = Base64UrlDecode(header_str_base64url_); + header_str_ = header_str_base64url_; parser.parse(header_str_); if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { @@ -303,7 +306,8 @@ Jwt::Jwt(const std::string &jwt) { // Parse payload json payload_str_base64url_ = std::string(jwt_split[1].begin(), jwt_split[1].end()); - payload_str_ = Base64UrlDecode(payload_str_base64url_); + // payload_str_ = Base64UrlDecode(payload_str_base64url_); + payload_str_ = payload_str_base64url_; parser.parse(payload_str_); if (parser.detail() != Wasm::Common::JsonParserResultDetail::OK) { UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); @@ -337,8 +341,8 @@ Jwt::Jwt(const std::string &jwt) { } // Set up signature - signature_ = - Base64UrlDecode(std::string(jwt_split[2].begin(), jwt_split[2].end())); + signature_ = ""; + // Base64UrlDecode(std::string(jwt_split[2].begin(), jwt_split[2].end())); if (signature_ == "") { // Signature is a bad Base64url input. UpdateStatus(Status::JWT_SIGNATURE_PARSE_ERROR); @@ -481,7 +485,7 @@ void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { keys_.clear(); std::unique_ptr key_ptr(new Pubkey()); EvpPkeyGetter e; - key_ptr->evp_pkey_ = e.EvpPkeyFromStr(pkey_pem); + // key_ptr->evp_pkey_ = e.EvpPkeyFromStr(pkey_pem); key_ptr->pem_format_ = true; UpdateStatus(e.GetStatus()); if (e.GetStatus() == Status::OK) { @@ -589,7 +593,7 @@ bool Pubkeys::ExtractPubkeyFromJwkRSA( e_str = e_str_field.fetch(); EvpPkeyGetter e; - pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); + // pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); if (e.GetStatus() == Status::OK) { keys_.push_back(std::move(pubkey)); } else { @@ -636,7 +640,7 @@ bool Pubkeys::ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject &jwk_json) { y_str = y_str_field.fetch(); EvpPkeyGetter e; - pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); + // pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); if (e.GetStatus() == Status::OK) { keys_.push_back(std::move(pubkey)); } else { @@ -657,7 +661,8 @@ std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, keys->CreateFromPemCore(pkey); break; default: - PANIC("can not reach here"); + std::cout << "" << std::endl; + // PANIC("can not reach here"); } return keys; } diff --git a/src/envoy/utils/BUILD b/src/envoy/utils/BUILD index 81eb3010446..97e88910215 100644 --- a/src/envoy/utils/BUILD +++ b/src/envoy/utils/BUILD @@ -70,6 +70,24 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "utils_wasm_lib", + srcs = [ + "utils.cc" + ], + hdrs = [ + "utils.h" + ], + repository = "@envoy", + visibility = ["//visibility:public"], + deps = [ + "//include/istio/mixerclient:headers_lib", + "//include/istio/utils:headers_lib", + "@envoy//include/envoy/network:connection_interface", + "@envoy//source/common/http:headers_lib", + ] +) + envoy_cc_test( name = "authn_test", srcs = [ From 10b8fb0489e1f7aab6f667d14b2bdb501ba85c39 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sun, 21 Jun 2020 12:09:19 +0000 Subject: [PATCH 27/44] fix --- src/envoy/http/jwt_auth/jwt.cc | 1 - src/envoy/http/jwt_auth/jwt_authenticator_test.cc | 1 - 2 files changed, 2 deletions(-) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 23daec354a0..88d0afe70e5 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -26,7 +26,6 @@ #include "common/common/assert.h" #include "common/common/base64.h" #include "common/common/utility.h" -#include "common/json/json_loader.h" #include "openssl/bn.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" diff --git a/src/envoy/http/jwt_auth/jwt_authenticator_test.cc b/src/envoy/http/jwt_auth/jwt_authenticator_test.cc index cf54a86d1bb..c7fc0d4d2bc 100644 --- a/src/envoy/http/jwt_auth/jwt_authenticator_test.cc +++ b/src/envoy/http/jwt_auth/jwt_authenticator_test.cc @@ -16,7 +16,6 @@ #include "src/envoy/http/jwt_auth/jwt_authenticator.h" #include "common/http/message_impl.h" -#include "common/json/json_loader.h" #include "gtest/gtest.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/utility.h" From 63c66f8a430e0e4c44ad4277ffac71fd67ca5dab Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sun, 21 Jun 2020 13:43:59 +0000 Subject: [PATCH 28/44] jwt_authn: replace with wasm buildable base64 --- extensions/BUILD | 2 +- extensions/common/BUILD | 18 ++++ .../{metadata_exchange => common}/base64.h | 64 +++++++++++++-- extensions/metadata_exchange/BUILD | 2 - src/envoy/http/jwt_auth/BUILD | 2 +- src/envoy/http/jwt_auth/jwt.cc | 82 ++----------------- 6 files changed, 83 insertions(+), 87 deletions(-) rename extensions/{metadata_exchange => common}/base64.h (67%) diff --git a/extensions/BUILD b/extensions/BUILD index 02aa31d00c8..eef0b787533 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -50,13 +50,13 @@ wasm_cc_binary( wasm_cc_binary( name = "metadata_exchange.wasm", srcs = [ + "//extensions/common:base64.h", "//extensions/common:context.cc", "//extensions/common:context.h", "//extensions/common:proto_util.cc", "//extensions/common:proto_util.h", "//extensions/common:util.cc", "//extensions/common:util.h", - "//extensions/metadata_exchange:base64.h", "//extensions/metadata_exchange:plugin.cc", "//extensions/metadata_exchange:plugin.h", ], diff --git a/extensions/common/BUILD b/extensions/common/BUILD index 5472c310588..af0acef2826 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -183,3 +183,21 @@ cc_library( "@com_google_absl_wasm//absl/types:optional", ], ) + +envoy_cc_library( + name = "base64", + hdrs = [ + "base64.h", + ], + repository = "@envoy", + visibility = ["//visibility:public"], +) + +cc_library( + name = "base64_wasm", + hdrs = [ + "base64.h", + ], + copts = ["-UNULL_PLUGIN"], + visibility = ["//visibility:public"], +) diff --git a/extensions/metadata_exchange/base64.h b/extensions/common/base64.h similarity index 67% rename from extensions/metadata_exchange/base64.h rename to extensions/common/base64.h index 329b7036c1e..6ee7745e944 100644 --- a/extensions/metadata_exchange/base64.h +++ b/extensions/common/base64.h @@ -20,6 +20,8 @@ #include +#include "absl/strings/string_view.h" + class Base64 { public: static std::string encode(const char* input, uint64_t length, @@ -27,14 +29,14 @@ class Base64 { static std::string encode(const char* input, uint64_t length) { return encode(input, length, true); } - static std::string decodeWithoutPadding(std::string_view input); + static std::string decodeWithoutPadding(absl::string_view input); }; // clang-format off -inline constexpr char CHAR_TABLE[] = +constexpr char CHAR_TABLE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -inline constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { +constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, @@ -46,6 +48,10 @@ inline constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; + +constexpr unsigned char REVERSE_LOOKUP_TABLE_BASE64_URL[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, 63, 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; // clang-format on inline bool decodeBase(const uint8_t cur_char, uint64_t pos, std::string& ret, @@ -139,6 +145,48 @@ inline void encodeLast(uint64_t pos, uint8_t last_char, std::string& ret, } } +std::string Base64UrlDecode(std::string input) { + // allow at most 2 padding letters at the end of the input, only if input + // length is divisible by 4 + int len = input.length(); + if (len % 4 == 0) { + if (input[len - 1] == '=') { + input.pop_back(); + if (input[len - 2] == '=') { + input.pop_back(); + } + } + } + // if input contains non-base64url character, return empty string + // Note: padding letter must not be contained + if (std::find_if(input.begin(), input.end(), [](auto c) -> bool { + return REVERSE_LOOKUP_TABLE_BASE64_URL[static_cast(c)] & 64; + }) != input.end()) { + return ""; + } + + // base64url is using '-', '_' instead of '+', '/' in base64 string. + std::replace(input.begin(), input.end(), '-', '+'); + std::replace(input.begin(), input.end(), '_', '/'); + + // base64 string should be padded with '=' so as to the length of the string + // is divisible by 4. + switch (input.length() % 4) { + case 0: + break; + case 2: + input += "=="; + break; + case 3: + input += "="; + break; + default: + // * an invalid base64url input. return empty string. + return ""; + } + return Base64::decodeWithoutPadding(input); +} + inline std::string Base64::encode(const char* input, uint64_t length, bool add_padding) { uint64_t output_length = (length + 2) / 3 * 4; @@ -157,9 +205,9 @@ inline std::string Base64::encode(const char* input, uint64_t length, return ret; } -inline std::string Base64::decodeWithoutPadding(StringView input) { +inline std::string Base64::decodeWithoutPadding(absl::string_view input) { if (input.empty()) { - return EMPTY_STRING; + return ""; } // At most last two chars can be '='. @@ -185,14 +233,14 @@ inline std::string Base64::decodeWithoutPadding(StringView input) { ret.reserve(max_length); for (uint64_t i = 0; i < last; ++i) { if (!decodeBase(input[i], i, ret, REVERSE_LOOKUP_TABLE)) { - return EMPTY_STRING; + return ""; } } if (!decodeLast(input[last], last, ret, REVERSE_LOOKUP_TABLE)) { - return EMPTY_STRING; + return ""; } - ASSERT(ret.size() == max_length); + assert(ret.size() == max_length); return ret; } diff --git a/extensions/metadata_exchange/BUILD b/extensions/metadata_exchange/BUILD index 50a105cb785..f0520208567 100644 --- a/extensions/metadata_exchange/BUILD +++ b/extensions/metadata_exchange/BUILD @@ -50,5 +50,3 @@ cc_proto_library( name = "declare_property_proto_cc", deps = [":declare_property_proto"], ) - -exports_files(["base64.h"]) diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index 69d10e6fb97..4737feddd35 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -33,8 +33,8 @@ envoy_cc_library( ], repository = "@envoy", deps = [ + "//extensions/common:base64", "//extensions/common:json_util", - "@envoy//source/exe:envoy_common_lib", ], ) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 88d0afe70e5..183395f58eb 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -17,15 +17,14 @@ #include #include +#include #include #include #include #include #include -#include "common/common/assert.h" -#include "common/common/base64.h" -#include "common/common/utility.h" +#include "absl/strings/str_split.h" #include "openssl/bn.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" @@ -36,6 +35,8 @@ namespace Envoy { namespace Http { namespace JwtAuth { +#include "extensions/common/base64.h" + std::string StatusToString(Status status) { static std::map table = { {Status::OK, "OK"}, @@ -70,75 +71,6 @@ std::string StatusToString(Status status) { namespace { -// Conversion table is taken from -// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c -// -// and modified the position of 62 ('+' to '-') and 63 ('/' to '_') -const uint8_t kReverseLookupTableBase64Url[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, - 63, 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64}; - -bool IsNotBase64UrlChar(int8_t c) { - return kReverseLookupTableBase64Url[static_cast(c)] & 64; -} - -} // namespace - -std::string Base64UrlDecode(std::string input) { - // allow at most 2 padding letters at the end of the input, only if input - // length is divisible by 4 - int len = input.length(); - if (len % 4 == 0) { - if (input[len - 1] == '=') { - input.pop_back(); - if (input[len - 2] == '=') { - input.pop_back(); - } - } - } - // if input contains non-base64url character, return empty string - // Note: padding letter must not be contained - if (std::find_if(input.begin(), input.end(), IsNotBase64UrlChar) != - input.end()) { - return ""; - } - - // base64url is using '-', '_' instead of '+', '/' in base64 string. - std::replace(input.begin(), input.end(), '-', '+'); - std::replace(input.begin(), input.end(), '_', '/'); - - // base64 string should be padded with '=' so as to the length of the string - // is divisible by 4. - switch (input.length() % 4) { - case 0: - break; - case 2: - input += "=="; - break; - case 3: - input += "="; - break; - default: - // * an invalid base64url input. return empty string. - return ""; - } - return Base64::decode(input); -} - -namespace { - const uint8_t *CastToUChar(const std::string &str) { return reinterpret_cast(str.c_str()); } @@ -157,7 +89,7 @@ class EvpPkeyGetter : public WithStatus { EvpPkeyGetter() {} bssl::UniquePtr EvpPkeyFromStr(const std::string &pkey_pem) { - std::string pkey_der = Base64::decode(pkey_pem); + std::string pkey_der = Base64::decodeWithoutPadding(pkey_pem); if (pkey_der == "") { UpdateStatus(Status::PEM_PUBKEY_BAD_BASE64); return nullptr; @@ -244,7 +176,7 @@ Jwt::Jwt(const std::string &jwt) { UpdateStatus(Status::JWT_BAD_FORMAT); return; } - auto jwt_split = StringUtil::splitToken(jwt, "."); + std::vector jwt_split = absl::StrSplit(jwt, "."); if (jwt_split.size() != 3) { UpdateStatus(Status::JWT_BAD_FORMAT); return; @@ -656,7 +588,7 @@ std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, keys->CreateFromPemCore(pkey); break; default: - PANIC("can not reach here"); + abort(); } return keys; } From 15da72e648d831948f2e6f4da00a0a9fad6b97f8 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sun, 21 Jun 2020 13:54:26 +0000 Subject: [PATCH 29/44] stash --- src/envoy/http/jwt_auth/BUILD | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index 7420dc8b18c..453bd2d5289 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -34,8 +34,7 @@ envoy_cc_library( repository = "@envoy", deps = [ "//extensions/common:base64", - "//extensions/common:json_util_wasm", - "//external:ssl", + "//extensions/common:json_util", ], ) From 7b2562ea4a3212a66cec9c0655e8984ed057b571 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sun, 21 Jun 2020 13:58:50 +0000 Subject: [PATCH 30/44] fix --- extensions/access_log_policy/plugin.cc | 2 +- extensions/metadata_exchange/plugin.cc | 2 +- src/envoy/http/jwt_auth/BUILD | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/extensions/access_log_policy/plugin.cc b/extensions/access_log_policy/plugin.cc index 69e20d0298f..1bbc8ecf99b 100644 --- a/extensions/access_log_policy/plugin.cc +++ b/extensions/access_log_policy/plugin.cc @@ -25,7 +25,7 @@ #ifndef NULL_PLUGIN -#include "base64.h" +#include "extensions/common/base64.h" #else diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 824dd89f254..1f29eabad1a 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -24,7 +24,7 @@ #ifndef NULL_PLUGIN -#include "base64.h" +#include "extensions/common/base64.h" #include "extensions/metadata_exchange/declare_property.pb.h" #else diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD index 4737feddd35..841aa2c4326 100644 --- a/src/envoy/http/jwt_auth/BUILD +++ b/src/envoy/http/jwt_auth/BUILD @@ -38,6 +38,18 @@ envoy_cc_library( ], ) +cc_library( + name = "jwt_lib_wasm", + srcs = ["jwt.cc"], + hdrs = ["jwt.h"], + copts = ["-UNULL_PLUGIN"], + deps = [ + "//extensions/common:base64_wasm", + "//extensions/common:json_util_wasm", + "//external:ssl", + ], +) + envoy_cc_library( name = "jwt_authenticator_lib", srcs = [ From 4b0926414d86700f41b3f8362c551a054126232d Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Thu, 25 Jun 2020 15:53:34 +0000 Subject: [PATCH 31/44] stash --- WORKSPACE | 43 ++++--------------- extensions/BUILD | 2 +- extensions/authn/authenticator_base.cc | 2 +- extensions/authn/authenticator_base.h | 2 +- extensions/authn/authenticator_base_test.cc | 2 +- extensions/authn/authn_utils.cc | 2 +- extensions/authn/authn_utils.h | 2 +- extensions/authn/authn_utils_test.cc | 2 +- extensions/authn/filter_context.cc | 2 +- extensions/authn/filter_context.h | 2 +- extensions/authn/filter_context_test.cc | 2 +- .../authn/http_filter_integration_test.cc | 2 +- extensions/authn/http_filter_test.cc | 2 +- extensions/authn/origin_authenticator.cc | 2 +- extensions/authn/origin_authenticator.h | 2 +- extensions/authn/origin_authenticator_test.cc | 2 +- extensions/authn/peer_authenticator.cc | 2 +- extensions/authn/peer_authenticator.h | 2 +- extensions/authn/peer_authenticator_test.cc | 2 +- extensions/authn/test_utils.h | 2 +- src/envoy/utils/BUILD | 37 +++++++++------- src/istio/authn/BUILD | 16 ++----- src/istio/authn/context.proto | 11 +---- wasm.bzl | 31 ------------- 24 files changed, 52 insertions(+), 124 deletions(-) delete mode 100644 wasm.bzl diff --git a/WORKSPACE b/WORKSPACE index 719e4e4b501..f9e7240a1a0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -38,10 +38,10 @@ bind( # 1. Determine SHA256 `wget https://github.com/envoyproxy/envoy-wasm/archive/$COMMIT.tar.gz && sha256sum $COMMIT.tar.gz` # 2. Update .bazelversion, envoy.bazelrc and .bazelrc if needed. # -# Commit time: 6/17/20 -ENVOY_SHA = "5f51ecd39b2462ca3ec5947c5dbb0e492f36fa53" +# Commit time: 6/25/20 +ENVOY_SHA = "55fb4cc96779806d28c20d114327520763cfa602" -ENVOY_SHA256 = "0219971a5dd9e7f5d54ce9300dd9784f0090671770a7f557bbf68a622ea14e7e" +ENVOY_SHA256 = "42931787b8205c2c12c31614f3e2240b931fa6f998551208b8d469b8d003cddf" ENVOY_ORG = "Shikugawa" @@ -49,16 +49,11 @@ ENVOY_REPO = "envoy-wasm" # To override with local envoy, just pass `--override_repository=envoy=/PATH/TO/ENVOY` to Bazel or # persist the option in `user.bazelrc`. -# http_archive( -# name = "envoy", -# sha256 = ENVOY_SHA256, -# strip_prefix = ENVOY_REPO + "-" + ENVOY_SHA, -# url = "https://github.com/" + ENVOY_ORG + "/" + ENVOY_REPO + "/archive/" + ENVOY_SHA + ".tar.gz", -# ) - -local_repository( - name = "envoy", - path = "/home/shimizurei/envoy-wasm", +http_archive( + name = "envoy", + sha256 = ENVOY_SHA256, + strip_prefix = ENVOY_REPO + "-" + ENVOY_SHA, + url = "https://github.com/" + ENVOY_ORG + "/" + ENVOY_REPO + "/archive/" + ENVOY_SHA + ".tar.gz", ) load("@envoy//bazel:api_binding.bzl", "envoy_api_binding") @@ -153,25 +148,3 @@ http_archive( strip_prefix = "abseil-cpp-" + COM_GOOGLE_ABSL_WASM_SHA, url = "https://github.com/abseil/abseil-cpp/archive/" + COM_GOOGLE_ABSL_WASM_SHA + ".tar.gz", ) - -COM_GITHUB_TENCENT_RAPIDJSON = "dfbe1db9da455552f7a9ad5d2aea17dd9d832ac1" - -RAPIDJSON_BUILD = """ -licenses(["notice"]) # Apache 2 - -cc_library( - name = "rapidjson", - hdrs = glob(["include/rapidjson/**/*.h"]), - defines = ["RAPIDJSON_HAS_STDSTRING=1"], - includes = ["include"], - visibility = ["//visibility:public"], -) -""" - -http_archive( - name = "com_github_tencent_rapidjson", - build_file_content = RAPIDJSON_BUILD, - sha256 = "a2faafbc402394df0fa94602df4b5e4befd734aad6bb55dfef46f62fcaf1090b", - strip_prefix = "rapidjson-" + COM_GITHUB_TENCENT_RAPIDJSON, - url = "https://github.com/Tencent/rapidjson/archive/" + COM_GITHUB_TENCENT_RAPIDJSON + ".tar.gz", -) diff --git a/extensions/BUILD b/extensions/BUILD index 9c302b3963d..015a5625005 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -68,7 +68,7 @@ wasm_cc_binary( "//external:authentication_policy_config_cc_proto", "//src/istio/authn:context_proto_cc_wasm", "//src/envoy/utils:filter_names_lib", - "//src/envoy/utils:utils_lib_wasm", + "//src/envoy/utils:utils_lib", "//src/envoy/http/jwt_auth:jwt_lib_wasm", "@com_google_absl_wasm//absl/strings", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", diff --git a/extensions/authn/authenticator_base.cc b/extensions/authn/authenticator_base.cc index 5ec86a597fb..33e3d207171 100644 --- a/extensions/authn/authenticator_base.cc +++ b/extensions/authn/authenticator_base.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/authenticator_base.h b/extensions/authn/authenticator_base.h index d16fc21509a..f980591bc99 100644 --- a/extensions/authn/authenticator_base.h +++ b/extensions/authn/authenticator_base.h @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/authenticator_base_test.cc b/extensions/authn/authenticator_base_test.cc index 82d355661b0..d47942acf56 100644 --- a/extensions/authn/authenticator_base_test.cc +++ b/extensions/authn/authenticator_base_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/authn_utils.cc b/extensions/authn/authn_utils.cc index 93925ac983e..ae2b2a0557f 100644 --- a/extensions/authn/authn_utils.cc +++ b/extensions/authn/authn_utils.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/authn_utils.h b/extensions/authn/authn_utils.h index 47a1fe63495..cf14901e610 100644 --- a/extensions/authn/authn_utils.h +++ b/extensions/authn/authn_utils.h @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/authn_utils_test.cc b/extensions/authn/authn_utils_test.cc index 0420d012257..2a918046557 100644 --- a/extensions/authn/authn_utils_test.cc +++ b/extensions/authn/authn_utils_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/filter_context.cc b/extensions/authn/filter_context.cc index 40b43792532..588a6b6e0d2 100644 --- a/extensions/authn/filter_context.cc +++ b/extensions/authn/filter_context.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/filter_context.h b/extensions/authn/filter_context.h index 243b5c0d17f..65f7a571462 100644 --- a/extensions/authn/filter_context.h +++ b/extensions/authn/filter_context.h @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/filter_context_test.cc b/extensions/authn/filter_context_test.cc index 94db0c38875..7b523213b96 100644 --- a/extensions/authn/filter_context_test.cc +++ b/extensions/authn/filter_context_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/http_filter_integration_test.cc b/extensions/authn/http_filter_integration_test.cc index f2ee0e79ac8..d6d39a98678 100644 --- a/extensions/authn/http_filter_integration_test.cc +++ b/extensions/authn/http_filter_integration_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/http_filter_test.cc b/extensions/authn/http_filter_test.cc index 8c471abbb62..241dfb5d3e4 100644 --- a/extensions/authn/http_filter_test.cc +++ b/extensions/authn/http_filter_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/origin_authenticator.cc b/extensions/authn/origin_authenticator.cc index 5ff286cd712..fb8619b1cc5 100644 --- a/extensions/authn/origin_authenticator.cc +++ b/extensions/authn/origin_authenticator.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/origin_authenticator.h b/extensions/authn/origin_authenticator.h index f43e41526e3..84ee300462c 100644 --- a/extensions/authn/origin_authenticator.h +++ b/extensions/authn/origin_authenticator.h @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/origin_authenticator_test.cc b/extensions/authn/origin_authenticator_test.cc index 47390651fe7..60c9739eef5 100644 --- a/extensions/authn/origin_authenticator_test.cc +++ b/extensions/authn/origin_authenticator_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/peer_authenticator.cc b/extensions/authn/peer_authenticator.cc index 4c0a430acbc..f755e5a6520 100644 --- a/extensions/authn/peer_authenticator.cc +++ b/extensions/authn/peer_authenticator.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/peer_authenticator.h b/extensions/authn/peer_authenticator.h index 4d0dff523c9..30d0df525cb 100644 --- a/extensions/authn/peer_authenticator.h +++ b/extensions/authn/peer_authenticator.h @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/peer_authenticator_test.cc b/extensions/authn/peer_authenticator_test.cc index 56f95f9f980..fc5729abe6a 100644 --- a/extensions/authn/peer_authenticator_test.cc +++ b/extensions/authn/peer_authenticator_test.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/extensions/authn/test_utils.h b/extensions/authn/test_utils.h index 40ffaacc6c5..36b3c2e22b6 100644 --- a/extensions/authn/test_utils.h +++ b/extensions/authn/test_utils.h @@ -1,4 +1,4 @@ -/* Copyright 2018 Istio Authors. All Rights Reserved. +/* Copyright 2020 Istio Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/envoy/utils/BUILD b/src/envoy/utils/BUILD index 835a8be6287..00291a11dc8 100644 --- a/src/envoy/utils/BUILD +++ b/src/envoy/utils/BUILD @@ -64,27 +64,32 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ "//external:mixer_client_config_cc_proto", - "//src/istio/control/http:control_lib", + # "//src/istio/control/http:control_lib", "//src/istio/mixerclient:mixerclient_lib", - "@envoy//source/exe:envoy_common_lib", + "//include/istio/mixerclient:headers_lib", + "//include/istio/utils:headers_lib", + # "@envoy//source/exe:envoy_common_lib", + "@envoy//include/envoy/http:header_map_interface", + "@envoy//include/envoy/network:connection_interface", + "@envoy//include/envoy/event:dispatcher_interface", ], ) cc_library( - name = "utils_lib_wasm", - srcs = [ - "utils.cc" - ], - hdrs = [ - "utils.h" - ], - visibility = ["//visibility:public"], - deps = [ - "//include/istio/mixerclient:headers_lib", - "//include/istio/utils:headers_lib", - "@envoy//include/envoy/network:connection_interface", - "@envoy//include/envoy/http:header_map_interface", - ] + name = "utils_lib_wasm", + srcs = [ + "utils.cc", + ], + hdrs = [ + "utils.h", + ], + visibility = ["//visibility:public"], + deps = [ + "//include/istio/mixerclient:headers_lib", + "//include/istio/utils:headers_lib", + "@envoy//include/envoy/http:header_map_interface", + "@envoy//include/envoy/network:connection_interface", + ], ) envoy_cc_test( diff --git a/src/istio/authn/BUILD b/src/istio/authn/BUILD index d77a397aff0..049bed9a56e 100644 --- a/src/istio/authn/BUILD +++ b/src/istio/authn/BUILD @@ -21,10 +21,6 @@ load( "@envoy//bazel:envoy_build_system.bzl", "envoy_proto_library", ) -load( - "//:wasm.bzl", - "wasm_cc_proto_library", -) envoy_proto_library( name = "context_proto", @@ -32,15 +28,9 @@ envoy_proto_library( external_deps = ["well_known_protos"], ) -proto_library( - name = "context_proto_wasm", - srcs = ["context.proto"], +cc_proto_library( + name = "context_proto_cc_wasm", deps = [ - "@com_google_protobuf//:struct_proto", + ":context_proto", ], ) - -wasm_cc_proto_library( - name = "context_proto_cc_wasm", - deps = ":context_proto_wasm", -) diff --git a/src/istio/authn/context.proto b/src/istio/authn/context.proto index 26b3d8ea3f0..3379520b39a 100644 --- a/src/istio/authn/context.proto +++ b/src/istio/authn/context.proto @@ -83,13 +83,4 @@ message Result { // Origin authentication supports only JWT at the moment, so we can use // JwtPayload for origin authenticated attributes. JwtPayload origin = 3; -} - -// This is a transplantation of envoy_api Metadata. We can't build envoy_api -// with wasm because one of dependencies is required google/re2 , which depends -// on pthread. Now emscripten doesn't have pthread support. -message Metadata { - // Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* - // namespace is reserved for Envoy's built-in filters. - map filter_metadata = 1; -} +} \ No newline at end of file diff --git a/wasm.bzl b/wasm.bzl deleted file mode 100644 index fe4e2f892dd..00000000000 --- a/wasm.bzl +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2020 Istio Authors. All Rights Reserved. -# -# Licensed 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. -# -################################################################################ -# - -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library") - -def wasm_cc_proto_library(name, deps): - proto_name = "_proto_" + name - - cc_proto_library( - name = proto_name, - deps = [deps], - ) - - native.cc_library( - name = name, - deps = [":" + proto_name], - ) From e619be3a195e861f1dc1361e5f01ea3257359b64 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 7 Jul 2020 20:09:57 +0900 Subject: [PATCH 32/44] fix --- extensions/common/BUILD | 9 --------- 1 file changed, 9 deletions(-) diff --git a/extensions/common/BUILD b/extensions/common/BUILD index af0acef2826..f4b0d1b5eba 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -192,12 +192,3 @@ envoy_cc_library( repository = "@envoy", visibility = ["//visibility:public"], ) - -cc_library( - name = "base64_wasm", - hdrs = [ - "base64.h", - ], - copts = ["-UNULL_PLUGIN"], - visibility = ["//visibility:public"], -) From f725a034bcbd34f8cd184af991d3a439c2333762 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 7 Jul 2020 21:14:02 +0900 Subject: [PATCH 33/44] fix --- extensions/BUILD | 1 + extensions/common/BUILD | 12 ++++++++++++ extensions/metadata_exchange/plugin.cc | 2 +- src/envoy/http/mixer/filter.cc | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/extensions/BUILD b/extensions/BUILD index eef0b787533..52a8d2ddfff 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -62,6 +62,7 @@ wasm_cc_binary( ], copts = ["-UNULL_PLUGIN"], deps = [ + "//extensions/common:base64_wasm", "//extensions/common:json_util_wasm", "//extensions/common:node_info_fb_cc", "//extensions/metadata_exchange:declare_property_proto_cc", diff --git a/extensions/common/BUILD b/extensions/common/BUILD index f4b0d1b5eba..37fadf32b03 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -192,3 +192,15 @@ envoy_cc_library( repository = "@envoy", visibility = ["//visibility:public"], ) + +# TODO(shikugawa): These redundant build strategy is caused by the build problem of abseil. +# This problem is resolved on https://github.com/abseil/abseil-cpp/pull/721 +# so we can destroy after that merged into upstream +cc_library( + name = "base64_wasm", + hdrs = [ + "base64.h", + ], + copts = ["-UNULL_PLUGIN"], + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 860d966fe57..2debf5acf29 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -24,7 +24,7 @@ #ifndef NULL_PLUGIN -#include "base64.h" +#include "extensions/common/base64.h" #include "extensions/metadata_exchange/declare_property.pb.h" #else diff --git a/src/envoy/http/mixer/filter.cc b/src/envoy/http/mixer/filter.cc index f5e074419f8..6afedb1c280 100644 --- a/src/envoy/http/mixer/filter.cc +++ b/src/envoy/http/mixer/filter.cc @@ -113,7 +113,7 @@ FilterTrailersStatus Filter::decodeTrailers(RequestTrailerMap& trailers) { void Filter::UpdateHeaders( HeaderMap& headers, const ::google::protobuf::RepeatedPtrField< ::istio::mixer::v1::HeaderOperation>& operations) { - for (auto const iter : operations) { + for (auto const& iter : operations) { switch (iter.operation()) { case ::istio::mixer::v1::HeaderOperation_Operation_REPLACE: headers.remove(LowerCaseString(iter.name())); From 972bc3eeff1d7cd4c29ef8b0dfb199032cf98448 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 7 Jul 2020 21:19:02 +0900 Subject: [PATCH 34/44] list --- extensions/common/BUILD | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/common/BUILD b/extensions/common/BUILD index 37fadf32b03..d3fc08309d9 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -197,10 +197,10 @@ envoy_cc_library( # This problem is resolved on https://github.com/abseil/abseil-cpp/pull/721 # so we can destroy after that merged into upstream cc_library( - name = "base64_wasm", - hdrs = [ - "base64.h", - ], - copts = ["-UNULL_PLUGIN"], - visibility = ["//visibility:public"], -) \ No newline at end of file + name = "base64_wasm", + hdrs = [ + "base64.h", + ], + copts = ["-UNULL_PLUGIN"], + visibility = ["//visibility:public"], +) From 909546c166d9316a8eb741de8da98e6164c6fc8d Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 8 Jul 2020 16:16:06 +0900 Subject: [PATCH 35/44] fix --- extensions/BUILD | 1 - src/envoy/http/jwt_auth/jwt.cc | 73 +--------------------------------- src/envoy/http/jwt_auth/jwt.h | 2 - 3 files changed, 2 insertions(+), 74 deletions(-) diff --git a/extensions/BUILD b/extensions/BUILD index 52a8d2ddfff..59d96b47492 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -50,7 +50,6 @@ wasm_cc_binary( wasm_cc_binary( name = "metadata_exchange.wasm", srcs = [ - "//extensions/common:base64.h", "//extensions/common:context.cc", "//extensions/common:context.h", "//extensions/common:proto_util.cc", diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc index 6556ff7d78f..4bc587498eb 100644 --- a/src/envoy/http/jwt_auth/jwt.cc +++ b/src/envoy/http/jwt_auth/jwt.cc @@ -24,8 +24,8 @@ #include #include "common/common/assert.h" -#include "common/common/base64.h" #include "common/common/utility.h" +#include "extensions/common/base64.h" #include "openssl/bn.h" #include "openssl/ecdsa.h" #include "openssl/evp.h" @@ -70,75 +70,6 @@ std::string StatusToString(Status status) { namespace { -// Conversion table is taken from -// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c -// -// and modified the position of 62 ('+' to '-') and 63 ('/' to '_') -const uint8_t kReverseLookupTableBase64Url[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, - 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, - 63, 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64}; - -bool IsNotBase64UrlChar(int8_t c) { - return kReverseLookupTableBase64Url[static_cast(c)] & 64; -} - -} // namespace - -std::string Base64UrlDecode(std::string input) { - // allow at most 2 padding letters at the end of the input, only if input - // length is divisible by 4 - int len = input.length(); - if (len % 4 == 0) { - if (input[len - 1] == '=') { - input.pop_back(); - if (input[len - 2] == '=') { - input.pop_back(); - } - } - } - // if input contains non-base64url character, return empty string - // Note: padding letter must not be contained - if (std::find_if(input.begin(), input.end(), IsNotBase64UrlChar) != - input.end()) { - return ""; - } - - // base64url is using '-', '_' instead of '+', '/' in base64 string. - std::replace(input.begin(), input.end(), '-', '+'); - std::replace(input.begin(), input.end(), '_', '/'); - - // base64 string should be padded with '=' so as to the length of the string - // is divisible by 4. - switch (input.length() % 4) { - case 0: - break; - case 2: - input += "=="; - break; - case 3: - input += "="; - break; - default: - // * an invalid base64url input. return empty string. - return ""; - } - return Base64::decode(input); -} - -namespace { - const uint8_t *CastToUChar(const std::string &str) { return reinterpret_cast(str.c_str()); } @@ -157,7 +88,7 @@ class EvpPkeyGetter : public WithStatus { EvpPkeyGetter() {} bssl::UniquePtr EvpPkeyFromStr(const std::string &pkey_pem) { - std::string pkey_der = Base64::decode(pkey_pem); + std::string pkey_der = Base64::decodeWithoutPadding(pkey_pem); if (pkey_der == "") { UpdateStatus(Status::PEM_PUBKEY_BAD_BASE64); return nullptr; diff --git a/src/envoy/http/jwt_auth/jwt.h b/src/envoy/http/jwt_auth/jwt.h index 48bc23f10d8..2d64b40e702 100644 --- a/src/envoy/http/jwt_auth/jwt.h +++ b/src/envoy/http/jwt_auth/jwt.h @@ -110,8 +110,6 @@ enum class Status { std::string StatusToString(Status status); -std::string Base64UrlDecode(std::string input); - // Base class to keep the status that represents "OK" or the first failure // reason class WithStatus { From eba563194a51e8502f52a37c4ca5fc8d7f9930a8 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Sat, 11 Jul 2020 02:07:08 +0900 Subject: [PATCH 36/44] fix --- extensions/common/base64.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/extensions/common/base64.h b/extensions/common/base64.h index 6ee7745e944..0f8d6aa29ab 100644 --- a/extensions/common/base64.h +++ b/extensions/common/base64.h @@ -49,9 +49,22 @@ constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; +// Conversion table is taken from +// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c +// +// and modified the position of 62 ('+' to '-') and 63 ('/' to '_') constexpr unsigned char REVERSE_LOOKUP_TABLE_BASE64_URL[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, 63, 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 -}; + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, 63, + 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; // clang-format on inline bool decodeBase(const uint8_t cur_char, uint64_t pos, std::string& ret, From 986742b6df39c9218d9ce5882de505cf2b88a8ab Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 14 Jul 2020 14:32:25 +0900 Subject: [PATCH 37/44] fix --- src/envoy/http/jwt_auth/BUILD | 145 -------- src/envoy/http/jwt_auth/jwt.cc | 592 --------------------------------- src/envoy/http/jwt_auth/jwt.h | 298 ----------------- 3 files changed, 1035 deletions(-) delete mode 100644 src/envoy/http/jwt_auth/BUILD delete mode 100644 src/envoy/http/jwt_auth/jwt.cc delete mode 100644 src/envoy/http/jwt_auth/jwt.h diff --git a/src/envoy/http/jwt_auth/BUILD b/src/envoy/http/jwt_auth/BUILD deleted file mode 100644 index 4737feddd35..00000000000 --- a/src/envoy/http/jwt_auth/BUILD +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright 2017 Istio Authors. All Rights Reserved. -# -# Licensed 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. -# -################################################################################ -# - -package(default_visibility = ["//visibility:public"]) - -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_cc_binary", - "envoy_cc_library", - "envoy_cc_test", -) - -envoy_cc_library( - name = "jwt_lib", - srcs = ["jwt.cc"], - hdrs = ["jwt.h"], - external_deps = [ - "ssl", - ], - repository = "@envoy", - deps = [ - "//extensions/common:base64", - "//extensions/common:json_util", - ], -) - -envoy_cc_library( - name = "jwt_authenticator_lib", - srcs = [ - "jwt_authenticator.cc", - "token_extractor.cc", - ], - hdrs = [ - "auth_store.h", - "jwt_authenticator.h", - "pubkey_cache.h", - "token_extractor.h", - ], - repository = "@envoy", - deps = [ - ":jwt_lib", - "//external:jwt_auth_config_cc_proto", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "http_filter_lib", - srcs = [ - "http_filter.cc", - ], - hdrs = [ - "http_filter.h", - ], - repository = "@envoy", - deps = [ - ":jwt_authenticator_lib", - "//src/envoy/utils:filter_names_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_library( - name = "http_filter_factory", - srcs = ["http_filter_factory.cc"], - repository = "@envoy", - deps = [ - ":http_filter_lib", - "//src/envoy/utils:filter_names_lib", - "@envoy//source/exe:envoy_common_lib", - ], -) - -envoy_cc_test( - name = "http_filter_integration_test", - srcs = [":integration_test/http_filter_integration_test.cc"], - data = [ - "integration_test/envoy.conf.jwk", - "integration_test/envoy_allow_missing_or_failed_jwt.conf.jwk", - ], - repository = "@envoy", - deps = [ - ":http_filter_factory", - ":jwt_lib", - "@envoy//test/integration:http_integration_lib", - "@envoy//test/integration:integration_lib", - ], -) - -envoy_cc_test( - name = "jwt_test", - srcs = [ - "jwt_test.cc", - ], - data = [], - repository = "@envoy", - deps = [ - ":jwt_lib", - "@envoy//source/exe:envoy_common_lib", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "jwt_authenticator_test", - srcs = [ - "jwt_authenticator_test.cc", - ], - data = [], - repository = "@envoy", - deps = [ - ":jwt_authenticator_lib", - "@envoy//source/exe:envoy_common_lib", - "@envoy//test/mocks/upstream:upstream_mocks", - "@envoy//test/test_common:utility_lib", - ], -) - -envoy_cc_test( - name = "token_extractor_test", - srcs = [ - "token_extractor_test.cc", - ], - data = [], - repository = "@envoy", - deps = [ - ":jwt_authenticator_lib", - "@envoy//source/exe:envoy_common_lib", - "@envoy//test/test_common:utility_lib", - ], -) diff --git a/src/envoy/http/jwt_auth/jwt.cc b/src/envoy/http/jwt_auth/jwt.cc deleted file mode 100644 index 4bc587498eb..00000000000 --- a/src/envoy/http/jwt_auth/jwt.cc +++ /dev/null @@ -1,592 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#include "jwt.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "common/common/assert.h" -#include "common/common/utility.h" -#include "extensions/common/base64.h" -#include "openssl/bn.h" -#include "openssl/ecdsa.h" -#include "openssl/evp.h" -#include "openssl/rsa.h" -#include "openssl/sha.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -std::string StatusToString(Status status) { - static std::map table = { - {Status::OK, "OK"}, - {Status::JWT_MISSED, "Required JWT token is missing"}, - {Status::JWT_EXPIRED, "JWT is expired"}, - {Status::JWT_BAD_FORMAT, "JWT_BAD_FORMAT"}, - {Status::JWT_HEADER_PARSE_ERROR, "JWT_HEADER_PARSE_ERROR"}, - {Status::JWT_HEADER_NO_ALG, "JWT_HEADER_NO_ALG"}, - {Status::JWT_HEADER_BAD_ALG, "JWT_HEADER_BAD_ALG"}, - {Status::JWT_SIGNATURE_PARSE_ERROR, "JWT_SIGNATURE_PARSE_ERROR"}, - {Status::JWT_INVALID_SIGNATURE, "JWT_INVALID_SIGNATURE"}, - {Status::JWT_PAYLOAD_PARSE_ERROR, "JWT_PAYLOAD_PARSE_ERROR"}, - {Status::JWT_HEADER_BAD_KID, "JWT_HEADER_BAD_KID"}, - {Status::JWT_UNKNOWN_ISSUER, "Unknown issuer"}, - {Status::JWK_PARSE_ERROR, "JWK_PARSE_ERROR"}, - {Status::JWK_NO_KEYS, "JWK_NO_KEYS"}, - {Status::JWK_BAD_KEYS, "JWK_BAD_KEYS"}, - {Status::JWK_NO_VALID_PUBKEY, "JWK_NO_VALID_PUBKEY"}, - {Status::KID_ALG_UNMATCH, "KID_ALG_UNMATCH"}, - {Status::ALG_NOT_IMPLEMENTED, "ALG_NOT_IMPLEMENTED"}, - {Status::PEM_PUBKEY_BAD_BASE64, "PEM_PUBKEY_BAD_BASE64"}, - {Status::PEM_PUBKEY_PARSE_ERROR, "PEM_PUBKEY_PARSE_ERROR"}, - {Status::JWK_RSA_PUBKEY_PARSE_ERROR, "JWK_RSA_PUBKEY_PARSE_ERROR"}, - {Status::FAILED_CREATE_EC_KEY, "FAILED_CREATE_EC_KEY"}, - {Status::JWK_EC_PUBKEY_PARSE_ERROR, "JWK_EC_PUBKEY_PARSE_ERROR"}, - {Status::FAILED_CREATE_ECDSA_SIGNATURE, "FAILED_CREATE_ECDSA_SIGNATURE"}, - {Status::AUDIENCE_NOT_ALLOWED, "Audience doesn't match"}, - {Status::FAILED_FETCH_PUBKEY, "Failed to fetch public key"}, - }; - return table[status]; -} - -namespace { - -const uint8_t *CastToUChar(const std::string &str) { - return reinterpret_cast(str.c_str()); -} - -// Class to create EVP_PKEY object from string of public key, formatted in PEM -// or JWKs. -// If it failed, status_ holds the failure reason. -// -// Usage example: -// EvpPkeyGetter e; -// bssl::UniquePtr pkey = -// e.EvpPkeyFromStr(pem_formatted_public_key); -// (You can use EvpPkeyFromJwkRSA() or EcKeyFromJwkEC() for JWKs) -class EvpPkeyGetter : public WithStatus { - public: - EvpPkeyGetter() {} - - bssl::UniquePtr EvpPkeyFromStr(const std::string &pkey_pem) { - std::string pkey_der = Base64::decodeWithoutPadding(pkey_pem); - if (pkey_der == "") { - UpdateStatus(Status::PEM_PUBKEY_BAD_BASE64); - return nullptr; - } - auto rsa = bssl::UniquePtr( - RSA_public_key_from_bytes(CastToUChar(pkey_der), pkey_der.length())); - if (!rsa) { - UpdateStatus(Status::PEM_PUBKEY_PARSE_ERROR); - } - return EvpPkeyFromRsa(rsa.get()); - } - - bssl::UniquePtr EvpPkeyFromJwkRSA(const std::string &n, - const std::string &e) { - return EvpPkeyFromRsa(RsaFromJwk(n, e).get()); - } - - bssl::UniquePtr EcKeyFromJwkEC(const std::string &x, - const std::string &y) { - bssl::UniquePtr ec_key( - EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); - if (!ec_key) { - UpdateStatus(Status::FAILED_CREATE_EC_KEY); - return nullptr; - } - bssl::UniquePtr bn_x = BigNumFromBase64UrlString(x); - bssl::UniquePtr bn_y = BigNumFromBase64UrlString(y); - if (!bn_x || !bn_y) { - // EC public key field is missing or has parse error. - UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); - return nullptr; - } - - if (EC_KEY_set_public_key_affine_coordinates(ec_key.get(), bn_x.get(), - bn_y.get()) == 0) { - UpdateStatus(Status::JWK_EC_PUBKEY_PARSE_ERROR); - return nullptr; - } - return ec_key; - } - - private: - // In the case where rsa is nullptr, UpdateStatus() should be called - // appropriately elsewhere. - bssl::UniquePtr EvpPkeyFromRsa(RSA *rsa) { - if (!rsa) { - return nullptr; - } - bssl::UniquePtr key(EVP_PKEY_new()); - EVP_PKEY_set1_RSA(key.get(), rsa); - return key; - } - - bssl::UniquePtr BigNumFromBase64UrlString(const std::string &s) { - std::string s_decoded = Base64UrlDecode(s); - if (s_decoded == "") { - return nullptr; - } - return bssl::UniquePtr( - BN_bin2bn(CastToUChar(s_decoded), s_decoded.length(), NULL)); - }; - - bssl::UniquePtr RsaFromJwk(const std::string &n, const std::string &e) { - bssl::UniquePtr rsa(RSA_new()); - // It crash if RSA object couldn't be created. - assert(rsa); - - rsa->n = BigNumFromBase64UrlString(n).release(); - rsa->e = BigNumFromBase64UrlString(e).release(); - if (!rsa->n || !rsa->e) { - // RSA public key field is missing or has parse error. - UpdateStatus(Status::JWK_RSA_PUBKEY_PARSE_ERROR); - return nullptr; - } - return rsa; - } -}; - -} // namespace - -Jwt::Jwt(const std::string &jwt) { - // jwt must have exactly 2 dots - if (std::count(jwt.begin(), jwt.end(), '.') != 2) { - UpdateStatus(Status::JWT_BAD_FORMAT); - return; - } - auto jwt_split = StringUtil::splitToken(jwt, "."); - if (jwt_split.size() != 3) { - UpdateStatus(Status::JWT_BAD_FORMAT); - return; - } - - // Parse header json - header_str_base64url_ = std::string(jwt_split[0].begin(), jwt_split[0].end()); - header_str_ = Base64UrlDecode(header_str_base64url_); - - auto result = Wasm::Common::JsonParse(header_str_); - if (!result.has_value()) { - UpdateStatus(Status::JWT_HEADER_PARSE_ERROR); - return; - } - header_ = result.value(); - - // Header should contain "alg". - if (header_.find("alg") == header_.end()) { - UpdateStatus(Status::JWT_HEADER_NO_ALG); - return; - } - - auto alg_field = Wasm::Common::JsonGetField(header_, "alg"); - if (alg_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - UpdateStatus(Status::JWT_HEADER_BAD_ALG); - return; - } - alg_ = alg_field.value(); - - if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && - alg_ != "RS512") { - UpdateStatus(Status::ALG_NOT_IMPLEMENTED); - return; - } - - // Header may contain "kid", which should be a string if exists. - auto kid_field = Wasm::Common::JsonGetField(header_, "kid"); - if (kid_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - if (kid_field.detail() == - Wasm::Common::JsonParserResultDetail::TYPE_ERROR) { - UpdateStatus(Status::JWT_HEADER_BAD_KID); - return; - } else if (kid_field.detail() == - Wasm::Common::JsonParserResultDetail::OUT_OF_RANGE) { - kid_ = ""; - } else { - return; - } - } else { - kid_ = kid_field.value(); - } - - // Parse payload json - payload_str_base64url_ = - std::string(jwt_split[1].begin(), jwt_split[1].end()); - payload_str_ = Base64UrlDecode(payload_str_base64url_); - result = Wasm::Common::JsonParse(payload_str_); - if (!result.has_value()) { - UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); - return; - } - payload_ = result.value(); - - iss_ = Wasm::Common::JsonGetField(payload_, "iss").value_or(""); - sub_ = Wasm::Common::JsonGetField(payload_, "sub").value_or(""); - exp_ = Wasm::Common::JsonGetField(payload_, "exp").value_or(0); - - // "aud" can be either string array or string. - // Try as string array, read it as empty array if doesn't exist. - if (!Wasm::Common::JsonArrayIterate( - payload_, "aud", [&](const Wasm::Common::JsonObject &obj) -> bool { - auto str_obj_result = Wasm::Common::JsonValueAs(obj); - if (str_obj_result.second != - Wasm::Common::JsonParserResultDetail::OK) { - return false; - } - aud_.emplace_back(str_obj_result.first.value()); - return true; - })) { - auto aud_field = Wasm::Common::JsonGetField(payload_, "aud"); - if (aud_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - UpdateStatus(Status::JWT_PAYLOAD_PARSE_ERROR); - return; - } - aud_.emplace_back(aud_field.value()); - } - - // Set up signature - signature_ = - Base64UrlDecode(std::string(jwt_split[2].begin(), jwt_split[2].end())); - if (signature_ == "") { - // Signature is a bad Base64url input. - UpdateStatus(Status::JWT_SIGNATURE_PARSE_ERROR); - return; - } -} - -bool Verifier::VerifySignatureRSA(EVP_PKEY *key, const EVP_MD *md, - const uint8_t *signature, - size_t signature_len, - const uint8_t *signed_data, - size_t signed_data_len) { - bssl::UniquePtr md_ctx(EVP_MD_CTX_create()); - - if (EVP_DigestVerifyInit(md_ctx.get(), nullptr, md, nullptr, key) != 1) { - return false; - } - if (EVP_DigestVerifyUpdate(md_ctx.get(), signed_data, signed_data_len) != 1) { - return false; - } - return (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) == 1); -} - -bool Verifier::VerifySignatureRSA(EVP_PKEY *key, const EVP_MD *md, - const std::string &signature, - const std::string &signed_data) { - return VerifySignatureRSA(key, md, CastToUChar(signature), signature.length(), - CastToUChar(signed_data), signed_data.length()); -} - -bool Verifier::VerifySignatureEC(EC_KEY *key, const uint8_t *signature, - size_t signature_len, - const uint8_t *signed_data, - size_t signed_data_len) { - // ES256 signature should be 64 bytes. - if (signature_len != 2 * 32) { - return false; - } - - uint8_t digest[SHA256_DIGEST_LENGTH]; - SHA256(signed_data, signed_data_len, digest); - - bssl::UniquePtr ecdsa_sig(ECDSA_SIG_new()); - if (!ecdsa_sig) { - UpdateStatus(Status::FAILED_CREATE_ECDSA_SIGNATURE); - return false; - } - - BN_bin2bn(signature, 32, ecdsa_sig->r); - BN_bin2bn(signature + 32, 32, ecdsa_sig->s); - return (ECDSA_do_verify(digest, SHA256_DIGEST_LENGTH, ecdsa_sig.get(), key) == - 1); -} - -bool Verifier::VerifySignatureEC(EC_KEY *key, const std::string &signature, - const std::string &signed_data) { - return VerifySignatureEC(key, CastToUChar(signature), signature.length(), - CastToUChar(signed_data), signed_data.length()); -} - -bool Verifier::Verify(const Jwt &jwt, const Pubkeys &pubkeys) { - // If JWT status is not OK, inherits its status and return false. - if (jwt.GetStatus() != Status::OK) { - UpdateStatus(jwt.GetStatus()); - return false; - } - - // If pubkeys status is not OK, inherits its status and return false. - if (pubkeys.GetStatus() != Status::OK) { - UpdateStatus(pubkeys.GetStatus()); - return false; - } - - std::string signed_data = - jwt.header_str_base64url_ + '.' + jwt.payload_str_base64url_; - bool kid_alg_matched = false; - for (auto &pubkey : pubkeys.keys_) { - // If kid is specified in JWT, JWK with the same kid is used for - // verification. - // If kid is not specified in JWT, try all JWK. - if (jwt.kid_ != "" && pubkey->kid_specified_ && pubkey->kid_ != jwt.kid_) { - continue; - } - - // The same alg must be used. - if (pubkey->alg_specified_ && pubkey->alg_ != jwt.alg_) { - continue; - } - kid_alg_matched = true; - - if (pubkey->kty_ == "EC" && - VerifySignatureEC(pubkey->ec_key_.get(), jwt.signature_, signed_data)) { - // Verification succeeded. - return true; - } else if (pubkey->pem_format_ || pubkey->kty_ == "RSA") { - const EVP_MD *md; - if (jwt.alg_ == "RS384") { - md = EVP_sha384(); - } else if (jwt.alg_ == "RS512") { - md = EVP_sha512(); - } else { - // default to SHA256 - md = EVP_sha256(); - } - if (VerifySignatureRSA(pubkey->evp_pkey_.get(), md, jwt.signature_, - signed_data)) { - // Verification succeeded. - return true; - } - } - } - // Verification failed. - if (kid_alg_matched) { - UpdateStatus(Status::JWT_INVALID_SIGNATURE); - } else { - UpdateStatus(Status::KID_ALG_UNMATCH); - } - return false; -} - -// Returns the parsed header. -Wasm::Common::JsonObject &Jwt::Header() { return header_; } - -const std::string &Jwt::HeaderStr() { return header_str_; } -const std::string &Jwt::HeaderStrBase64Url() { return header_str_base64url_; } -const std::string &Jwt::Alg() { return alg_; } -const std::string &Jwt::Kid() { return kid_; } - -// Returns payload JSON. -Wasm::Common::JsonObject &Jwt::Payload() { return payload_; } - -const std::string &Jwt::PayloadStr() { return payload_str_; } -const std::string &Jwt::PayloadStrBase64Url() { return payload_str_base64url_; } -const std::string &Jwt::Iss() { return iss_; } -const std::vector &Jwt::Aud() { return aud_; } -const std::string &Jwt::Sub() { return sub_; } -int64_t Jwt::Exp() { return exp_; } - -void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) { - keys_.clear(); - std::unique_ptr key_ptr(new Pubkey()); - EvpPkeyGetter e; - key_ptr->evp_pkey_ = e.EvpPkeyFromStr(pkey_pem); - key_ptr->pem_format_ = true; - UpdateStatus(e.GetStatus()); - if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(key_ptr)); - } -} - -void Pubkeys::CreateFromJwksCore(const std::string &pkey_jwks) { - keys_.clear(); - - Wasm::Common::JsonObject jwks_json; - auto result = Wasm::Common::JsonParse(pkey_jwks); - if (!result.has_value()) { - UpdateStatus(Status::JWK_PARSE_ERROR); - return; - } - jwks_json = result.value(); - - std::vector> key_refs; - - if (jwks_json.find("keys") == jwks_json.end()) { - UpdateStatus(Status::JWK_NO_KEYS); - return; - } - - if (!Wasm::Common::JsonArrayIterate( - jwks_json, "keys", [&](const Wasm::Common::JsonObject &obj) -> bool { - key_refs.emplace_back( - std::reference_wrapper(obj)); - return true; - })) { - UpdateStatus(Status::JWK_BAD_KEYS); - return; - } - - for (auto &key_ref : key_refs) { - if (!ExtractPubkeyFromJwk(key_ref.get())) { - continue; - } - } - - if (keys_.size() == 0) { - UpdateStatus(Status::JWK_NO_VALID_PUBKEY); - } -} - -bool Pubkeys::ExtractPubkeyFromJwk(const Wasm::Common::JsonObject &jwk_json) { - // Check "kty" parameter, it should exist. - // https://tools.ietf.org/html/rfc7517#section-4.1 - // If "kty" is missing, getString throws an exception. - auto kty_field = Wasm::Common::JsonGetField(jwk_json, "kty"); - if (kty_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - return false; - } - - // Extract public key according to "kty" value. - // https://tools.ietf.org/html/rfc7518#section-6.1 - if (kty_field.value() == "EC") { - return ExtractPubkeyFromJwkEC(jwk_json); - } else if (kty_field.value() == "RSA") { - return ExtractPubkeyFromJwkRSA(jwk_json); - } - - return false; -} - -bool Pubkeys::ExtractPubkeyFromJwkRSA( - const Wasm::Common::JsonObject &jwk_json) { - std::unique_ptr pubkey(new Pubkey()); - std::string n_str, e_str; - - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - auto kid_field = Wasm::Common::JsonGetField(jwk_json, "kid"); - if (kid_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { - pubkey->kid_ = kid_field.value(); - pubkey->kid_specified_ = true; - } - - auto alg_field = Wasm::Common::JsonGetField(jwk_json, "alg"); - if (alg_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { - // Allow only "RS" prefixed algorithms. - // https://tools.ietf.org/html/rfc7518#section-3.1 - if (!(alg_field.value() == "RS256" || alg_field.value() == "RS384" || - alg_field.value() == "RS512")) { - return false; - } - pubkey->alg_ = alg_field.value(); - pubkey->alg_specified_ = true; - } - - auto pubkey_kty_field = - Wasm::Common::JsonGetField(jwk_json, "kty"); - assert(pubkey_kty_field.detail() == Wasm::Common::JsonParserResultDetail::OK); - pubkey->kty_ = pubkey_kty_field.value(); - auto n_str_field = Wasm::Common::JsonGetField(jwk_json, "n"); - auto e_str_field = Wasm::Common::JsonGetField(jwk_json, "e"); - if (n_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK || - e_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - return false; - } - n_str = n_str_field.value(); - e_str = e_str_field.value(); - - EvpPkeyGetter e; - pubkey->evp_pkey_ = e.EvpPkeyFromJwkRSA(n_str, e_str); - if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(pubkey)); - } else { - UpdateStatus(e.GetStatus()); - } - - return true; -} - -bool Pubkeys::ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject &jwk_json) { - std::unique_ptr pubkey(new Pubkey()); - std::string x_str, y_str; - - // "kid" and "alg" are optional, if they do not exist, set them to "". - // https://tools.ietf.org/html/rfc7517#page-8 - auto kid_field = Wasm::Common::JsonGetField(jwk_json, "kid"); - if (kid_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { - pubkey->kid_ = kid_field.value(); - pubkey->kid_specified_ = true; - } - - auto alg_field = Wasm::Common::JsonGetField(jwk_json, "alg"); - if (alg_field.detail() == Wasm::Common::JsonParserResultDetail::OK) { - // Allow only "RS" prefixed algorithms. - // https://tools.ietf.org/html/rfc7518#section-3.1 - if (alg_field.value() != "ES256") { - return false; - } - pubkey->alg_ = alg_field.value(); - pubkey->alg_specified_ = true; - } - - auto pubkey_kty_field = - Wasm::Common::JsonGetField(jwk_json, "kty"); - assert(pubkey_kty_field.detail() == Wasm::Common::JsonParserResultDetail::OK); - pubkey->kty_ = pubkey_kty_field.value(); - auto x_str_field = Wasm::Common::JsonGetField(jwk_json, "x"); - auto y_str_field = Wasm::Common::JsonGetField(jwk_json, "y"); - if (x_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK || - y_str_field.detail() != Wasm::Common::JsonParserResultDetail::OK) { - return false; - } - x_str = x_str_field.value(); - y_str = y_str_field.value(); - - EvpPkeyGetter e; - pubkey->ec_key_ = e.EcKeyFromJwkEC(x_str, y_str); - if (e.GetStatus() == Status::OK) { - keys_.push_back(std::move(pubkey)); - } else { - UpdateStatus(e.GetStatus()); - } - - return true; -} - -std::unique_ptr Pubkeys::CreateFrom(const std::string &pkey, - Type type) { - std::unique_ptr keys(new Pubkeys()); - switch (type) { - case Type::JWKS: - keys->CreateFromJwksCore(pkey); - break; - case Type::PEM: - keys->CreateFromPemCore(pkey); - break; - default: - PANIC("can not reach here"); - } - return keys; -} - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy diff --git a/src/envoy/http/jwt_auth/jwt.h b/src/envoy/http/jwt_auth/jwt.h deleted file mode 100644 index 2d64b40e702..00000000000 --- a/src/envoy/http/jwt_auth/jwt.h +++ /dev/null @@ -1,298 +0,0 @@ -/* Copyright 2017 Istio Authors. All Rights Reserved. - * - * Licensed 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. - */ - -#pragma once - -#include -#include -#include -#include - -#include "extensions/common/json_util.h" -#include "openssl/ec.h" -#include "openssl/evp.h" - -namespace Envoy { -namespace Http { -namespace JwtAuth { - -enum class Status { - OK = 0, - - // JWT token is required. - JWT_MISSED = 1, - - // Token expired. - JWT_EXPIRED = 2, - - // Given JWT is not in the form of Header.Payload.Signature - JWT_BAD_FORMAT = 3, - - // Header is an invalid Base64url input or an invalid JSON. - JWT_HEADER_PARSE_ERROR = 4, - - // Header does not have "alg". - JWT_HEADER_NO_ALG = 5, - - // "alg" in the header is not a string. - JWT_HEADER_BAD_ALG = 6, - - // Signature is an invalid Base64url input. - JWT_SIGNATURE_PARSE_ERROR = 7, - - // Signature Verification failed (= Failed in DigestVerifyFinal()) - JWT_INVALID_SIGNATURE = 8, - - // Signature is valid but payload is an invalid Base64url input or an invalid - // JSON. - JWT_PAYLOAD_PARSE_ERROR = 9, - - // "kid" in the JWT header is not a string. - JWT_HEADER_BAD_KID = 10, - - // Issuer is not configured. - JWT_UNKNOWN_ISSUER = 11, - - // JWK is an invalid JSON. - JWK_PARSE_ERROR = 12, - - // JWK does not have "keys". - JWK_NO_KEYS = 13, - - // "keys" in JWK is not an array. - JWK_BAD_KEYS = 14, - - // There are no valid public key in given JWKs. - JWK_NO_VALID_PUBKEY = 15, - - // There is no key the kid and the alg of which match those of the given JWT. - KID_ALG_UNMATCH = 16, - - // Value of "alg" in the header is invalid. - ALG_NOT_IMPLEMENTED = 17, - - // Given PEM formatted public key is an invalid Base64 input. - PEM_PUBKEY_BAD_BASE64 = 18, - - // A parse error on PEM formatted public key happened. - PEM_PUBKEY_PARSE_ERROR = 19, - - // "n" or "e" field of a JWK has a parse error or is missing. - JWK_RSA_PUBKEY_PARSE_ERROR = 20, - - // Failed to create a EC_KEY object. - FAILED_CREATE_EC_KEY = 21, - - // "x" or "y" field of a JWK has a parse error or is missing. - JWK_EC_PUBKEY_PARSE_ERROR = 22, - - // Failed to create ECDSA_SIG object. - FAILED_CREATE_ECDSA_SIGNATURE = 23, - - // Audience is not allowed. - AUDIENCE_NOT_ALLOWED = 24, - - // Failed to fetch public key - FAILED_FETCH_PUBKEY = 25, -}; - -std::string StatusToString(Status status); - -// Base class to keep the status that represents "OK" or the first failure -// reason -class WithStatus { - public: - WithStatus() : status_(Status::OK) {} - Status GetStatus() const { return status_; } - - protected: - void UpdateStatus(Status status) { - // Not overwrite failure status to keep the reason of the first failure - if (status_ == Status::OK) { - status_ = status; - } - } - - private: - Status status_; -}; - -class Pubkeys; -class Jwt; - -// JWT Verifier class. -// -// Usage example: -// Verifier v; -// Jwt jwt(jwt_string); -// std::unique_ptr pubkey = ... -// if (v.Verify(jwt, *pubkey)) { -// auto payload = jwt.Payload(); -// ... -// } else { -// Status s = v.GetStatus(); -// ... -// } -class Verifier : public WithStatus { - public: - // This function verifies JWT signature. - // If verification failed, GetStatus() returns the failture reason. - // When the given JWT has a format error, this verification always fails and - // the JWT's status is handed over to Verifier. - // When pubkeys.GetStatus() is not equal to Status::OK, this verification - // always fails and the public key's status is handed over to Verifier. - bool Verify(const Jwt& jwt, const Pubkeys& pubkeys); - - private: - // Functions to verify with single public key. - // (Note: Pubkeys object passed to Verify() may contains multiple public keys) - // When verification fails, UpdateStatus() is NOT called. - bool VerifySignatureRSA(EVP_PKEY* key, const EVP_MD* md, - const uint8_t* signature, size_t signature_len, - const uint8_t* signed_data, size_t signed_data_len); - bool VerifySignatureRSA(EVP_PKEY* key, const EVP_MD* md, - const std::string& signature, - const std::string& signed_data); - bool VerifySignatureEC(EC_KEY* key, const std::string& signature, - const std::string& signed_data); - bool VerifySignatureEC(EC_KEY* key, const uint8_t* signature, - size_t signature_len, const uint8_t* signed_data, - size_t signed_data_len); -}; - -// Class to parse and a hold a JWT. -// It also holds the failure reason if parse failed. -// -// Usage example: -// Jwt jwt(jwt_string); -// if(jwt.GetStatus() == Status::OK) { ... } -class Jwt : public WithStatus { - public: - // This constructor parses the given JWT and prepares for verification. - // You can check if the setup was successfully done by seeing if GetStatus() - // == Status::OK. When the given JWT has a format error, GetStatus() returns - // the error detail. - Jwt(const std::string& jwt); - - // It returns a pointer to a JSON object of the header of the given JWT. - // When the given JWT has a format error, it returns nullptr. - // It returns the header JSON even if the signature is invalid. - Wasm::Common::JsonObject& Header(); - - // They return a string (or base64url-encoded string) of the header JSON of - // the given JWT. - const std::string& HeaderStr(); - const std::string& HeaderStrBase64Url(); - - // They return the "alg" (or "kid") value of the header of the given JWT. - const std::string& Alg(); - - // It returns the "kid" value of the header of the given JWT, or an empty - // string if "kid" does not exist in the header. - const std::string& Kid(); - - // It returns a pointer to a JSON object of the payload of the given JWT. - // When the given jWT has a format error, it returns nullptr. - // It returns the payload JSON even if the signature is invalid. - Wasm::Common::JsonObject& Payload(); - - // They return a string (or base64url-encoded string) of the payload JSON of - // the given JWT. - const std::string& PayloadStr(); - const std::string& PayloadStrBase64Url(); - - // It returns the "iss" claim value of the given JWT, or an empty string if - // "iss" claim does not exist. - const std::string& Iss(); - - // It returns the "aud" claim value of the given JWT, or an empty string if - // "aud" claim does not exist. - const std::vector& Aud(); - - // It returns the "sub" claim value of the given JWT, or an empty string if - // "sub" claim does not exist. - const std::string& Sub(); - - // It returns the "exp" claim value of the given JWT, or 0 if "exp" claim does - // not exist. - int64_t Exp(); - - private: - Wasm::Common::JsonObject header_; - std::string header_str_; - std::string header_str_base64url_; - Wasm::Common::JsonObject payload_; - std::string payload_str_; - std::string payload_str_base64url_; - std::string signature_; - std::string alg_; - std::string kid_; - std::string iss_; - std::vector aud_; - std::string sub_; - int64_t exp_; - - /* - * TODO: try not to use friend function - */ - friend bool Verifier::Verify(const Jwt& jwt, const Pubkeys& pubkeys); -}; - -// Class to parse and a hold public key(s). -// It also holds the failure reason if parse failed. -// -// Usage example: -// std::unique_ptr keys = Pubkeys::ParseFromJwks(jwks_string); -// if(keys->GetStatus() == Status::OK) { ... } -class Pubkeys : public WithStatus { - public: - // Format of public key. - enum Type { PEM, JWKS }; - - Pubkeys(){}; - static std::unique_ptr CreateFrom(const std::string& pkey, - Type type); - - private: - void CreateFromPemCore(const std::string& pkey_pem); - void CreateFromJwksCore(const std::string& pkey_jwks); - // Extracts the public key from a jwk key (jkey) and sets it to keys_; - bool ExtractPubkeyFromJwk(const Wasm::Common::JsonObject& jwk_json); - bool ExtractPubkeyFromJwkRSA(const Wasm::Common::JsonObject& jwk_json); - bool ExtractPubkeyFromJwkEC(const Wasm::Common::JsonObject& jwk_json); - - class Pubkey { - public: - Pubkey(){}; - bssl::UniquePtr evp_pkey_; - bssl::UniquePtr ec_key_; - std::string kid_; - std::string kty_; - bool alg_specified_ = false; - bool kid_specified_ = false; - bool pem_format_ = false; - std::string alg_; - }; - std::vector > keys_; - - /* - * TODO: try not to use friend function - */ - friend bool Verifier::Verify(const Jwt& jwt, const Pubkeys& pubkeys); -}; - -} // namespace JwtAuth -} // namespace Http -} // namespace Envoy From 74c500095f6e4bcc785bec7952d5fce2ad1ff2f3 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Tue, 14 Jul 2020 15:06:32 +0900 Subject: [PATCH 38/44] deadcode --- extensions/common/base64.h | 59 -------------------------------------- 1 file changed, 59 deletions(-) diff --git a/extensions/common/base64.h b/extensions/common/base64.h index 0f8d6aa29ab..f3c3ca7749d 100644 --- a/extensions/common/base64.h +++ b/extensions/common/base64.h @@ -48,23 +48,6 @@ constexpr unsigned char REVERSE_LOOKUP_TABLE[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; - -// Conversion table is taken from -// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c -// -// and modified the position of 62 ('+' to '-') and 63 ('/' to '_') -constexpr unsigned char REVERSE_LOOKUP_TABLE_BASE64_URL[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 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, 64, 64, 64, 64, 63, - 64, 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, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64}; // clang-format on inline bool decodeBase(const uint8_t cur_char, uint64_t pos, std::string& ret, @@ -158,48 +141,6 @@ inline void encodeLast(uint64_t pos, uint8_t last_char, std::string& ret, } } -std::string Base64UrlDecode(std::string input) { - // allow at most 2 padding letters at the end of the input, only if input - // length is divisible by 4 - int len = input.length(); - if (len % 4 == 0) { - if (input[len - 1] == '=') { - input.pop_back(); - if (input[len - 2] == '=') { - input.pop_back(); - } - } - } - // if input contains non-base64url character, return empty string - // Note: padding letter must not be contained - if (std::find_if(input.begin(), input.end(), [](auto c) -> bool { - return REVERSE_LOOKUP_TABLE_BASE64_URL[static_cast(c)] & 64; - }) != input.end()) { - return ""; - } - - // base64url is using '-', '_' instead of '+', '/' in base64 string. - std::replace(input.begin(), input.end(), '-', '+'); - std::replace(input.begin(), input.end(), '_', '/'); - - // base64 string should be padded with '=' so as to the length of the string - // is divisible by 4. - switch (input.length() % 4) { - case 0: - break; - case 2: - input += "=="; - break; - case 3: - input += "="; - break; - default: - // * an invalid base64url input. return empty string. - return ""; - } - return Base64::decodeWithoutPadding(input); -} - inline std::string Base64::encode(const char* input, uint64_t length, bool add_padding) { uint64_t output_length = (length + 2) / 3 * 4; From a01f6981da2b14b72a106b0e93fd828af7a24a94 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Wed, 15 Jul 2020 21:25:50 +0900 Subject: [PATCH 39/44] abseil: destroy wasm un-compatible repository --- WORKSPACE | 17 ++--------------- bazel/patches/absl.patch | 22 ---------------------- extensions/BUILD | 12 ++++++------ extensions/common/BUILD | 4 ++-- 4 files changed, 10 insertions(+), 45 deletions(-) delete mode 100644 bazel/patches/absl.patch diff --git a/WORKSPACE b/WORKSPACE index 8cb40dad52b..2c14d3cf687 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -38,9 +38,9 @@ bind( # 2. Update .bazelversion, envoy.bazelrc and .bazelrc if needed. # # Commit date: 7/13/20 -ENVOY_SHA = "140d4e539cf452278669c8cf06478fb08633956a" +ENVOY_SHA = "a88cacda8717a02e40ff8cb05414c2f7b640a56d" -ENVOY_SHA256 = "ad31244db7bd66aad46d9dfba9000899ba109319839b4325ef0eae8228c1cb38" +ENVOY_SHA256 = "e857faaa8cd823e7002aa3d2223dd1e3835f8f7f21f0fa1b729e9ef955ff20dd" ENVOY_ORG = "envoyproxy" @@ -134,16 +134,3 @@ http_file( "https://github.com/nlohmann/json/releases/download/v3.7.3/json.hpp", ], ) - -COM_GOOGLE_ABSL_WASM_SHA = "768eb2ca2857342673fcd462792ce04b8bac3fa3" - -http_archive( - name = "com_google_absl_wasm", - patch_args = ["-p1"], - patches = [ - "@io_istio_proxy//:bazel/patches/absl.patch", - ], - sha256 = "bc9dd47d9676b016a8bec86f4e1cdc3edd22042bd9d7948a7b355f600974565e", - strip_prefix = "abseil-cpp-" + COM_GOOGLE_ABSL_WASM_SHA, - url = "https://github.com/abseil/abseil-cpp/archive/" + COM_GOOGLE_ABSL_WASM_SHA + ".tar.gz", -) diff --git a/bazel/patches/absl.patch b/bazel/patches/absl.patch deleted file mode 100644 index 9008e83b585..00000000000 --- a/bazel/patches/absl.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel -index 76122da..ac419f2 100644 ---- a/absl/base/BUILD.bazel -+++ b/absl/base/BUILD.bazel -@@ -153,7 +153,7 @@ cc_library( - copts = ABSL_DEFAULT_COPTS, - linkopts = select({ - "//absl:windows": [], -- "//conditions:default": ["-pthread"], -+ "//conditions:default": [], - }) + ABSL_DEFAULT_LINKOPTS, - visibility = [ - "//visibility:public", -@@ -214,7 +214,7 @@ cc_library( - "//absl:windows": [ - "-DEFAULTLIB:advapi32.lib", - ], -- "//conditions:default": ["-pthread"], -+ "//conditions:default": [], - }) + ABSL_DEFAULT_LINKOPTS, - deps = [ - ":atomic_hook", diff --git a/extensions/BUILD b/extensions/BUILD index 02aa31d00c8..470d0df8efa 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -40,8 +40,8 @@ wasm_cc_binary( deps = [ "//extensions/common:json_util_wasm", "//extensions/common:node_info_fb_cc", - "@com_google_absl_wasm//absl/strings", - "@com_google_absl_wasm//absl/time", + "//external:abseil_strings", + "//external:abseil_time", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", "@proxy_wasm_cpp_sdk//contrib:contrib_lib", ], @@ -65,8 +65,8 @@ wasm_cc_binary( "//extensions/common:json_util_wasm", "//extensions/common:node_info_fb_cc", "//extensions/metadata_exchange:declare_property_proto_cc", - "@com_google_absl_wasm//absl/strings", - "@com_google_absl_wasm//absl/time", + "//external:abseil_strings", + "//external:abseil_time", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", "@proxy_wasm_cpp_sdk//contrib:contrib_lib", ], @@ -87,8 +87,8 @@ wasm_cc_binary( "//extensions/attributegen:config_cc_proto", "//extensions/common:json_util_wasm", "//extensions/common:node_info_fb_cc", - "@com_google_absl_wasm//absl/strings", - "@com_google_absl_wasm//absl/time", + "//external:abseil_strings", + "//external:abseil_time",, "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", "@proxy_wasm_cpp_sdk//contrib:contrib_lib", ], diff --git a/extensions/common/BUILD b/extensions/common/BUILD index 5472c310588..a3c4378a94e 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -179,7 +179,7 @@ cc_library( copts = ["-UNULL_PLUGIN"], visibility = ["//visibility:public"], deps = [ - "@com_google_absl_wasm//absl/strings", - "@com_google_absl_wasm//absl/types:optional", + "//external:abseil_strings", + "//external:abseil_optional", ], ) From 2a3a7ac20f8b411b2236053a44ca2f63000278f3 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Thu, 16 Jul 2020 12:34:42 +0900 Subject: [PATCH 40/44] update --- WORKSPACE | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 2c14d3cf687..cffcfc04ffe 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -37,10 +37,10 @@ bind( # 1. Determine SHA256 `wget https://github.com/envoyproxy/envoy-wasm/archive/$COMMIT.tar.gz && sha256sum $COMMIT.tar.gz` # 2. Update .bazelversion, envoy.bazelrc and .bazelrc if needed. # -# Commit date: 7/13/20 -ENVOY_SHA = "a88cacda8717a02e40ff8cb05414c2f7b640a56d" +# Commit date: 7/15/20 +ENVOY_SHA = "a84abd3b451f2c7149d778d5f1d0d3c5c0733b20" -ENVOY_SHA256 = "e857faaa8cd823e7002aa3d2223dd1e3835f8f7f21f0fa1b729e9ef955ff20dd" +ENVOY_SHA256 = "1df6f62968869163bba12ca0c2f51feb1d65e7b3ef44569a011c5a1106076dcb" ENVOY_ORG = "envoyproxy" From 20947742a9cdb56c7e38d2d0e1dea9b5c917ed9f Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Thu, 16 Jul 2020 15:09:20 +0900 Subject: [PATCH 41/44] fix --- extensions/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/BUILD b/extensions/BUILD index 470d0df8efa..7341cc2bf28 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -88,7 +88,7 @@ wasm_cc_binary( "//extensions/common:json_util_wasm", "//extensions/common:node_info_fb_cc", "//external:abseil_strings", - "//external:abseil_time",, + "//external:abseil_time", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_full", "@proxy_wasm_cpp_sdk//contrib:contrib_lib", ], From 3b688a2616350019525efb486f9630408dafc1ee Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Thu, 16 Jul 2020 15:22:22 +0900 Subject: [PATCH 42/44] format --- extensions/common/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/common/BUILD b/extensions/common/BUILD index a3c4378a94e..8c6cd10fe88 100644 --- a/extensions/common/BUILD +++ b/extensions/common/BUILD @@ -179,7 +179,7 @@ cc_library( copts = ["-UNULL_PLUGIN"], visibility = ["//visibility:public"], deps = [ - "//external:abseil_strings", "//external:abseil_optional", + "//external:abseil_strings", ], ) From f211924860acc4d164a388e02a109ed436116971 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Thu, 16 Jul 2020 15:31:32 +0900 Subject: [PATCH 43/44] fix --- extensions/metadata_exchange/BUILD | 2 +- extensions/metadata_exchange/plugin.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/metadata_exchange/BUILD b/extensions/metadata_exchange/BUILD index 50a105cb785..1da81d7f815 100644 --- a/extensions/metadata_exchange/BUILD +++ b/extensions/metadata_exchange/BUILD @@ -28,7 +28,7 @@ envoy_cc_library( "//extensions/common:json_util", "//extensions/common:proto_util", "@envoy//source/common/common:base64_lib", - "@envoy//source/extensions/common/wasm:declare_property_cc_proto", + "@envoy//source/extensions/common/wasm/ext:declare_property_cc_proto", "@proxy_wasm_cpp_host//:lib", ], ) diff --git a/extensions/metadata_exchange/plugin.cc b/extensions/metadata_exchange/plugin.cc index 860d966fe57..8ee42605728 100644 --- a/extensions/metadata_exchange/plugin.cc +++ b/extensions/metadata_exchange/plugin.cc @@ -30,7 +30,7 @@ #else #include "common/common/base64.h" -#include "source/extensions/common/wasm/declare_property.pb.h" +#include "source/extensions/common/wasm/ext/declare_property.pb.h" namespace proxy_wasm { namespace null_plugin { From 81d237d11060e6fc213b87d66815dd4fbee2b147 Mon Sep 17 00:00:00 2001 From: Shikugawa Date: Fri, 17 Jul 2020 02:10:09 +0900 Subject: [PATCH 44/44] stash --- WORKSPACE | 2 +- extensions/authn/BUILD | 64 ++++++++-------- extensions/authn/authenticator_base_test.cc | 59 +++++++------- extensions/authn/authn_utils_test.cc | 22 +++--- extensions/authn/filter_context_test.cc | 25 +++--- extensions/authn/origin_authenticator.cc | 11 ++- extensions/authn/origin_authenticator_test.cc | 76 +++++++++---------- extensions/authn/peer_authenticator_test.cc | 52 ++++++------- extensions/authn/test_utils.h | 8 +- 9 files changed, 154 insertions(+), 165 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 0ebb3573ea6..175c7be59bf 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -43,7 +43,7 @@ ENVOY_SHA = "a84abd3b451f2c7149d778d5f1d0d3c5c0733b20" ENVOY_SHA256 = "1df6f62968869163bba12ca0c2f51feb1d65e7b3ef44569a011c5a1106076dcb" -ENVOY_ORG = "Shikugawa" +ENVOY_ORG = "envoyproxy" ENVOY_REPO = "envoy-wasm" diff --git a/extensions/authn/BUILD b/extensions/authn/BUILD index 936c1847fb5..d9de3ade20a 100644 --- a/extensions/authn/BUILD +++ b/extensions/authn/BUILD @@ -32,7 +32,7 @@ envoy_cc_library( "filter_context.cc", "origin_authenticator.cc", "peer_authenticator.cc", - "plugin.cc", + # "plugin.cc", ], hdrs = [ "authenticator_base.h", @@ -40,7 +40,7 @@ envoy_cc_library( "filter_context.h", "origin_authenticator.h", "peer_authenticator.h", - "plugin.h", + # "plugin.h", ], repository = "@envoy", visibility = ["//visibility:public"], @@ -71,7 +71,7 @@ envoy_cc_test( srcs = ["filter_context_test.cc"], repository = "@envoy", deps = [ - ":authenticator", + ":authn_plugin", ":test_utils", "@envoy//test/mocks/http:http_mocks", "@envoy//test/test_common:utility_lib", @@ -83,7 +83,7 @@ envoy_cc_test( srcs = ["authenticator_base_test.cc"], repository = "@envoy", deps = [ - ":authenticator", + ":authn_plugin", ":test_utils", "//src/envoy/utils:filter_names_lib", "@envoy//test/mocks/network:network_mocks", @@ -97,7 +97,7 @@ envoy_cc_test( srcs = ["authn_utils_test.cc"], repository = "@envoy", deps = [ - ":authenticator", + ":authn_plugin", ":test_utils", "@envoy//test/test_common:utility_lib", ], @@ -108,7 +108,7 @@ envoy_cc_test( srcs = ["peer_authenticator_test.cc"], repository = "@envoy", deps = [ - ":authenticator", + ":authn_plugin", ":test_utils", "@envoy//test/mocks/http:http_mocks", "@envoy//test/test_common:utility_lib", @@ -120,36 +120,36 @@ envoy_cc_test( srcs = ["origin_authenticator_test.cc"], repository = "@envoy", deps = [ - ":authenticator", + ":authn_plugin", ":test_utils", "@envoy//test/mocks/http:http_mocks", "@envoy//test/test_common:utility_lib", ], ) -envoy_cc_test( - name = "http_filter_test", - srcs = ["http_filter_test.cc"], - repository = "@envoy", - deps = [ - ":filter_lib", - ":test_utils", - "//external:authentication_policy_config_cc_proto", - "@envoy//source/common/http:header_map_lib", - "@envoy//test/mocks/http:http_mocks", - "@envoy//test/test_common:utility_lib", - ], -) +# envoy_cc_test( +# name = "http_filter_test", +# srcs = ["http_filter_test.cc"], +# repository = "@envoy", +# deps = [ +# ":filter_lib", +# ":test_utils", +# "//external:authentication_policy_config_cc_proto", +# "@envoy//source/common/http:header_map_lib", +# "@envoy//test/mocks/http:http_mocks", +# "@envoy//test/test_common:utility_lib", +# ], +# ) -envoy_cc_test( - name = "http_filter_integration_test", - srcs = ["http_filter_integration_test.cc"], - data = glob(["testdata/*"]), - repository = "@envoy", - deps = [ - ":filter_lib", - "//src/envoy/utils:filter_names_lib", - "@envoy//source/common/common:utility_lib", - "@envoy//test/integration:http_protocol_integration_lib", - ], -) +# envoy_cc_test( +# name = "http_filter_integration_test", +# srcs = ["http_filter_integration_test.cc"], +# data = glob(["testdata/*"]), +# repository = "@envoy", +# deps = [ +# ":filter_lib", +# "//src/envoy/utils:filter_names_lib", +# "@envoy//source/common/common:utility_lib", +# "@envoy//test/integration:http_protocol_integration_lib", +# ], +# ) diff --git a/extensions/authn/authenticator_base_test.cc b/extensions/authn/authenticator_base_test.cc index d47942acf56..9a9f1a13007 100644 --- a/extensions/authn/authenticator_base_test.cc +++ b/extensions/authn/authenticator_base_test.cc @@ -13,14 +13,14 @@ * limitations under the License. */ -#include "src/envoy/http/authn/authenticator_base.h" +#include "extensions/authn/authenticator_base.h" #include "common/common/base64.h" #include "common/protobuf/protobuf.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/filter/http/authn/v2alpha1/config.pb.h" #include "gmock/gmock.h" -#include "src/envoy/http/authn/test_utils.h" +#include "extensions/authn/test_utils.h" #include "src/envoy/utils/filter_names.h" #include "test/mocks/network/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -34,9 +34,8 @@ using testing::StrictMock; namespace iaapi = istio::authentication::v1alpha1; -namespace Envoy { -namespace Http { -namespace Istio { +namespace proxy_wasm { +namespace null_plugin { namespace AuthN { namespace { @@ -83,18 +82,16 @@ class MockAuthenticatorBase : public AuthenticatorBase { MOCK_METHOD1(run, bool(Payload*)); }; -class ValidateX509Test : public testing::TestWithParam, - public Logger::Loggable { +class ValidateX509Test : public testing::TestWithParam { public: virtual ~ValidateX509Test() {} - + NiceMock connection_{}; - Envoy::Http::RequestHeaderMapImpl header_{}; + Envoy::Http::TestRequestHeaderMapImpl header_{}; FilterConfig filter_config_{}; FilterContext filter_context_{ envoy::config::core::v3::Metadata::default_instance(), header_, &connection_, filter_config_}; - MockAuthenticatorBase authenticator_{&filter_context_}; void SetUp() override { @@ -122,7 +119,7 @@ TEST_P(ValidateX509Test, PlaintextConnection) { } TEST_P(ValidateX509Test, SslConnectionWithNoPeerCert) { - auto ssl = std::make_shared>(); + auto ssl = std::make_shared>(); ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(false)); EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl)); @@ -136,7 +133,7 @@ TEST_P(ValidateX509Test, SslConnectionWithNoPeerCert) { } TEST_P(ValidateX509Test, SslConnectionWithPeerCert) { - auto ssl = std::make_shared>(); + auto ssl = std::make_shared>(); ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); ON_CALL(*ssl, uriSanPeerCertificate()) .WillByDefault(Return(std::vector{"foo"})); @@ -155,7 +152,7 @@ TEST_P(ValidateX509Test, SslConnectionWithCertsSkipTrustDomainValidation) { JsonStringToMessage("{ skip_validate_trust_domain: true }", &filter_config_, options); - auto ssl = std::make_shared>(); + auto ssl = std::make_shared>(); ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); ON_CALL(*ssl, uriSanPeerCertificate()) .WillByDefault(Return(std::vector{"foo"})); @@ -167,7 +164,7 @@ TEST_P(ValidateX509Test, SslConnectionWithCertsSkipTrustDomainValidation) { } TEST_P(ValidateX509Test, SslConnectionWithSpiffeCertsSameTrustDomain) { - auto ssl = std::make_shared>(); + auto ssl = std::make_shared>(); ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); ON_CALL(*ssl, uriSanPeerCertificate()) .WillByDefault(Return(std::vector{"spiffe://td/foo"})); @@ -182,7 +179,7 @@ TEST_P(ValidateX509Test, SslConnectionWithSpiffeCertsSameTrustDomain) { } TEST_P(ValidateX509Test, SslConnectionWithSpiffeCertsDifferentTrustDomain) { - auto ssl = std::make_shared>(); + auto ssl = std::make_shared>(); ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); ON_CALL(*ssl, uriSanPeerCertificate()) .WillByDefault(Return(std::vector{"spiffe://td-1/foo"})); @@ -203,7 +200,7 @@ TEST_P(ValidateX509Test, SslConnectionWithPeerMalformedSpiffeCert) { JsonStringToMessage("{ skip_validate_trust_domain: true }", &filter_config_, options); - auto ssl = std::make_shared>(); + auto ssl = std::make_shared>(); ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); ON_CALL(*ssl, uriSanPeerCertificate()) .WillByDefault(Return(std::vector{"spiffe:foo"})); @@ -223,15 +220,14 @@ INSTANTIATE_TEST_SUITE_P(ValidateX509Tests, ValidateX509Test, testing::Values(iaapi::MutualTls::STRICT, iaapi::MutualTls::PERMISSIVE)); -class ValidateJwtTest : public testing::Test, - public Logger::Loggable { +class ValidateJwtTest : public testing::Test { public: virtual ~ValidateJwtTest() {} // StrictMock request_info_{}; envoy::config::core::v3::Metadata dynamic_metadata_; NiceMock connection_{}; - Envoy::Http::RequestHeaderMapImpl header_{}; + Envoy::Http::TestRequestHeaderMapImpl header_{}; FilterConfig filter_config_{}; FilterContext filter_context_{dynamic_metadata_, header_, &connection_, filter_config_}; @@ -307,8 +303,8 @@ TEST_F(ValidateJwtTest, NoJwtPayloadOutput) { TEST_F(ValidateJwtTest, HasJwtPayloadOutputButNoDataForKey) { jwt_.set_issuer("issuer@foo.com"); - (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] - .MergeFrom(MessageUtil::keyValueStruct("foo", "bar")); + (*dynamic_metadata_.mutable_filter_metadata())[Envoy::Utils::IstioFilterName::kJwt] + .MergeFrom(Envoy::MessageUtil::keyValueStruct("foo", "bar")); // When there is no JWT payload for given issuer in request info dynamic // metadata, validateJwt() should return nullptr and failure. @@ -318,8 +314,8 @@ TEST_F(ValidateJwtTest, HasJwtPayloadOutputButNoDataForKey) { TEST_F(ValidateJwtTest, JwtPayloadAvailableWithBadData) { jwt_.set_issuer("issuer@foo.com"); - (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] - .MergeFrom(MessageUtil::keyValueStruct("issuer@foo.com", "bad-data")); + (*dynamic_metadata_.mutable_filter_metadata())[Envoy::Utils::IstioFilterName::kJwt] + .MergeFrom(Envoy::MessageUtil::keyValueStruct("issuer@foo.com", "bad-data")); // EXPECT_CALL(request_info_, dynamicMetadata()); EXPECT_FALSE(authenticator_.validateJwt(jwt_, payload_)); @@ -328,8 +324,8 @@ TEST_F(ValidateJwtTest, JwtPayloadAvailableWithBadData) { TEST_F(ValidateJwtTest, JwtPayloadAvailable) { jwt_.set_issuer("issuer@foo.com"); - (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] - .MergeFrom(MessageUtil::keyValueStruct("issuer@foo.com", + (*dynamic_metadata_.mutable_filter_metadata())[Envoy::Utils::IstioFilterName::kJwt] + .MergeFrom(Envoy::MessageUtil::keyValueStruct("issuer@foo.com", kSecIstioAuthUserinfoHeaderValue)); Payload expected_payload; @@ -359,9 +355,9 @@ TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedToken) { jwt_.set_issuer("token-service"); jwt_.add_jwt_headers(kExchangedTokenHeaderName); - (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + (*dynamic_metadata_.mutable_filter_metadata())[Envoy::Utils::IstioFilterName::kJwt] .MergeFrom( - MessageUtil::keyValueStruct("token-service", kExchangedTokenPayload)); + Envoy::MessageUtil::keyValueStruct("token-service", kExchangedTokenPayload)); Payload expected_payload; JsonStringToMessage( @@ -396,8 +392,8 @@ TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedTokenMissing) { jwt_.set_issuer("token-service"); jwt_.add_jwt_headers(kExchangedTokenHeaderName); - (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] - .MergeFrom(MessageUtil::keyValueStruct( + (*dynamic_metadata_.mutable_filter_metadata())[Envoy::Utils::IstioFilterName::kJwt] + .MergeFrom(Envoy::MessageUtil::keyValueStruct( "token-service", kExchangedTokenPayloadNoOriginalClaims)); // When no original_claims in an exchanged token, the token @@ -408,9 +404,9 @@ TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedTokenMissing) { TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedTokenNotInIntendedHeader) { jwt_.set_issuer("token-service"); - (*dynamic_metadata_.mutable_filter_metadata())[Utils::IstioFilterName::kJwt] + (*dynamic_metadata_.mutable_filter_metadata())[Envoy::Utils::IstioFilterName::kJwt] .MergeFrom( - MessageUtil::keyValueStruct("token-service", kExchangedTokenPayload)); + Envoy::MessageUtil::keyValueStruct("token-service", kExchangedTokenPayload)); Payload expected_payload; JsonStringToMessage( @@ -437,6 +433,5 @@ TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedTokenNotInIntendedHeader) { } // namespace } // namespace AuthN -} // namespace Istio } // namespace Http } // namespace Envoy diff --git a/extensions/authn/authn_utils_test.cc b/extensions/authn/authn_utils_test.cc index 2a918046557..1d94394109a 100644 --- a/extensions/authn/authn_utils_test.cc +++ b/extensions/authn/authn_utils_test.cc @@ -12,19 +12,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "src/envoy/http/authn/authn_utils.h" +#include "extensions/authn/authn_utils.h" #include "common/common/base64.h" #include "common/common/utility.h" -#include "src/envoy/http/authn/test_utils.h" +#include "extensions/authn/test_utils.h" #include "test/test_common/utility.h" using google::protobuf::util::MessageDifferencer; using istio::authn::JwtPayload; -namespace Envoy { -namespace Http { -namespace Istio { +namespace proxy_wasm { +namespace null_plugin { namespace AuthN { namespace { @@ -61,7 +60,7 @@ const std::string kSecIstioAuthUserInfoHeaderWithAudValueArray = TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderTest) { JwtPayload payload, expected_payload; - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( R"( user: "issuer@foo.com/sub@foo.com" audiences: ["aud1"] @@ -108,7 +107,7 @@ TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderTest) { } } raw_claims: ")" + - StringUtil::escape(kSecIstioAuthUserinfoHeaderValue) + R"(")", + Envoy::StringUtil::escape(kSecIstioAuthUserinfoHeaderValue) + R"(")", &expected_payload)); // The payload returned from ProcessJwtPayload() should be the same as // the expected. @@ -120,7 +119,7 @@ TEST(AuthnUtilsTest, GetJwtPayloadFromHeaderTest) { TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudListTest) { JwtPayload payload, expected_payload; - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( R"( user: "issuer@foo.com/sub@foo.com" audiences: "aud1" @@ -171,7 +170,7 @@ TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudListTest) { } } raw_claims: ")" + - StringUtil::escape(kSecIstioAuthUserInfoHeaderWithAudValueList) + + Envoy::StringUtil::escape(kSecIstioAuthUserInfoHeaderWithAudValueList) + R"(")", &expected_payload)); // The payload returned from ProcessJwtPayload() should be the same as @@ -185,7 +184,7 @@ TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudListTest) { TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudArrayTest) { JwtPayload payload, expected_payload; - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( R"( user: "issuer@foo.com/sub@foo.com" audiences: "aud1" @@ -236,7 +235,7 @@ TEST(AuthnUtilsTest, ProcessJwtPayloadWithAudArrayTest) { } } raw_claims: ")" + - StringUtil::escape(kSecIstioAuthUserInfoHeaderWithAudValueArray) + + Envoy::StringUtil::escape(kSecIstioAuthUserInfoHeaderWithAudValueArray) + R"(")", &expected_payload)); // The payload returned from ProcessJwtPayload() should be the same as @@ -346,6 +345,5 @@ TEST(AuthnUtilsTest, ShouldValidateJwtPerPathDefault) { } // namespace } // namespace AuthN -} // namespace Istio } // namespace Http } // namespace Envoy diff --git a/extensions/authn/filter_context_test.cc b/extensions/authn/filter_context_test.cc index 7b523213b96..d74643e52c0 100644 --- a/extensions/authn/filter_context_test.cc +++ b/extensions/authn/filter_context_test.cc @@ -13,10 +13,10 @@ * limitations under the License. */ -#include "src/envoy/http/authn/filter_context.h" +#include "extensions/authn/filter_context.h" #include "envoy/config/core/v3/base.pb.h" -#include "src/envoy/http/authn/test_utils.h" +#include "extensions/authn/test_utils.h" #include "test/test_common/utility.h" using istio::authn::Payload; @@ -24,9 +24,8 @@ using testing::StrictMock; namespace iaapi = istio::authentication::v1alpha1; -namespace Envoy { -namespace Http { -namespace Istio { +namespace proxy_wasm { +namespace null_plugin { namespace AuthN { namespace { @@ -45,16 +44,17 @@ class FilterContextTest : public testing::Test { Payload jwt_payload_{TestUtilities::CreateJwtPayload("bar", "istio.io")}; }; + TEST_F(FilterContextTest, SetPeerResult) { filter_context_.setPeerResult(&x509_payload_); - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( TestUtilities::AuthNResultFromString("peer_user: \"foo\""), filter_context_.authenticationResult())); } TEST_F(FilterContextTest, SetOriginResult) { filter_context_.setOriginResult(&jwt_payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( origin { user: "bar" presenter: "istio.io" @@ -66,7 +66,7 @@ TEST_F(FilterContextTest, SetOriginResult) { TEST_F(FilterContextTest, SetBoth) { filter_context_.setPeerResult(&x509_payload_); filter_context_.setOriginResult(&jwt_payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( peer_user: "foo" origin { user: "bar" @@ -80,7 +80,7 @@ TEST_F(FilterContextTest, UseOrigin) { filter_context_.setPeerResult(&x509_payload_); filter_context_.setOriginResult(&jwt_payload_); filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_ORIGIN); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( principal: "bar" peer_user: "foo" origin { @@ -94,7 +94,7 @@ TEST_F(FilterContextTest, UseOrigin) { TEST_F(FilterContextTest, UseOriginOnEmptyOrigin) { filter_context_.setPeerResult(&x509_payload_); filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_ORIGIN); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( peer_user: "foo" )"), filter_context_.authenticationResult())); @@ -103,7 +103,7 @@ TEST_F(FilterContextTest, UseOriginOnEmptyOrigin) { TEST_F(FilterContextTest, PrincipalUsePeer) { filter_context_.setPeerResult(&x509_payload_); filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_PEER); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( principal: "foo" peer_user: "foo" )"), @@ -113,7 +113,7 @@ TEST_F(FilterContextTest, PrincipalUsePeer) { TEST_F(FilterContextTest, PrincipalUsePeerOnEmptyPeer) { filter_context_.setOriginResult(&jwt_payload_); filter_context_.setPrincipal(iaapi::PrincipalBinding::USE_PEER); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(R"( origin { user: "bar" presenter: "istio.io" @@ -126,4 +126,3 @@ TEST_F(FilterContextTest, PrincipalUsePeerOnEmptyPeer) { } // namespace AuthN } // namespace Istio } // namespace Http -} // namespace Envoy diff --git a/extensions/authn/origin_authenticator.cc b/extensions/authn/origin_authenticator.cc index 536fc1a97a4..676fbcb8a8d 100644 --- a/extensions/authn/origin_authenticator.cc +++ b/extensions/authn/origin_authenticator.cc @@ -47,15 +47,18 @@ namespace AuthN { Envoy::Http::RegisterCustomInlineHeader< Envoy::Http::CustomInlineHeaderRegistry::Type::RequestHeaders> - access_control_request_method( - Envoy::Http::Headers::get().AccessControlRequestMethod); + access_control_request_method_handle( + Envoy::Http::CustomHeaders::get().AccessControlRequestMethod); +Envoy::Http::RegisterCustomInlineHeader< + Envoy::Http::CustomInlineHeaderRegistry::Type::RequestHeaders> + origin_handle(Envoy::Http::CustomHeaders::get().Origin); bool isCORSPreflightRequest(const Envoy::Http::RequestHeaderMap& headers) { return headers.Method() && headers.Method()->value().getStringView() == Envoy::Http::Headers::get().MethodValues.Options && - headers.Origin() && !headers.Origin()->value().empty() && - !headers.getInlineValue(access_control_request_method.handle()) + !headers.getInlineValue(origin_handle.handle()).empty() && + !headers.getInlineValue(access_control_request_method_handle.handle()) .empty(); } diff --git a/extensions/authn/origin_authenticator_test.cc b/extensions/authn/origin_authenticator_test.cc index 60c9739eef5..5e8295e8683 100644 --- a/extensions/authn/origin_authenticator_test.cc +++ b/extensions/authn/origin_authenticator_test.cc @@ -13,14 +13,14 @@ * limitations under the License. */ -#include "src/envoy/http/authn/origin_authenticator.h" +#include "extensions/authn/origin_authenticator.h" #include "authentication/v1alpha1/policy.pb.h" #include "common/protobuf/protobuf.h" #include "envoy/config/core/v3/base.pb.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "src/envoy/http/authn/test_utils.h" +#include "extensions/authn/test_utils.h" #include "test/mocks/http/mocks.h" #include "test/test_common/utility.h" @@ -36,9 +36,8 @@ using testing::Return; using testing::SetArgPointee; using testing::StrictMock; -namespace Envoy { -namespace Http { -namespace Istio { +namespace proxy_wasm { +namespace null_plugin { namespace AuthN { namespace { @@ -232,13 +231,13 @@ TEST_P(OriginAuthenticatorTest, Empty) { if (set_peer_) { initial_result_.set_principal("bar"); } - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } // It should fail if the binding is USE_ORIGIN but origin methods are empty. TEST_P(OriginAuthenticatorTest, ZeroMethodFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kZeroOriginMethodPolicyBindOrigin, &policy_)); createAuthenticator(); EXPECT_FALSE(authenticator_->run(payload_)); @@ -246,7 +245,7 @@ TEST_P(OriginAuthenticatorTest, ZeroMethodFail) { // It should pass if the binding is USE_PEER and origin methods are empty. TEST_P(OriginAuthenticatorTest, ZeroMethodPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kZeroOriginMethodPolicyBindPeer, &policy_)); createAuthenticator(); @@ -262,12 +261,12 @@ TEST_P(OriginAuthenticatorTest, ZeroMethodPass) { } EXPECT_TRUE(authenticator_->run(&jwt_extra_payload_)); - EXPECT_TRUE(TestUtility::protoEqual(expected_result, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, SingleMethodPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, &policy_)); createAuthenticator(); @@ -277,12 +276,12 @@ TEST_P(OriginAuthenticatorTest, SingleMethodPass) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result_when_pass_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, SingleMethodFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, &policy_)); createAuthenticator(); @@ -292,12 +291,12 @@ TEST_P(OriginAuthenticatorTest, SingleMethodFail) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(false))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, CORSPreflight) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(kSingleOriginMethodPolicy, &policy_)); createAuthenticator(); @@ -311,7 +310,7 @@ TEST_P(OriginAuthenticatorTest, CORSPreflight) { } TEST_P(OriginAuthenticatorTest, TriggeredWithNullPath) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kSingleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -321,12 +320,12 @@ TEST_P(OriginAuthenticatorTest, TriggeredWithNullPath) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result_when_pass_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, SingleRuleTriggered) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kSingleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -337,7 +336,7 @@ TEST_P(OriginAuthenticatorTest, SingleRuleTriggered) { setPath("/allow"); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result_when_pass_, filter_context_.authenticationResult())); } @@ -353,7 +352,7 @@ TEST_P(OriginAuthenticatorTest, SingleRuleTriggeredWithComponents) { "/allow?a=b#c", "/allow#a?b=c"}; for (const auto& path : input_paths) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kSingleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -364,7 +363,7 @@ TEST_P(OriginAuthenticatorTest, SingleRuleTriggeredWithComponents) { setPath(path); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( expected_result_when_pass_, filter_context_.authenticationResult())); } } @@ -372,7 +371,7 @@ TEST_P(OriginAuthenticatorTest, SingleRuleTriggeredWithComponents) { TEST_P(OriginAuthenticatorTest, SingleRuleNotTriggered) { const std::vector input_paths{"/bad", "/allow$?", "/allow$#"}; for (const auto& path : input_paths) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kSingleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -381,13 +380,13 @@ TEST_P(OriginAuthenticatorTest, SingleRuleNotTriggered) { setPath(path); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( initial_result_, filter_context_.authenticationResult())); } } TEST_P(OriginAuthenticatorTest, SingleExcludeRuleTriggeredWithQueryParam) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kSingleOriginMethodWithExcludeTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -396,12 +395,12 @@ TEST_P(OriginAuthenticatorTest, SingleExcludeRuleTriggeredWithQueryParam) { setPath("/login?a=b&c=d"); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, Multiple) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kMultipleOriginMethodsPolicy, &policy_)); createAuthenticator(); @@ -413,12 +412,12 @@ TEST_P(OriginAuthenticatorTest, Multiple) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result_when_pass_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, MultipleFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kMultipleOriginMethodsPolicy, &policy_)); createAuthenticator(); @@ -430,12 +429,12 @@ TEST_P(OriginAuthenticatorTest, MultipleFail) { DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, MultipleRuleTriggeredValidationSucceeded) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kMultipleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -448,12 +447,12 @@ TEST_P(OriginAuthenticatorTest, MultipleRuleTriggeredValidationSucceeded) { setPath("/allow"); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result_when_pass_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, MultipleRuleTriggeredValidationFailed) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kMultipleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -465,12 +464,12 @@ TEST_P(OriginAuthenticatorTest, MultipleRuleTriggeredValidationFailed) { setPath("/two"); EXPECT_FALSE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, MultipleRuleNotTriggered) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString( kMultipleOriginMethodWithTriggerRulePolicy, &policy_)); createAuthenticator(); @@ -478,12 +477,12 @@ TEST_P(OriginAuthenticatorTest, MultipleRuleNotTriggered) { setPath("/bad"); EXPECT_TRUE(authenticator_->run(payload_)); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, PeerBindingPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); // Expected principal is from peer_user. expected_result_when_pass_.set_principal(initial_result_.peer_user()); @@ -494,12 +493,12 @@ TEST_P(OriginAuthenticatorTest, PeerBindingPass) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(expected_result_when_pass_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(expected_result_when_pass_, filter_context_.authenticationResult())); } TEST_P(OriginAuthenticatorTest, PeerBindingFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(kPeerBinding, &policy_)); createAuthenticator(); // All fail. @@ -508,7 +507,7 @@ TEST_P(OriginAuthenticatorTest, PeerBindingFail) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(false))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(initial_result_, + EXPECT_TRUE(Envoy::TestUtility::protoEqual(initial_result_, filter_context_.authenticationResult())); } @@ -517,6 +516,5 @@ INSTANTIATE_TEST_SUITE_P(OriginAuthenticatorTests, OriginAuthenticatorTest, } // namespace } // namespace AuthN -} // namespace Istio } // namespace Http } // namespace Envoy diff --git a/extensions/authn/peer_authenticator_test.cc b/extensions/authn/peer_authenticator_test.cc index fc5729abe6a..5b0d5e8fed7 100644 --- a/extensions/authn/peer_authenticator_test.cc +++ b/extensions/authn/peer_authenticator_test.cc @@ -13,14 +13,14 @@ * limitations under the License. */ -#include "src/envoy/http/authn/peer_authenticator.h" +#include "extensions/authn/peer_authenticator.h" #include "authentication/v1alpha1/policy.pb.h" #include "common/protobuf/protobuf.h" #include "envoy/config/core/v3/base.pb.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "src/envoy/http/authn/test_utils.h" +#include "extensions/authn/test_utils.h" #include "test/mocks/http/mocks.h" #include "test/test_common/utility.h" @@ -35,9 +35,8 @@ using testing::Return; using testing::SetArgPointee; using testing::StrictMock; -namespace Envoy { -namespace Http { -namespace Istio { +namespace proxy_wasm { +namespace null_plugin { namespace AuthN { namespace { @@ -85,12 +84,12 @@ class PeerAuthenticatorTest : public testing::Test { TEST_F(PeerAuthenticatorTest, EmptyPolicy) { createAuthenticator(); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, MTlsOnlyPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls { } @@ -104,13 +103,13 @@ TEST_F(PeerAuthenticatorTest, MTlsOnlyPass) { .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, TlsOnlyPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls { allow_tls: true @@ -127,13 +126,13 @@ TEST_F(PeerAuthenticatorTest, TlsOnlyPass) { authenticator_->run(payload_); // When client certificate is present on TLS, authenticated attribute // should be extracted. - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, MTlsOnlyFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls { } @@ -146,12 +145,12 @@ TEST_F(PeerAuthenticatorTest, MTlsOnlyFail) { .Times(1) .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, TlsOnlyFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls { allow_tls: true @@ -168,12 +167,12 @@ TEST_F(PeerAuthenticatorTest, TlsOnlyFail) { authenticator_->run(payload_); // When TLS authentication failse, the authenticated attribute should be // empty. - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, JwtOnlyPass) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { jwt { issuer: "abc.xyz" @@ -187,13 +186,13 @@ TEST_F(PeerAuthenticatorTest, JwtOnlyPass) { .Times(1) .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(true))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, JwtOnlyFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { jwt { issuer: "abc.xyz" @@ -207,12 +206,12 @@ TEST_F(PeerAuthenticatorTest, JwtOnlyFail) { .Times(1) .WillOnce(DoAll(SetArgPointee<1>(x509_payload_), Return(false))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, Multiple) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls {} } @@ -239,13 +238,13 @@ TEST_F(PeerAuthenticatorTest, Multiple) { .WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, TlsFailAndJwtSucceed) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls { allow_tls: true } } @@ -272,13 +271,13 @@ TEST_F(PeerAuthenticatorTest, TlsFailAndJwtSucceed) { authenticator_->run(payload_); // validateX509 fail and validateJwt succeeds, // result should be "foo", as expected as in jwt_payload. - EXPECT_TRUE(TestUtility::protoEqual( + EXPECT_TRUE(Envoy::TestUtility::protoEqual( TestUtilities::AuthNResultFromString(R"(peer_user: "foo")"), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, MultipleAllFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls {} } @@ -304,12 +303,12 @@ TEST_F(PeerAuthenticatorTest, MultipleAllFail) { .WillRepeatedly( DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); authenticator_->run(payload_); - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), filter_context_.authenticationResult())); } TEST_F(PeerAuthenticatorTest, TlsFailJwtFail) { - ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(R"( + ASSERT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(R"( peers { mtls { allow_tls: true } } @@ -336,12 +335,11 @@ TEST_F(PeerAuthenticatorTest, TlsFailJwtFail) { DoAll(SetArgPointee<1>(jwt_extra_payload_), Return(false))); authenticator_->run(payload_); // validateX509 and validateJwt fail, result should be empty. - EXPECT_TRUE(TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), + EXPECT_TRUE(Envoy::TestUtility::protoEqual(TestUtilities::AuthNResultFromString(""), filter_context_.authenticationResult())); } } // namespace } // namespace AuthN -} // namespace Istio } // namespace Http } // namespace Envoy diff --git a/extensions/authn/test_utils.h b/extensions/authn/test_utils.h index 36b3c2e22b6..972f82e28b6 100644 --- a/extensions/authn/test_utils.h +++ b/extensions/authn/test_utils.h @@ -19,9 +19,8 @@ #include "gmock/gmock.h" #include "src/istio/authn/context.pb.h" -namespace Envoy { -namespace Http { -namespace Istio { +namespace proxy_wasm { +namespace null_plugin { namespace AuthN { namespace TestUtilities { @@ -43,7 +42,7 @@ istio::authn::Payload CreateJwtPayload(const std::string& user, istio::authn::Result AuthNResultFromString(const std::string& text) { istio::authn::Result result; - EXPECT_TRUE(Protobuf::TextFormat::ParseFromString(text, &result)); + EXPECT_TRUE(Envoy::Protobuf::TextFormat::ParseFromString(text, &result)); return result; } @@ -51,4 +50,3 @@ istio::authn::Result AuthNResultFromString(const std::string& text) { } // namespace AuthN } // namespace Istio } // namespace Http -} // namespace Envoy