S'authentifier à l'aide de jetons via la fédération d'identité de charge de travail

Ce document explique comment s'authentifier auprès de Google Cloud à l'aide de jetons via la fédération d'identité de charge de travail. Avec la fédération d'identité de charge de travail, vous pouvez autoriser les charges de travail SAP sur site ou multicloud à accéder aux ressources Google Cloud sans utiliser de clé de compte de service. Vous pouvez utiliser la fédération d'identité avec Amazon Web Services (AWS) ou avec n'importe quel fournisseur d'identité (IdP) compatible avec OpenID Connect (OIDC), tel que Microsoft Azure, ou SAML 2.0.

La fédération d'identité de charge de travail respecte la spécification d'échange de jetons OAuth 2.0. Vous fournissez un identifiant provenant de votre IdP au service de jetons de sécurité, qui vérifie l'identité sur l'identifiant, puis renvoie en échange un jeton d'accès. Vous pouvez utiliser ce jeton pour emprunter l'identité d'un compte de service et obtenir un jeton d'accès de courte durée. Le jeton d'accès de courte durée vous permet d'appeler les API Google Cloud auxquelles le compte de service a accès.

Pour l'authentification à l'aide de jetons via la fédération d'identité de charge de travail, les étapes de configuration de haut niveau sont les suivantes :

  1. Préparez votre fournisseur d'identité externe.
  2. Dans Google Cloud, configurez la fédération d'identité de charge de travail.
  3. Dans Google Cloud, créez un compte de service.
  4. Dans Google Cloud, autorisez la charge de travail externe à emprunter l'identité du compte de service.
  5. Dans le SDK ABAP pour Google Cloud, implémentez le code ABAP pour récupérer les jetons de sécurité à partir de votre fournisseur d'identité (IdP).
  6. Dans le SDK ABAP pour Google Cloud, configurez la clé client.

Tous les produits Google Cloud ne sont pas compatibles avec la fédération d'identité de charge de travail. Avant de configurer l'authentification à l'aide de la fédération d'identité de charge de travail, consultez la liste des produits compatibles et les limites. Pour en savoir plus, consultez la page Fédération des identités des employés : produits compatibles et limitations.

Préparer votre fournisseur d'identité externe

Vous devez préparer votre IdP afin que votre charge de travail SAP puisse obtenir des identifiants pouvant être échangés contre un jeton de sécurité Google OAuth 2.0.

Pour préparer votre fournisseur d'identité externe, procédez comme suit en fonction de votre fournisseur d'identité :

Configurer la fédération d'identité de charge de travail

Dans Google Cloud, configurez le pool d'identités de charge de travail et les fournisseurs.

Vous configurez un pool d'identités, qui est une entité permettant de gérer des identités externes. Vous configurez également un fournisseur de pools d'identités de charge de travail, qui est une entité décrivant une relation entre Google Cloud et votre IdP.

Pour configurer la fédération d'identité de charge de travail, procédez comme suit en fonction de votre fournisseur d'identité externe :

Remarques :

  • Numéro du projet : numéro du projet Google Cloud dans lequel vous avez créé le pool d'identités de charge de travail.
  • ID du pool : ID unique qui identifie le pool d'identités de charge de travail.
  • ID du fournisseur : ID qui identifie le fournisseur du pool d'identités de charge de travail.

Vous en avez besoin pour la configuration de la clé client du SDK ABAP.

Créer un compte de service

Dans la console Google Cloud, créez un compte de service IAM dédié pour accéder aux API Google Cloud. Ce compte de service doit être un compte principal dans le projet Google Cloud contenant les API Google Cloud que vous prévoyez d'utiliser à l'aide du SDK.

  1. Dans la console Google Cloud, activez l'API Service Account Credentials IAM, l'API Security Token Service et toute autre API compatible à laquelle vous prévoyez d'accéder à l'aide du SDK.

    Accéder à la bibliothèque d'API

    Pour en savoir plus sur l'activation des API Google Cloud, consultez la page Activer des API.

  2. Créez un compte de service qui représente la charge de travail.

  3. Attribuez au compte de service les rôles IAM requis pour accéder aux fonctionnalités de l'API. Pour comprendre les exigences de rôle pour les API Google Cloud, consultez la documentation de l'API concernée et suivez le principe du moindre privilège. Pour en savoir plus sur les rôles prédéfinis spécifiques à l'API, consultez la page Rechercher des rôles IAM pour les API Google Cloud.

