Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Merge firebase to master #258

Merged
merged 16 commits into from
Apr 24, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .bazelrc.jenkins
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This is from Bazel's former travis setup, to avoid blowing up the RAM usage.
startup --host_jvm_args=-Xmx8192m
startup --host_jvm_args=-Xms8192m
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not firebase related. where is it coming from

startup --batch

# This is so we understand failures better
build --verbose_failures

34 changes: 34 additions & 0 deletions contrib/endpoints/src/api_manager/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ cc_proto_library(
visibility = ["//visibility:public"],
)

cc_proto_library(
name = "security_rules_proto",
srcs = [
"proto/security_rules.proto",
],
default_runtime = "//external:protobuf",
protoc = "//external:protoc",
visibility = ["//visibility:public"],
deps = [
"//external:cc_wkt_protos",
],
)

cc_library(
name = "auth_headers",
hdrs = [
Expand Down Expand Up @@ -65,6 +78,8 @@ cc_library(
"api_manager_impl.cc",
"check_auth.cc",
"check_auth.h",
"check_security_rules.cc",
"check_security_rules.h",
"check_service_control.cc",
"check_service_control.h",
"check_workflow.cc",
Expand Down Expand Up @@ -95,11 +110,13 @@ cc_library(
":path_matcher",
":impl_headers",
":server_config_proto",
":security_rules_proto",
"//contrib/endpoints/src/api_manager/auth",
"//contrib/endpoints/src/api_manager/cloud_trace",
"//contrib/endpoints/src/api_manager/context",
"//contrib/endpoints/src/api_manager/service_control",
"//contrib/endpoints/src/api_manager/utils",
"//contrib/endpoints/src/api_manager/firebase_rules",
"//external:cc_wkt_protos",
"//external:cloud_trace",
"//external:googletest_prod",
Expand Down Expand Up @@ -288,3 +305,20 @@ cc_test(
"//external:googletest_main",
],
)

cc_test(
name = "check_security_rules_test",
size = "small",
srcs = [
"check_security_rules_test.cc",
"mock_request.h",
],
linkstatic = 1,
deps = [
":api_manager",
":mock_api_manager_environment",
":security_rules_proto",
"//external:cc_wkt_protos",
"//external:googletest_main",
],
)
2 changes: 2 additions & 0 deletions contrib/endpoints/src/api_manager/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ struct UserInfo {
// Authorized party of the incoming JWT.
// See http://openid.net/specs/openid-connect-core-1_0.html#IDToken
std::string authorized_party;
// String of claims
std::string claims;

// Returns audiences as a comma separated strings.
std::string AudiencesAsString() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -708,12 +708,19 @@ grpc_jwt_verifier_status JwtValidatorImpl::FillUserInfoAndSetExp(

// Optional field.
const grpc_json *grpc_json = grpc_jwt_claims_json(claims_);

char *json_str =
grpc_json_dump_to_string(const_cast<::grpc_json *>(grpc_json), 0);
if (json_str != nullptr) {
user_info->claims = json_str;
gpr_free(json_str);
}

const char *email = GetStringValue(grpc_json, "email");
user_info->email = email == nullptr ? "" : email;
const char *authorized_party = GetStringValue(grpc_json, "azp");
user_info->authorized_party =
authorized_party == nullptr ? "" : authorized_party;

exp_ = system_clock::from_time_t(grpc_jwt_claims_expires_at(claims_).tv_sec);

return GRPC_JWT_VERIFIER_OK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,20 @@ Status ServiceAccountToken::SetClientAuthSecret(const std::string& secret) {
void ServiceAccountToken::SetAudience(JWT_TOKEN_TYPE type,
const std::string& audience) {
GOOGLE_CHECK(type >= 0 && type < JWT_TOKEN_TYPE_MAX);
jwt_tokens_[type].set_audience(audience);
if (jwt_tokens_[type].audience() != audience) {
jwt_tokens_[type].set_token("", 0);
jwt_tokens_[type].set_audience(audience);
}
}

const std::string& ServiceAccountToken::GetAuthToken(JWT_TOKEN_TYPE type) {
return GetAuthToken(type, jwt_tokens_[type].audience());
}

const std::string& ServiceAccountToken::GetAuthToken(
JWT_TOKEN_TYPE type, const std::string& audience) {
SetAudience(type, audience);

// Uses authentication secret if available.
if (!client_auth_secret_.empty()) {
GOOGLE_CHECK(type >= 0 && type < JWT_TOKEN_TYPE_MAX);
Expand Down
11 changes: 11 additions & 0 deletions contrib/endpoints/src/api_manager/auth/service_account_token.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class ServiceAccountToken {
enum JWT_TOKEN_TYPE {
JWT_TOKEN_FOR_SERVICE_CONTROL = 0,
JWT_TOKEN_FOR_CLOUD_TRACING,
JWT_TOKEN_FOR_FIREBASE,

// JWT token for accessing the http endpoints defined in Firebase Rules.
JWT_TOKEN_FOR_AUTHORIZATION_SERVICE,
JWT_TOKEN_FOR_QUOTA_CONTROL,
JWT_TOKEN_TYPE_MAX,
};
Expand All @@ -75,6 +79,13 @@ class ServiceAccountToken {
// Otherwise, use the access token fetched from metadata server.
const std::string& GetAuthToken(JWT_TOKEN_TYPE type);

// Gets the auth token to access Google services. This method accepts an
// audience parameter to set when generating JWT token.
// If client auth secret is specified, use it to calcualte JWT token.
// Otherwise, use the access token fetched from metadata server.
const std::string& GetAuthToken(JWT_TOKEN_TYPE type,
const std::string& audience);

private:
// Stores base token info. Used for both OAuth and JWT tokens.
class TokenInfo {
Expand Down
2 changes: 2 additions & 0 deletions contrib/endpoints/src/api_manager/check_auth.cc
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ void AuthChecker::CheckAudience(bool cache_hit) {
context_->set_auth_audience(audience);
context_->set_auth_authorized_party(user_info_.authorized_party);

context_->set_auth_claims(user_info_.claims);

// Remove http/s header and trailing '/' for issuer.
std::string issuer = utils::GetUrlContent(user_info_.issuer);
if (!context_->method()->isIssuerAllowed(issuer)) {
Expand Down
230 changes: 230 additions & 0 deletions contrib/endpoints/src/api_manager/check_security_rules.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// Copyright 2017 Google Inc. 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 "contrib/endpoints/src/api_manager/check_security_rules.h"
#include <iostream>
#include <sstream>
#include "contrib/endpoints/src/api_manager/auth/lib/json_util.h"
#include "contrib/endpoints/src/api_manager/firebase_rules/firebase_request.h"
#include "contrib/endpoints/src/api_manager/utils/marshalling.h"

using ::google::api_manager::auth::GetStringValue;
using ::google::api_manager::firebase_rules::FirebaseRequest;
using ::google::api_manager::utils::Status;
const char kFirebaseAudience[] =
"https://staging-firebaserules.sandbox.googleapis.com/"
"google.firebase.rules.v1.FirebaseRulesService";

namespace google {
namespace api_manager {
namespace {

const std::string kFailedFirebaseReleaseFetch =
"Failed to fetch Firebase Release";
const std::string kFailedFirebaseTest = "Failed to execute Firebase Test";
const std::string kInvalidResponse =
"Invalid JSON response from Firebase Service";
const std::string kV1 = "/v1";
const std::string kHttpGetMethod = "GET";
const std::string kProjects = "/projects";
const std::string kReleases = "/releases";
const std::string kRulesetName = "rulesetName";
const std::string kContentType = "Content-Type";
const std::string kApplication = "application/json";

std::string GetReleaseName(const context::RequestContext &context) {
return context.service_context()->service_name() + ":" +
context.service_context()->service().apis(0).version();
}

std::string GetReleaseUrl(const context::RequestContext &context) {
return context.service_context()->config()->GetFirebaseServer() + kV1 +
kProjects + "/" + context.service_context()->project_id() + kReleases +
"/" + GetReleaseName(context);
}

// An AuthzChecker object is created for every incoming request. It does
// authorizaiton by calling Firebase Rules service.
class AuthzChecker : public std::enable_shared_from_this<AuthzChecker> {
public:
// Constructor
AuthzChecker(ApiManagerEnvInterface *env,
auth::ServiceAccountToken *sa_token);

// Check for Authorization success or failure
void Check(std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> continuation);

private:
// This method invokes the Firebase TestRuleset API endpoint as well as user
// defined endpoints provided by the TestRulesetResponse.
void CallNextRequest(std::function<void(Status status)> continuation);

// Parse the response for GET RELEASE API call
Status ParseReleaseResponse(const std::string &json_str,
std::string *ruleset_id);

// Invoke the HTTP call
void HttpFetch(const std::string &url, const std::string &method,
const std::string &request_body,
auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type,
const std::string &audience,
std::function<void(Status, std::string &&)> continuation);

std::shared_ptr<AuthzChecker> GetPtr() { return shared_from_this(); }

ApiManagerEnvInterface *env_;
auth::ServiceAccountToken *sa_token_;
std::unique_ptr<FirebaseRequest> request_handler_;
};

AuthzChecker::AuthzChecker(ApiManagerEnvInterface *env,
auth::ServiceAccountToken *sa_token)
: env_(env), sa_token_(sa_token) {}

void AuthzChecker::Check(
std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> final_continuation) {
// TODO: Check service config to see if "useSecurityRules" is specified.
// If so, call Firebase Rules service TestRuleset API.

if (!context->service_context()->IsRulesCheckEnabled() ||
context->method() == nullptr || !context->method()->auth()) {
env_->LogDebug("Skipping Firebase Rules checks since it is disabled.");
final_continuation(Status::OK);
return;
}

// Fetch the Release attributes and get ruleset name.
auto checker = GetPtr();
HttpFetch(GetReleaseUrl(*context), kHttpGetMethod, "",
auth::ServiceAccountToken::JWT_TOKEN_FOR_FIREBASE,
kFirebaseAudience, [context, final_continuation, checker](
Status status, std::string &&body) {
std::string ruleset_id;
if (status.ok()) {
checker->env_->LogDebug(
std::string("GetReleasName succeeded with ") + body);
status = checker->ParseReleaseResponse(body, &ruleset_id);
} else {
checker->env_->LogError(std::string("GetReleaseName for ") +
GetReleaseUrl(*context.get()) +
" with status " + status.ToString());
status = Status(Code::INTERNAL, kFailedFirebaseReleaseFetch);
}

// If the parsing of the release body is successful, then call the
// Test Api for firebase rules service.
if (status.ok()) {
checker->request_handler_ = std::unique_ptr<FirebaseRequest>(
new FirebaseRequest(ruleset_id, checker->env_, context));
checker->CallNextRequest(final_continuation);
} else {
final_continuation(status);
}
});
}

void AuthzChecker::CallNextRequest(
std::function<void(Status status)> continuation) {
if (request_handler_->is_done()) {
continuation(request_handler_->RequestStatus());
return;
}

auto checker = GetPtr();
firebase_rules::HttpRequest http_request = request_handler_->GetHttpRequest();
HttpFetch(http_request.url, http_request.method, http_request.body,
http_request.token_type, http_request.audience,
[continuation, checker](Status status, std::string &&body) {

checker->env_->LogError(std::string("Response Body = ") + body);
if (status.ok() && !body.empty()) {
checker->request_handler_->UpdateResponse(body);
checker->CallNextRequest(continuation);
} else {
checker->env_->LogError(
std::string("Test API failed with ") +
(status.ok() ? "Empty Response" : status.ToString()));
status = Status(Code::INTERNAL, kFailedFirebaseTest);
continuation(status);
}
});
}

Status AuthzChecker::ParseReleaseResponse(const std::string &json_str,
std::string *ruleset_id) {
grpc_json *json = grpc_json_parse_string_with_len(
const_cast<char *>(json_str.data()), json_str.length());

if (!json) {
return Status(Code::INVALID_ARGUMENT, kInvalidResponse);
}

Status status = Status::OK;
const char *id = GetStringValue(json, kRulesetName.c_str());
*ruleset_id = (id == nullptr) ? "" : id;

if (ruleset_id->empty()) {
env_->LogError("Empty ruleset Id received from firebase service");
status = Status(Code::INTERNAL, kInvalidResponse);
} else {
env_->LogDebug(std::string("Received ruleset Id: ") + *ruleset_id);
}

grpc_json_destroy(json);
return status;
}

void AuthzChecker::HttpFetch(
const std::string &url, const std::string &method,
const std::string &request_body,
auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type,
const std::string &audience,
std::function<void(Status, std::string &&)> continuation) {
env_->LogDebug(std::string("Issue HTTP Request to url :") + url +
" method : " + method + " body: " + request_body);

std::unique_ptr<HTTPRequest> request(new HTTPRequest([continuation](
Status status, std::map<std::string, std::string> &&,
std::string &&body) { continuation(status, std::move(body)); }));

if (!request) {
continuation(Status(Code::INTERNAL, "Out of memory"), "");
return;
}

request->set_method(method).set_url(url).set_auth_token(
sa_token_->GetAuthToken(token_type, audience));

if (!request_body.empty()) {
request->set_header(kContentType, kApplication).set_body(request_body);
}

env_->RunHTTPRequest(std::move(request));
}

} // namespace

void CheckSecurityRules(std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> continuation) {
std::shared_ptr<AuthzChecker> checker = std::make_shared<AuthzChecker>(
context->service_context()->env(),
context->service_context()->service_account_token());
checker->Check(context, continuation);
}

} // namespace api_manager
} // namespace google
Loading