From 3bd604d2c29e5d00de9282922a8d542afebb3307 Mon Sep 17 00:00:00 2001 From: SitaLakshmi Date: Mon, 6 Jun 2022 19:58:29 +0530 Subject: [PATCH 01/16] docs(samples): added client code for idtoken, adc and metadata server --- ...ApplicationDefaultCredentialsImplicit.java | 54 +++++++ .../IdTokenFromImpersonatedCredentials.java | 138 +++++++++++++++++ ...dTokenFromImpersonatedCredentialsREST.java | 146 ++++++++++++++++++ .../main/java/IdTokenFromMetadataServer.java | 110 +++++++++++++ .../main/java/IdTokenFromServiceAccount.java | 128 +++++++++++++++ .../java/IdTokenFromServiceAccountREST.java | 112 ++++++++++++++ .../src/main/java/VerifyNonGoogleIdToken.java | 113 ++++++++++++++ 7 files changed, 801 insertions(+) create mode 100644 samples/snippets/src/main/java/ApplicationDefaultCredentialsImplicit.java create mode 100644 samples/snippets/src/main/java/IdTokenFromImpersonatedCredentials.java create mode 100644 samples/snippets/src/main/java/IdTokenFromImpersonatedCredentialsREST.java create mode 100644 samples/snippets/src/main/java/IdTokenFromMetadataServer.java create mode 100644 samples/snippets/src/main/java/IdTokenFromServiceAccount.java create mode 100644 samples/snippets/src/main/java/IdTokenFromServiceAccountREST.java create mode 100644 samples/snippets/src/main/java/VerifyNonGoogleIdToken.java diff --git a/samples/snippets/src/main/java/ApplicationDefaultCredentialsImplicit.java b/samples/snippets/src/main/java/ApplicationDefaultCredentialsImplicit.java new file mode 100644 index 000000000..3f411d3dc --- /dev/null +++ b/samples/snippets/src/main/java/ApplicationDefaultCredentialsImplicit.java @@ -0,0 +1,54 @@ +import com.google.cloud.compute.v1.Instance; +import com.google.cloud.compute.v1.InstancesClient; +import java.io.IOException; + +public class ApplicationDefaultCredentialsImplicit { + + public static void main(String[] args) throws IOException { + // TODO(Developer): + // 1. Set the following environment variable before running the code. + // APPLICATION_DEFAULT_CREDENTIALS="path-to-the-service-account-json-credential-file" + // 2. Replace the below variable. + // 3. Make sure you have the necessary permission "compute.instances.list" + String projectId = "your-google-cloud-project-id"; + authenticateImplicitWithAdc(projectId); + } + + // ADC - Application Default Credentials + // When interacting with Google Cloud Client libraries, the library can auto-detect the + // credentials to use, if the "APPLICATION_DEFAULT_CREDENTIALS" is set. + // This APPLICATION_DEFAULT_CREDENTIALS is an environment variable/ configuration. + // This configuration can be made available to the code in various ways depending upon where the + // code is executed. + // Examples: + // 1. If running your code in local development environment, just set the following environment + // variable: + // APPLICATION_DEFAULT_CREDENTIALS="path-to-the-service-account-json-file" OR + // You can also set the ADC with gcloud if you have the gcloud installed: + // gcloud auth application-default login + // + // 2. When you use a Google Cloud cloud-based development environment such as Cloud Shell or + // Cloud Code, the tool uses the credentials you provided when you logged in, + // and manages any authorizations required. + // + // For more environments, see: https://cloud.devsite.corp.google.com/docs/authentication/provide-credentials-adc + // + // ADC detection is independent of the client library and language and works with all Cloud Client + // libraries. + public static void authenticateImplicitWithAdc(String project) throws IOException { + + String zone = "us-central1-a"; + // This snippet demonstrates how to initialize Cloud Compute Engine and list instances. + // Note that the credentials are not specified when constructing the client. + // Hence, the client library will look for credentials via the + // environment variable GOOGLE_APPLICATION_CREDENTIALS. + try (InstancesClient instancesClient = InstancesClient.create()) { + // Set the project and zone to retrieve instances present in the zone. + System.out.printf("Listing instances from %s in %s:", project, zone); + for (Instance zoneInstance : instancesClient.list(project, zone).iterateAll()) { + System.out.println(zoneInstance.getName()); + } + System.out.println("####### Listing instances complete #######"); + } + } +} diff --git a/samples/snippets/src/main/java/IdTokenFromImpersonatedCredentials.java b/samples/snippets/src/main/java/IdTokenFromImpersonatedCredentials.java new file mode 100644 index 000000000..7dedf37c0 --- /dev/null +++ b/samples/snippets/src/main/java/IdTokenFromImpersonatedCredentials.java @@ -0,0 +1,138 @@ +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.iamcredentials.v1.IAMCredentials; +import com.google.api.services.iamcredentials.v1.IAMCredentials.Projects.ServiceAccounts.GenerateIdToken; +import com.google.api.services.iamcredentials.v1.model.GenerateIdTokenRequest; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class IdTokenFromImpersonatedCredentials { + + public static void main(String[] args) + throws IOException, GeneralSecurityException { + // TODO(Developer): Replace the below variables before running the code. + + // Your Google Cloud project id. + String projectId = "your-google-cloud-project-id"; + + // Path to the service account json credential file. + String jsonCredentialPath = "path-to-json-credential-file"; + + // Provide the scopes that you might need to request to access Google APIs, + // depending on the level of access you need. + // Example: The following scope lets you view and manage Pub/Sub topics and subscriptions. + // For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes + String scope = "https://www.googleapis.com/auth/pubsub"; + + // The service name for which the id token is requested. Service name refers to the + // logical identifier of an API service, such as "pubsub.googleapis.com". + String targetAudience = "pubsub.googleapis.com"; + + // The service account name of the limited-privilege account for whom the credential is created. + String impersonatedServiceAccount = + "name@project.service.gserviceaccount.com"; + + getIdTokenFromImpersonatedCredentials(projectId, jsonCredentialPath, impersonatedServiceAccount, + scope, targetAudience); + } + + public static void getIdTokenFromImpersonatedCredentials(String projectId, + String jsonCredentialPath, String impersonatedServiceAccount, String scope, + String targetAudience) + throws GeneralSecurityException, IOException { + + // Initialize the IAMCredentials service with the source credential and scope. + IAMCredentials service = null; + try { + service = initService(jsonCredentialPath, scope); + } catch (IOException | GeneralSecurityException e) { + System.out.println("Unable to initialize service: \n" + e); + return; + } + + // delegates: The chained list of delegates required to grant the final accessToken. + // + // If set, the sequence of identities must have "Service Account Token Creator" capability + // granted to the preceding identity. + // For example, if set to [serviceAccountB, serviceAccountC], the source credential must have + // the Token Creator role on serviceAccountB. serviceAccountB must have the Token Creator on + // serviceAccountC. Finally, C must have Token Creator on impersonatedServiceAccount. + // + // If left unset, source credential must have that role on impersonatedServiceAccount. + List delegates = null; + + // Set the target audience and Token options. + GenerateIdTokenRequest idTokenRequest = new GenerateIdTokenRequest() + .setAudience(targetAudience) + .setDelegates(delegates) + // Setting this will include email in the id token. + .setIncludeEmail(Boolean.TRUE); + + // Generate the id token for the impersonated service account, using the generateIdToken() + // from IAMCredentials class. + GenerateIdToken idToken = service + .projects() + .serviceAccounts() + .generateIdToken( + String.format("projects/%s/serviceAccounts/%s", projectId, impersonatedServiceAccount), + idTokenRequest); + + // Verify the obtained id token. This is done at the receiving end of the OIDC endpoint. + boolean isVerified = verifyGoogleIdToken(idToken.getAccessToken(), targetAudience); + if (isVerified) { + System.out.println("Id token verified."); + return; + } + System.out.println("Unable to verify id token."); + } + + private static IAMCredentials initService(String jsonCredentialPath, String scope) + throws GeneralSecurityException, IOException { + GoogleCredentials credential = + GoogleCredentials.fromStream(new FileInputStream(jsonCredentialPath)) + .createScoped(Arrays.asList(scope)); + + // Initialize the IAMCredentials service. + return new IAMCredentials.Builder( + GoogleNetHttpTransport.newTrustedTransport(), + GsonFactory.getDefaultInstance(), + new HttpCredentialsAdapter(credential)) + .setApplicationName("service-accounts") + .build(); + } + + // Verifies the obtained Google id token. + public static boolean verifyGoogleIdToken(String idTokenString, String audience) + throws GeneralSecurityException, IOException { + // Initialize the Google id token verifier and set the audience. + GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( + GoogleNetHttpTransport.newTrustedTransport(), GsonFactory.getDefaultInstance()) + .setAudience(Collections.singletonList(audience)) + .build(); + + // Verify the id token. + GoogleIdToken idToken = verifier.verify(idTokenString); + if (idToken != null) { + Payload payload = idToken.getPayload(); + // Get the user id. + String userId = payload.getSubject(); + System.out.println("User ID: " + userId); + + // Optionally, if "INCLUDE_EMAIL" was set in the "IdTokenProvider.Option", check if the + // email was verified. + boolean emailVerified = payload.getEmailVerified(); + System.out.printf("Email verified: %s", emailVerified); + return true; + } + return false; + } +} diff --git a/samples/snippets/src/main/java/IdTokenFromImpersonatedCredentialsREST.java b/samples/snippets/src/main/java/IdTokenFromImpersonatedCredentialsREST.java new file mode 100644 index 000000000..8f3ef3a09 --- /dev/null +++ b/samples/snippets/src/main/java/IdTokenFromImpersonatedCredentialsREST.java @@ -0,0 +1,146 @@ +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.IdTokenCredentials; +import com.google.auth.oauth2.IdTokenProvider.Option; +import com.google.auth.oauth2.ImpersonatedCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class IdTokenFromImpersonatedCredentialsREST { + + public static void main(String[] args) + throws IOException, GeneralSecurityException { + // TODO(Developer): Replace the below variables before running the code. + // Path to the service account json credential file. + String jsonCredentialPath = "path-to-json-credential-file"; + + // Provide the scopes that you might need to request to access Google APIs, + // depending on the level of access you need. + // Example: The following scope lets you view and manage Pub/Sub topics and subscriptions. + // For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes + String scope = "https://www.googleapis.com/auth/pubsub"; + + // The service name for which the id token is requested. Service name refers to the + // logical identifier of an API service, such as "pubsub.googleapis.com". + String targetAudience = "pubsub.googleapis.com"; + + // The service account name of the limited-privilege account for whom the credential is created. + String impersonatedServiceAccount = + "name@project.service.gserviceaccount.com"; + + getIdTokenFromImpersonatedCredentials(jsonCredentialPath, impersonatedServiceAccount, scope, + targetAudience); + } + + // Use a service account (SA1) to impersonate as another service account (SA2) and obtain id token + // for the impersonated account. + // To obtain token for SA2, SA1 should have the "roles/iam.serviceAccountTokenCreator" permission + // on SA2. + public static void getIdTokenFromImpersonatedCredentials(String jsonCredentialPath, + String impersonatedServiceAccount, String scope, + String targetAudience) throws IOException, GeneralSecurityException { + // Initialize the Service Account Credentials class with the path to the json file. + // The caller who issues a request for the short-lived credentials. + ServiceAccountCredentials serviceAccountCredentials = ServiceAccountCredentials.fromStream( + new FileInputStream(jsonCredentialPath)); + // Restrict the scope of the service account. + serviceAccountCredentials = (ServiceAccountCredentials) serviceAccountCredentials.createScoped( + Arrays.asList("https://www.googleapis.com/auth/cloud-platform")); + + // delegates: The chained list of delegates required to grant the final accessToken. + // + // If set, the sequence of identities must have "Service Account Token Creator" capability + // granted to the preceding identity. + // For example, if set to [serviceAccountB, serviceAccountC], the source credential must have + // the Token Creator role on serviceAccountB. serviceAccountB must have the Token Creator on + // serviceAccountC. Finally, C must have Token Creator on impersonatedServiceAccount. + // + // If left unset, source credential must have that role on impersonatedServiceAccount. + List delegates = null; + + // Create the impersonated credential. + ImpersonatedCredentials impersonatedCredentials = ImpersonatedCredentials.create( + serviceAccountCredentials, + impersonatedServiceAccount, + delegates, + Arrays.asList(scope), + 300); + + // Set the impersonated credential, target audience and token options. + IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder() + .setIdTokenProvider(impersonatedCredentials) + .setTargetAudience(targetAudience) + // Setting this will include email in the id token. + .setOptions(Arrays.asList(Option.INCLUDE_EMAIL)) + .build(); + + // Make a http request with the idTokenCredentials to obtain the access token. + // stsEndpoint: The Security Token Service exchanges Google or third-party credentials for a + // short-lived access token to Google Cloud resources. + // https://cloud.google.com/iam/docs/reference/sts/rest + String stsEndpoint = "https://sts.googleapis.com/v1/token"; + makeAuthenticatedRequest(idTokenCredentials, stsEndpoint); + + // Verify the obtained id token. This is done at the receiving end of the OIDC endpoint. + boolean isVerified = verifyGoogleIdToken(idTokenCredentials.getAccessToken().getTokenValue(), + targetAudience); + if (isVerified) { + System.out.println("Id token verified."); + return; + } + System.out.println("Unable to verify id token."); + } + + // Makes a simple http get call. + public static void makeAuthenticatedRequest(IdTokenCredentials idTokenCredentials, String url) + throws IOException { + GenericUrl genericUrl = new GenericUrl(url); + HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(idTokenCredentials); + HttpTransport transport = new NetHttpTransport(); + HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl); + request.setThrowExceptionOnExecuteError(false); + HttpResponse response = request.execute(); + System.out.println(response.parseAsString()); + } + + // Verifies the obtained Google id token. + public static boolean verifyGoogleIdToken(String idTokenString, String audience) + throws GeneralSecurityException, IOException { + // Initialize the Google id token verifier and set the audience. + GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( + GoogleNetHttpTransport.newTrustedTransport(), GsonFactory.getDefaultInstance()) + .setAudience(Collections.singletonList(audience)) + .build(); + + // Verify the id token. + GoogleIdToken idToken = verifier.verify(idTokenString); + if (idToken != null) { + Payload payload = idToken.getPayload(); + // Get the user id. + String userId = payload.getSubject(); + System.out.println("User ID: " + userId); + + // Optionally, if "INCLUDE_EMAIL" was set in the "IdTokenProvider.Option", check if the + // email was verified. + boolean emailVerified = payload.getEmailVerified(); + System.out.printf("Email verified: %s", emailVerified); + return true; + } + return false; + } + +} diff --git a/samples/snippets/src/main/java/IdTokenFromMetadataServer.java b/samples/snippets/src/main/java/IdTokenFromMetadataServer.java new file mode 100644 index 000000000..3b293c07c --- /dev/null +++ b/samples/snippets/src/main/java/IdTokenFromMetadataServer.java @@ -0,0 +1,110 @@ +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.ComputeEngineCredentials; +import com.google.auth.oauth2.IdTokenCredentials; +import com.google.auth.oauth2.IdTokenProvider.Option; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; + +public class IdTokenFromMetadataServer { + + public static void main(String[] args) + throws IOException, GeneralSecurityException { + // TODO(Developer): Replace the below variables before running the code. + + // The service name for which the id token is requested. Service name refers to the + // logical identifier of an API service, such as "pubsub.googleapis.com". + String targetAudience = "pubsub.googleapis.com"; + + getIdTokenFromMetadataServer(targetAudience); + } + + // Every VM stores its metadata on a metadata server. You can query for default VM metadata, + // such as the VM's host name, instance ID, and service account information programmatically + // from within a VM. + // Here, we query the service account information from the Metadata server exposed by the + // ComputeEngine and use that information to obtain an id token. + // Appengine 2nd Generation, Cloud Run or even Kubernetes engine's also expose a + // metadata server. + // For AppEngine, see: https://cloud.google.com/appengine/docs/standard/java/accessing-instance-metadata#identifying_which_metadata_endpoint_to_use + // For CloudRun container instance, see: https://cloud.google.com/run/docs/container-contract#metadata-server + public static void getIdTokenFromMetadataServer(String targetAudience) + throws GeneralSecurityException, IOException { + + // Optionally, you can also set scopes in computeEngineCredentials. + ComputeEngineCredentials computeEngineCredentials = ComputeEngineCredentials.create(); + + IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder() + .setIdTokenProvider(computeEngineCredentials) + .setTargetAudience(targetAudience) + // Setting the id token options. + .setOptions(Arrays.asList(Option.FORMAT_FULL, Option.LICENSES_TRUE)) + .build(); + + // Make a http request with the idTokenCredentials to obtain the access token. + // stsEndpoint: The Security Token Service exchanges Google or third-party credentials for a + // short-lived access token to Google Cloud resources. + // https://cloud.google.com/iam/docs/reference/sts/rest + String stsEndpoint = "https://sts.googleapis.com/v1/token"; + makeAuthenticatedRequest(idTokenCredentials, stsEndpoint); + + // Verify the obtained id token. This is done at the receiving end of the OIDC endpoint. + boolean isVerified = verifyGoogleIdToken(idTokenCredentials.getAccessToken().getTokenValue(), + targetAudience); + if (isVerified) { + System.out.println("Id token verified."); + return; + } + System.out.println("Unable to verify id token."); + } + + // Makes a simple http get call. + public static void makeAuthenticatedRequest(IdTokenCredentials idTokenCredentials, String url) + throws IOException { + GenericUrl genericUrl = new GenericUrl(url); + HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(idTokenCredentials); + HttpTransport transport = new NetHttpTransport(); + HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl); + request.setThrowExceptionOnExecuteError(false); + HttpResponse response = request.execute(); + System.out.println(response.parseAsString()); + } + + // Verifies the obtained Google id token. + public static boolean verifyGoogleIdToken(String idTokenString, String audience) + throws GeneralSecurityException, IOException { + // Initialize the Google id token verifier and set the audience. + GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( + GoogleNetHttpTransport.newTrustedTransport(), GsonFactory.getDefaultInstance()) + .setAudience(Collections.singletonList(audience)) + .build(); + + // Verify the id token. + GoogleIdToken idToken = verifier.verify(idTokenString); + if (idToken != null) { + Payload payload = idToken.getPayload(); + // Get the user id. + String userId = payload.getSubject(); + System.out.println("User ID: " + userId); + + // Optionally, if "INCLUDE_EMAIL" was set in the "IdTokenProvider.Option", check if the + // email was verified. + boolean emailVerified = payload.getEmailVerified(); + System.out.printf("Email verified: %s", emailVerified); + return true; + } + return false; + } + +} diff --git a/samples/snippets/src/main/java/IdTokenFromServiceAccount.java b/samples/snippets/src/main/java/IdTokenFromServiceAccount.java new file mode 100644 index 000000000..9a2726a38 --- /dev/null +++ b/samples/snippets/src/main/java/IdTokenFromServiceAccount.java @@ -0,0 +1,128 @@ +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.auth.oauth2.IdToken; +import com.google.auth.oauth2.IdTokenProvider.Option; +import com.google.auth.oauth2.ServiceAccountCredentials; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class IdTokenFromServiceAccount { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException, GeneralSecurityException { + // TODO(Developer): Replace the below variables before running the code. + // Path to the service account json credential file. + String jsonCredentialPath = "path-to-json-credential-file"; + + // Provide the scopes that you might need to request to access Google APIs, + // depending on the level of access you need. + // Example: The following scope lets you view and manage Pub/Sub topics and subscriptions. + // For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes + String scope = "https://www.googleapis.com/auth/pubsub"; + + // The service name for which the id token is requested. Service name refers to the + // logical identifier of an API service, such as "pubsub.googleapis.com". + String targetAudience = "pubsub.googleapis.com"; + + getIdTokenFromServiceAccount(jsonCredentialPath, scope, targetAudience); + } + + public static void getIdTokenFromServiceAccount(String jsonCredentialPath, String scope, + String targetAudience) + throws IOException, GeneralSecurityException { + + // Initialize the Service Account Credentials class with the path to the json file. + ServiceAccountCredentials serviceAccountCredentials = ServiceAccountCredentials.fromStream( + new FileInputStream(jsonCredentialPath)); + // Restrict the scope of the service account. + serviceAccountCredentials = (ServiceAccountCredentials) serviceAccountCredentials.createScoped( + Arrays.asList(scope)); + + // Obtain the id token by providing the target audience. + // tokenOption: Enum of various credential-specific options to apply to the token. Applicable + // only for credentials obtained through Compute Engine or Impersonation. + List