Autoriser la charge de travail externe à emprunter l'identité du compte de service

Pour autoriser la charge de travail externe à emprunter l'identité du compte de service, procédez comme suit en fonction de votre fournisseur d'identité externe :

Implémentez le code ABAP pour récupérer les jetons de sécurité à partir de votre IdP

Le SDK ABAP pour Google Cloud fournit une classe abstraite /GOOG/CL_AUTH_WIF_BASE, qui dispose de la logique permettant de récupérer les jetons de sécurité OAuth 2.0 du service de jetons de sécurité, et les jetons d'accès OAuth 2.0 à partir de l'API IAM Service Account Credentials. En tant que développeur, vous devez créer une classe enfant dans votre espace de noms qui hérite de la classe abstraite /GOOG/CL_AUTH_WIF_BASE.

Pour appeler des fonctions Cloud Run à partir du SDK ABAP pour Google Cloud à l'aide de la fédération d'identité de charge de travail, le SDK fournit une autre classe abstraite /GOOG/CL_AUTH_WIF_ID_TOKEN. Si vous configurez l'authentification à l'aide de Workload Identity Federation, vous devez créer une autre classe enfant dans votre espace de noms qui hérite de la classe abstraite /GOOG/CL_AUTH_WIF_ID_TOKEN. Vous spécifiez cette classe enfant dans le champ Classe d'autorisation lors de la configuration de la clé client pour appeler Cloud Run Functions.

Veillez à implémenter la méthode GET_EXT_IDP_TOKEN dans la classe enfant et à écrire la logique permettant d'obtenir le jeton de sécurité de votre IdP. Complétez les champs suivants :

  • CV_TOKEN : jeton récupéré à partir de votre fournisseur d'identité au format string.
  • CV_TOKEN_TYPE : type de jeton de sécurité récupéré à partir de votre fournisseur d'identité. Les types de jetons compatibles sont les suivants :
    • urn:ietf:params:oauth:token-type:jwt
    • urn:ietf:params:oauth:token-type:id_token
    • urn:ietf:params:aws:token-type:aws4_request
    • urn:ietf:params:oauth:token-type:access_token
    • urn:ietf:params:oauth:token-type:saml2

Les valeurs renseignées dans CV_TOKEN et CV_TOKEN_TYPE sont ensuite utilisées par les méthodes de la classe abstraite /GOOG/CL_AUTH_WIF_BASE pour échanger et récupérer le jeton OAuth 2.0 final, qui est utilisé dans les appels d'API.

L'exemple suivant montre les implémentations d'exemple de la méthode GET_EXT_IDP_TOKEN pour d'autres fournisseurs de services cloud tels qu'AWS et Azure.

AWS

class ZCL_AUTH_WIF_AWS definition
  public
  inheriting from /GOOG/CL_AUTH_WIF_BASE
  final
  create public .

public section.

  types:
    BEGIN OF t_header_field,
      key type string,
      value TYPE string,
    END OF t_header_field .
  types:
    tt_header_field type STANDARD TABLE OF t_header_field WITH DEFAULT KEY .
  types:
    BEGIN OF t_token_request,
     url type string,
     method type string,
     headers type tt_header_field,
   END OF t_token_request .
protected section.

  methods GET_EXT_IDP_TOKEN
    redefinition .
private section.
ENDCLASS.



CLASS ZCL_AUTH_WIF_AWS IMPLEMENTATION.


