1 //
2 // Copyright 2024 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h"
18
19 #include <grpc/support/time.h>
20
21 #include "absl/functional/any_invocable.h"
22 #include "absl/status/status.h"
23 #include "absl/status/statusor.h"
24 #include "absl/strings/escaping.h"
25 #include "absl/strings/str_cat.h"
26 #include "absl/strings/str_split.h"
27 #include "absl/strings/string_view.h"
28 #include "src/core/lib/iomgr/error.h"
29 #include "src/core/lib/transport/metadata.h"
30 #include "src/core/lib/transport/status_conversion.h"
31 #include "src/core/util/json/json.h"
32 #include "src/core/util/json/json_args.h"
33 #include "src/core/util/json/json_object_loader.h"
34 #include "src/core/util/json/json_reader.h"
35 #include "src/core/util/ref_counted_ptr.h"
36 #include "src/core/util/status_helper.h"
37 #include "src/core/util/uri.h"
38
39 namespace grpc_core {
40
41 //
42 // JwtTokenFetcherCallCredentials
43 //
44
45 // State held for a pending HTTP request.
46 class JwtTokenFetcherCallCredentials::HttpFetchRequest final
47 : public TokenFetcherCredentials::FetchRequest {
48 public:
HttpFetchRequest(JwtTokenFetcherCallCredentials * creds,Timestamp deadline,absl::AnyInvocable<void (absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)> on_done)49 HttpFetchRequest(
50 JwtTokenFetcherCallCredentials* creds, Timestamp deadline,
51 absl::AnyInvocable<
52 void(absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)>
53 on_done)
54 : on_done_(std::move(on_done)) {
55 GRPC_CLOSURE_INIT(&on_http_response_, OnHttpResponse, this, nullptr);
56 Ref().release(); // Ref held by HTTP request callback.
57 http_request_ = creds->StartHttpRequest(creds->pollent(), deadline,
58 &response_, &on_http_response_);
59 }
60
~HttpFetchRequest()61 ~HttpFetchRequest() override { grpc_http_response_destroy(&response_); }
62
Orphan()63 void Orphan() override {
64 http_request_.reset();
65 Unref();
66 }
67
68 private:
OnHttpResponse(void * arg,grpc_error_handle error)69 static void OnHttpResponse(void* arg, grpc_error_handle error) {
70 RefCountedPtr<HttpFetchRequest> self(static_cast<HttpFetchRequest*>(arg));
71 if (!error.ok()) {
72 // TODO(roth): It shouldn't be necessary to explicitly set the
73 // status to UNAVAILABLE here. Once the HTTP client code is
74 // migrated to stop using legacy grpc_error APIs to create
75 // statuses, we should be able to just propagate the status as-is.
76 self->on_done_(absl::UnavailableError(StatusToString(error)));
77 return;
78 }
79 if (self->response_.status != 200) {
80 grpc_status_code status_code =
81 grpc_http2_status_to_grpc_status(self->response_.status);
82 if (status_code != GRPC_STATUS_UNAVAILABLE) {
83 status_code = GRPC_STATUS_UNAUTHENTICATED;
84 }
85 self->on_done_(absl::Status(static_cast<absl::StatusCode>(status_code),
86 absl::StrCat("JWT fetch failed with status ",
87 self->response_.status)));
88 return;
89 }
90 absl::string_view body(self->response_.body, self->response_.body_length);
91 // Parse JWT token based on https://datatracker.ietf.org/doc/html/rfc7519.
92 // We don't do full verification here, just enough to extract the
93 // expiration time.
94 // First, split the 3 '.'-delimited parts.
95 std::vector<absl::string_view> parts = absl::StrSplit(body, '.');
96 if (parts.size() != 3) {
97 self->on_done_(absl::UnauthenticatedError("error parsing JWT token"));
98 return;
99 }
100 // Base64-decode the payload.
101 std::string payload;
102 if (!absl::WebSafeBase64Unescape(parts[1], &payload)) {
103 self->on_done_(absl::UnauthenticatedError("error parsing JWT token"));
104 return;
105 }
106 // Parse as JSON.
107 auto json = JsonParse(payload);
108 if (!json.ok()) {
109 self->on_done_(absl::UnauthenticatedError("error parsing JWT token"));
110 return;
111 }
112 // Extract "exp" field.
113 struct ParsedPayload {
114 uint64_t exp = 0;
115
116 static const JsonLoaderInterface* JsonLoader(const JsonArgs&) {
117 static const auto kJsonLoader = JsonObjectLoader<ParsedPayload>()
118 .Field("exp", &ParsedPayload::exp)
119 .Finish();
120 return kJsonLoader;
121 }
122 };
123 auto parsed_payload = LoadFromJson<ParsedPayload>(*json, JsonArgs(), "");
124 if (!parsed_payload.ok()) {
125 self->on_done_(absl::UnauthenticatedError("error parsing JWT token"));
126 return;
127 }
128 gpr_timespec ts = gpr_time_0(GPR_CLOCK_REALTIME);
129 ts.tv_sec = parsed_payload->exp;
130 Timestamp expiration_time = Timestamp::FromTimespecRoundDown(ts);
131 // Return token object.
132 self->on_done_(MakeRefCounted<Token>(
133 Slice::FromCopiedString(absl::StrCat("Bearer ", body)),
134 expiration_time));
135 }
136
137 OrphanablePtr<HttpRequest> http_request_;
138 grpc_closure on_http_response_;
139 grpc_http_response response_;
140 absl::AnyInvocable<void(
141 absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)>
142 on_done_;
143 };
144
145 OrphanablePtr<TokenFetcherCredentials::FetchRequest>
FetchToken(Timestamp deadline,absl::AnyInvocable<void (absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)> on_done)146 JwtTokenFetcherCallCredentials::FetchToken(
147 Timestamp deadline,
148 absl::AnyInvocable<
149 void(absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)>
150 on_done) {
151 return MakeOrphanable<HttpFetchRequest>(this, deadline, std::move(on_done));
152 }
153
154 //
155 // GcpServiceAccountIdentityCallCredentials
156 //
157
debug_string()158 std::string GcpServiceAccountIdentityCallCredentials::debug_string() {
159 return absl::StrCat("GcpServiceAccountIdentityCallCredentials(", audience_,
160 ")");
161 }
162
Type()163 UniqueTypeName GcpServiceAccountIdentityCallCredentials::Type() {
164 static UniqueTypeName::Factory kFactory("GcpServiceAccountIdentity");
165 return kFactory.Create();
166 }
167
168 OrphanablePtr<HttpRequest>
StartHttpRequest(grpc_polling_entity * pollent,Timestamp deadline,grpc_http_response * response,grpc_closure * on_complete)169 GcpServiceAccountIdentityCallCredentials::StartHttpRequest(
170 grpc_polling_entity* pollent, Timestamp deadline,
171 grpc_http_response* response, grpc_closure* on_complete) {
172 grpc_http_header header = {const_cast<char*>("Metadata-Flavor"),
173 const_cast<char*>("Google")};
174 grpc_http_request request;
175 memset(&request, 0, sizeof(grpc_http_request));
176 request.hdr_count = 1;
177 request.hdrs = &header;
178 // TODO(ctiller): Carry the memory quota in ctx and share it with the host
179 // channel. This would allow us to cancel an authentication query when under
180 // extreme memory pressure.
181 auto uri = URI::Create(
182 "http", "metadata.google.internal.",
183 "/computeMetadata/v1/instance/service-accounts/default/identity",
184 {{"audience", audience_}}, /*fragment=*/"");
185 CHECK_OK(uri); // params are hardcoded
186 auto http_request =
187 HttpRequest::Get(std::move(*uri), /*args=*/nullptr, pollent, &request,
188 deadline, on_complete, response,
189 RefCountedPtr<grpc_channel_credentials>(
190 grpc_insecure_credentials_create()));
191 http_request->Start();
192 return http_request;
193 }
194
195 } // namespace grpc_core
196