• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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