METHOD get_ext_idp_token.
**********************************************************************
*  Copyright 2024 Google LLC                                         *
*                                                                    *
*  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                           *
*      https://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.                                *
**********************************************************************

  DATA: ls_key       TYPE /goog/client_key.

  /goog/cl_utility=>get_client_key( EXPORTING iv_keyname    = iv_keyname
                                        IMPORTING es_client_key = ls_key ).


  DATA: lv_awsdate TYPE string.

  DATA: lv_date         TYPE dats,
        lv_time         TYPE tims,
        lv_timestamp    TYPE timestampl,
        lv_tz_utc       TYPE timezone VALUE 'UTC',
        lv_awsts        TYPE string,
        lv_timechar(32) TYPE c.

  GET TIME STAMP FIELD lv_timestamp.

  CONVERT TIME STAMP lv_timestamp TIME ZONE lv_tz_utc INTO DATE lv_date TIME lv_time.
  MOVE lv_timestamp TO lv_timechar.
  CONDENSE lv_timechar.

  lv_awsdate = lv_date(4) &&
               lv_date+4(2) &&
               lv_date+6(2) &&
               'T' &&
               lv_time(2) &&
               lv_time+2(2) &&
               lv_time+4(2) &&
               'Z'.

  TRANSLATE lv_awsdate TO UPPER CASE.

  DATA: lv_lf TYPE string.
  DATA: lv_secret_key TYPE string.
  DATA: lv_accesskey TYPE string.
  DATA: lv_datepart TYPE string.
  DATA: lv_service TYPE string.
  DATA: lv_method TYPE string.

  lv_lf = cl_abap_char_utilities=>newline.
  lv_accesskey = '<Populate AWS Access Key>'.
  lv_secret_key = '<Populate AWS Secret Access Key>'.
  lv_datepart = lv_awsdate(8).
  lv_service = 'sts'.
  lv_method = 'GET'.


  DATA: lv_canonical_query_params TYPE string.
  DATA: lv_host TYPE string.
  DATA: lv_region TYPE string.
  DATA: lv_canonical_resource_path TYPE string.

  lv_canonical_query_params = 'Action=GetCallerIdentity&Version=2011-06-15'.
  lv_host = 'sts.amazonaws.com'.
  lv_region = '<Populate your AWS Region>'.   "Example: 'us-east-1'
  lv_canonical_resource_path = '/'.

  DATA: lv_canonical_header_names TYPE string.
  DATA: lv_canonical_headers TYPE string.

  lv_canonical_header_names = 'host;x-amz-date'.
  lv_canonical_headers = 'host:' && lv_host && lv_lf && 'x-amz-date:' && lv_awsdate && lv_lf.

  DATA: lv_canonical_request TYPE string.

  CONCATENATE lv_method lv_lf
              lv_canonical_resource_path lv_lf
              lv_canonical_query_params lv_lf
              lv_canonical_headers lv_lf
              lv_canonical_header_names
              INTO lv_canonical_request.

  DATA: lv_canonical_request_hash TYPE string.

  TRY.
      cl_abap_message_digest=>calculate_hash_for_char(
       EXPORTING
         if_algorithm = 'SHA-256'
         if_data = lv_canonical_request
       IMPORTING
         ef_hashstring = lv_canonical_request_hash ).
    CATCH cx_abap_message_digest.
      "Handle error
      RETURN.
  ENDTRY.

  TRANSLATE lv_canonical_request_hash TO LOWER CASE.

  DATA: lv_algorithm TYPE string.

  lv_algorithm = 'AWS4-HMAC-SHA256'.

  DATA: lv_credential_scope TYPE string.

  CONCATENATE lv_datepart '/' lv_region '/' lv_service '/' 'aws4_request' INTO lv_credential_scope.

  DATA: lv_string_to_sign TYPE string.

  CONCATENATE lv_algorithm lv_lf
              lv_awsdate lv_lf
              lv_credential_scope lv_lf
              lv_canonical_request_hash
              INTO lv_string_to_sign.

  DATA: lv_awskey TYPE string.

  CONCATENATE 'AWS4' lv_secret_key INTO lv_awskey.

  DATA: lv_ksecret TYPE xstring.

  TRY.
      lv_ksecret = cl_abap_hmac=>string_to_xstring( lv_awskey ).
    CATCH cx_abap_message_digest .
      "Handle error
      RETURN.
  ENDTRY.

  DATA: lv_kdate  TYPE xstring.
  TRY.
      cl_abap_hmac=>calculate_hmac_for_char(
        EXPORTING
           if_algorithm = 'SHA256'
           if_key = lv_ksecret
           if_data = lv_datepart
        IMPORTING
           ef_hmacxstring = lv_kdate ).
    CATCH cx_abap_message_digest. "
      "Handle error
      RETURN.
  ENDTRY.

  DATA: lv_kregion TYPE xstring.
  TRY.
      cl_abap_hmac=>calculate_hmac_for_char(
        EXPORTING
           if_algorithm = 'SHA256'
           if_key = lv_kdate
           if_data = lv_region
        IMPORTING
             ef_hmacxstring = lv_kregion ).
    CATCH cx_abap_message_digest.
      "Handle error
      RETURN.
  ENDTRY.

  DATA: lv_kservice TYPE xstring.
  TRY.
      cl_abap_hmac=>calculate_hmac_for_char(
         EXPORTING
           if_algorithm = 'SHA256'
           if_key = lv_kregion
           if_data = lv_service
           IMPORTING
             ef_hmacxstring = lv_kservice ).
    CATCH cx_abap_message_digest.
      "Handle error
      RETURN.
  ENDTRY.

  DATA: lv_ksigningkey TYPE xstring.
  TRY.
      cl_abap_hmac=>calculate_hmac_for_char(
         EXPORTING
           if_algorithm = 'SHA256'
           if_key = lv_kservice
           if_data = 'aws4_request'
         IMPORTING
             ef_hmacxstring = lv_ksigningkey ).
    CATCH cx_abap_message_digest.
      "Handle error
      RETURN.
  ENDTRY.

  DATA: lv_stringtosign TYPE string.

  lv_stringtosign = 'AWS4-HMAC-SHA256' && lv_lf &&
                   lv_awsdate && lv_lf &&
                   lv_datepart && '/' &&
                   lv_region && '/' &&
                   lv_service && '/aws4_request' && lv_lf &&
                   lv_canonical_request_hash.

  DATA: lv_ssignature TYPE string.

  TRY.
      cl_abap_hmac=>calculate_hmac_for_char(
         EXPORTING
           if_algorithm = 'SHA256'
           if_key = lv_ksigningkey
           if_data = lv_stringtosign
         IMPORTING
           ef_hmacstring = lv_ssignature ).
    CATCH cx_abap_message_digest.
      "Handle error
      RETURN.
  ENDTRY.

  TRANSLATE lv_ssignature TO LOWER CASE.

  DATA: lv_authorization_header TYPE string.

  lv_authorization_header = 'AWS4-HMAC-SHA256 Credential=' &&
                            lv_accesskey && '/' &&
                            lv_credential_scope &&
                            ', SignedHeaders=' &&
                            lv_canonical_header_names &&
                            ', Signature=' &&
                            lv_ssignature.

  DATA: ls_token_request TYPE t_token_request.

  ls_token_request-url = 'https://sts.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15'.
  ls_token_request-method = 'POST'.

  DATA: ls_header_field TYPE t_header_field.
  ls_header_field-key = 'Authorization'.
  ls_header_field-value = lv_authorization_header.
  APPEND ls_header_field TO ls_token_request-headers.

  CLEAR: ls_header_field.
  ls_header_field-key = 'host'.
  ls_header_field-value = 'sts.amazonaws.com'.
  APPEND ls_header_field TO ls_token_request-headers.

  CLEAR: ls_header_field.
  ls_header_field-key = 'x-amz-date'.
  ls_header_field-value = lv_awsdate.
  APPEND ls_header_field TO ls_token_request-headers.

  CLEAR: ls_header_field.
  ls_header_field-key = 'x-goog-cloud-target-resource'.
  ls_header_field-value = '//iam.googleapis.com/projects/' &&
                               ls_key-project_id &&
                               '/locations/global/workloadIdentityPools/' &&
                               ls_key-auth_param1 &&
                               '/providers/' &&
                               ls_key-auth_param2.
  APPEND ls_header_field TO ls_token_request-headers.

  cv_token = /ui2/cl_json=>serialize(  ls_token_request ).
  cv_token_type = 'urn:ietf:params:aws:token-type:aws4_request'.

