Add get_id_token() function to service account recipe module.

Bug: b:332370221
Change-Id: I423539256980aae07f31d139c8c1d4af6e5e2444
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/5419777
Reviewed-by: Vadim Shtayura <vadimsh@chromium.org>
Commit-Queue: Vadim Shtayura <vadimsh@chromium.org>
Auto-Submit: Jard Loucks <jaredloucks@google.com>
diff --git a/README.recipes.md b/README.recipes.md
index a94c3ea..e3bed11 100644
--- a/README.recipes.md
+++ b/README.recipes.md
@@ -3839,7 +3839,7 @@
 
 #### **class [ServiceAccountApi](/recipe_modules/service_account/api.py#16)([RecipeApi](/recipe_engine/recipe_api.py#470)):**
 
-&mdash; **def [default](/recipe_modules/service_account/api.py#56)(self):**
+&mdash; **def [default](/recipe_modules/service_account/api.py#72)(self):**
 
 Returns an account associated with the task.
 
@@ -3847,7 +3847,7 @@
 protocol. When running locally this is an account the user logged in via
 "luci-auth login ..." command prior to running the recipe.
 
-&mdash; **def [from\_credentials\_json](/recipe_modules/service_account/api.py#65)(self, key_path):**
+&mdash; **def [from\_credentials\_json](/recipe_modules/service_account/api.py#81)(self, key_path):**
 
 Returns a service account based on a JSON credentials file.
 
diff --git a/recipe_modules/service_account/api.py b/recipe_modules/service_account/api.py
index 06759d4..c784bf7 100644
--- a/recipe_modules/service_account/api.py
+++ b/recipe_modules/service_account/api.py
@@ -45,7 +45,23 @@
       extra_args = []
       if self._key_path:
         extra_args = ['-service-account-json', self._key_path]
-      return self._api._get_token(self._title, extra_args, scopes)
+      full_step_title = 'get access token for %s' % self._title
+      return self._api._get_token(full_step_title, extra_args, scopes)
+
+    def get_id_token(self, audience):
+      """
+      Returns an ID token for the given audience for this service account.
+
+      Token's lifetime is guaranteed to be at least 3 minutes and at most 45.
+
+      Args:
+        audience: Intended audience, e.g. http://www.my-service.com.
+      """
+      extra_args = ["-use-id-token", "-audience", audience]
+      if self._key_path:
+        extra_args += ['-service-account-json', self._key_path]
+      full_step_title = 'get ID token for %s' % self._title
+      return self._api._get_token(full_step_title, extra_args, None)
 
     def get_email(self):
       """Returns the service account email."""
@@ -74,14 +90,14 @@
     return self.ServiceAccount(self, self.m.path.basename(key_path), key_path)
 
 
-  def _get_token(self, title, extra_args, scopes):
+  def _get_token(self, full_step_title, extra_args, scopes):
     cmd = ['luci-auth', 'token'] + extra_args
     if scopes:
       cmd += ['-scopes', ' '.join(sorted(scopes))]
     # Due to Swarming, 5 min is the hard upper limit.
     cmd += ['-lifetime', '3m']
     step_result = self.m.step(
-        'get access token for %s' % title,
+        full_step_title,
         cmd,
         infra_step=True,
         stdout=self.m.raw_io.output_text(),
diff --git a/recipe_modules/service_account/examples/full.expected/custom_scopes.json b/recipe_modules/service_account/examples/full.expected/custom_scopes.json
index 5616364..aa8c080 100644
--- a/recipe_modules/service_account/examples/full.expected/custom_scopes.json
+++ b/recipe_modules/service_account/examples/full.expected/custom_scopes.json
@@ -12,6 +12,19 @@
     "name": "get access token for default account"
   },
   {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-use-id-token",
+      "-audience",
+      "http://www.example.com",
+      "-lifetime",
+      "3m"
+    ],
+    "infra_step": true,
+    "name": "get ID token for default account"
+  },
+  {
     "name": "$result"
   }
 ]
\ No newline at end of file
diff --git a/recipe_modules/service_account/examples/full.expected/default.json b/recipe_modules/service_account/examples/full.expected/default.json
index f14d8d7..8c2f164 100644
--- a/recipe_modules/service_account/examples/full.expected/default.json
+++ b/recipe_modules/service_account/examples/full.expected/default.json
@@ -10,6 +10,19 @@
     "name": "get access token for default account"
   },
   {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-use-id-token",
+      "-audience",
+      "http://www.example.com",
+      "-lifetime",
+      "3m"
+    ],
+    "infra_step": true,
+    "name": "get ID token for default account"
+  },
+  {
     "name": "$result"
   }
 ]
\ No newline at end of file
diff --git a/recipe_modules/service_account/examples/full.expected/json_key.json b/recipe_modules/service_account/examples/full.expected/json_key.json
index f5a58a2..85e5150 100644
--- a/recipe_modules/service_account/examples/full.expected/json_key.json
+++ b/recipe_modules/service_account/examples/full.expected/json_key.json
@@ -12,6 +12,21 @@
     "name": "get access token for key_name.json"
   },
   {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-use-id-token",
+      "-audience",
+      "http://www.example.com",
+      "-service-account-json",
+      "[START_DIR]/key_name.json",
+      "-lifetime",
+      "3m"
+    ],
+    "infra_step": true,
+    "name": "get ID token for key_name.json"
+  },
+  {
     "name": "$result"
   }
 ]
\ No newline at end of file
diff --git a/recipe_modules/service_account/examples/full.expected/windows.json b/recipe_modules/service_account/examples/full.expected/windows.json
index f14d8d7..8c2f164 100644
--- a/recipe_modules/service_account/examples/full.expected/windows.json
+++ b/recipe_modules/service_account/examples/full.expected/windows.json
@@ -10,6 +10,19 @@
     "name": "get access token for default account"
   },
   {
+    "cmd": [
+      "luci-auth",
+      "token",
+      "-use-id-token",
+      "-audience",
+      "http://www.example.com",
+      "-lifetime",
+      "3m"
+    ],
+    "infra_step": true,
+    "name": "get ID token for default account"
+  },
+  {
     "name": "$result"
   }
 ]
\ No newline at end of file
diff --git a/recipe_modules/service_account/examples/full.py b/recipe_modules/service_account/examples/full.py
index 35ef7fc..fbba4c8 100644
--- a/recipe_modules/service_account/examples/full.py
+++ b/recipe_modules/service_account/examples/full.py
@@ -25,6 +25,7 @@
   else:
     account = api.service_account.default()
   account.get_access_token(scopes)
+  account.get_id_token("http://www.example.com")
 
 
 def GenTests(api):