ENDMETHOD.
ENDCLASS.

Azure

class ZCL_AUTH_WIF_AZURE definition
  public
  inheriting from /GOOG/CL_AUTH_WIF_BASE
  final
  create public .

public section.
protected section.

  methods GET_EXT_IDP_TOKEN
    redefinition .
private section.
ENDCLASS.



CLASS ZCL_AUTH_WIF_AZURE IMPLEMENTATION.


  METHOD GET_EXT_IDP_TOKEN.
**********************************************************************
*  Copyright 2024 Google LLC                                         *
*                                                                    *
*  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                           *
*      https://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.                                *
**********************************************************************

    TYPES:
      BEGIN OF t_azure_resp,
        access_token TYPE string,
      END OF t_azure_resp.

    DATA: lo_client TYPE REF TO if_http_client.

    DATA: lv_url type string.
    lv_url = 'http://169.254.169.254/metadata/identity/oauth2/token?resource=<APP_ID_URI>&api-version=2018-02-01'.
    "Replace <APP_ID_URI> with the value of Application ID URI of the application that you've configured for workload identity federation.

    cl_http_client=>create_by_url(
       EXPORTING
         url                        = lv_url
       IMPORTING
         client                     = lo_client
       EXCEPTIONS
         argument_not_found         = 1
         plugin_not_active          = 2
         internal_error             = 3
         pse_not_found              = 4
         pse_not_distrib            = 5
         pse_errors                 = 6
         oa2c_set_token_error       = 7
         oa2c_missing_authorization = 8
         oa2c_invalid_config        = 9
         oa2c_invalid_parameters    = 10
         oa2c_invalid_scope         = 11
         oa2c_invalid_grant         = 12
         OTHERS                     = 13 ).

    IF sy-subrc <> 0.
      RETURN.
    ENDIF.

    lo_client->request->set_method( 'GET' ).
    lo_client->request->set_header_field( name = 'Metadata' value = 'true' ).

    lo_client->send(
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3
        http_invalid_timeout       = 4
        OTHERS                     = 5 ).

    lo_client->propertytype_logon_popup = lo_client->co_disabled.

    lo_client->receive(
      EXCEPTIONS
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3 ).

    DATA: lv_json TYPE string.

    lv_json = lo_client->response->get_cdata( ).

    DATA: ls_azure_resp TYPE t_azure_resp.

    /goog/cl_json=>deserialize(
      EXPORTING
        json             = lv_json
      CHANGING
        data             = ls_azure_resp ).
    cv_token = ls_azure_resp-access_token.
    cv_token_type = 'urn:ietf:params:oauth:token-type:jwt'.

  ENDMETHOD.
ENDCLASS.

Configurer la clé client

  1. Dans l'interface utilisateur graphique de SAP, exécutez le code de transaction /GOOG/SDK_IMG.

    Vous pouvez également exécuter le code de transaction SPRO, puis cliquer sur SAP Reference IMG (IMG de référence SAP).

  2. Cliquez sur SDK ABAP pour Google Cloud > Paramètres de base > Configurer la clé client.
  3. Cliquez sur Nouvelles entrées.
  4. Renseignez les champs suivants :

    Champ Description
    Nom de la clé Google Cloud Spécifiez un nom de configuration de clé client.
    Nom du compte de service Google Cloud Spécifiez le nom du compte de service (au format d'adresse e-mail) créé pour accéder aux API Google Cloud à l'étape Créer un compte de service. Par exemple : sap-example-svc-acct@example-project-123456.iam.gserviceaccount.com.
    Champ d'application Google Cloud Laissez ce champ vide.
    Identifiant de projet Google Cloud Indiquez l'ID du projet Google Cloud dans lequel vous avez créé le pool d'identités de charge de travail.
    Nom de la commande Laissez ce champ vide.
    Classe d'autorisation Spécifiez la classe enfant, qui contient l'implémentation de la classe /GOOG/CL_AUTH_WIF_BASE. Pour en savoir plus, consultez la section Implémenter du code ABAP pour récupérer les jetons de sécurité à partir de votre IdP.
    Mise en cache des jetons Laissez ce champ vide.
    Secondes avant actualisation du jeton Laissez ce champ vide.
    Paramètre d'autorisation 1 Indiquez l'ID du pool d'identités de charge de travail.
    Paramètre d'autorisation 2 Indiquez l'ID du fournisseur d'identité de charge de travail.
  5. Enregistrez l'entrée.

Obtenir de l'aide

Procédez comme suit si vous avez besoin d'aide pour résoudre les problèmes liés au SDK ABAP pour Google Cloud :