Autentica mediante tokens mediante la Federación de Workload Identity

En este documento, se describe cómo autenticarte en Google Cloud mediante tokens a través de la federación de identidades para cargas de trabajo. Con la federación de Workload Identity, puedes otorgar a las cargas de trabajo de SAP locales o de múltiples nubes acceso a los recursos de Google Cloud sin usar una clave de cuenta de servicio. Puedes usar la federación de Workload Identity con Amazon Web Services (AWS) o con cualquier proveedor de identidad (IdP) que admita OpenID Connect (OIDC), como Microsoft Azure o SAML 2.0.

La federación Workload Identity sigue la especificación del intercambio de tokens de OAuth 2.0. Proporciona una credencial desde tu IdP al servicio de tokens de seguridad, que verifica la identidad en la credencial y, luego, muestra un token federado a cambio. Puedes usar este token para actuar como una cuenta de servicio y obtener un token de acceso de OAuth 2.0 de corta duración. El token de acceso de corta duración te permite llamar a cualquier API de Google Cloud a la que tenga acceso la cuenta de servicio.

Para la autenticación mediante tokens a través de la federación de Workload Identity, los pasos de configuración de alto nivel son los siguientes:

  1. Prepara el IdP externo
  2. En Google Cloud, configura la federación de Workload Identity.
  3. En Google Cloud, crea una cuenta de servicio.
  4. En Google Cloud, permite que la carga de trabajo externa actúe en nombre de la cuenta de servicio.
  5. En el SDK de ABAP para Google Cloud, implementa el código ABAP para recuperar tokens de seguridad de tu IdP.
  6. En el SDK de ABAP para Google Cloud, configura la clave del cliente.

No todos los productos de Google Cloud admiten la federación de Workload Identity. Antes de configurar la autenticación mediante la federación de Workload Identity, revisa la lista de productos compatibles y las limitaciones. Para obtener más información, consulta Federación de Workload Identity: productos admitidos y limitaciones.

Prepara el IdP externo

Debes preparar tu IdP para que tu carga de trabajo de SAP pueda obtener credenciales que se puedan intercambiar por un token de seguridad de Google OAuth 2.0.

Para preparar tu IdP externo, sigue los pasos según tu IdP:

Configura la federación de Workload Identity

En Google Cloud, configura el grupo y los proveedores de Workload Identity.

Configura un grupo de identidades, que es una entidad que te permite administrar identidades externas. También debes configurar un proveedor de grupo de Workload Identity, que es una entidad que describe una relación entre Google Cloud y tu IdP.

Para configurar la federación de Workload Identity, sigue los pasos según tu IdP externo:

Toma nota de lo siguiente:

  • Número de proyecto: el número de proyecto del proyecto de Google Cloud en el que creaste el grupo de Workload Identity.
  • ID del grupo: Un ID único que identifica el grupo de identidades para cargas de trabajo.
  • ID de proveedor: Un ID que identifica al proveedor de grupos de identidades para cargas de trabajo.

Los necesitarás para la configuración de la clave del cliente del SDK de ABAP.

Crear una cuenta de servicio

En la consola de Google Cloud, crea una cuenta de servicio de IAM dedicada para acceder a las APIs de Google Cloud. Esta cuenta de servicio debe ser una principal en el proyecto de Google Cloud que contiene las APIs de Google Cloud que planeas consumir mediante el SDK.

  1. En la consola de Google Cloud, habilita la API de credenciales de la cuenta de servicio de IAM, la API del servicio de tokens de seguridad y cualquier otra API compatible que planeas acceder mediante el SDK.

    Ir a la biblioteca de API

    Para obtener información sobre cómo habilitar las APIs de Google Cloud, consulta Habilita las API.

  2. Crea una cuenta de servicio que represente la carga de trabajo.

  3. Otorga a la cuenta de servicio los roles de IAM necesarios para acceder a la funcionalidad de la API. Para comprender el requisito de rol para las APIs de Google Cloud, consulta la documentación de la API individual y sigue el principio de privilegio mínimo. Si deseas obtener más información sobre los roles predefinidos específicos de la API, consulta Encuentra funciones de IAM para las APIs de Google Cloud.

Permite que la carga de trabajo externa actúe en nombre de la cuenta de servicio

Para permitir que la carga de trabajo externa actúe en nombre de la cuenta de servicio, sigue estos pasos según tu IdP externo:

Implementa el código ABAP para recuperar tokens de seguridad de tu IdP

El SDK de ABAP para Google Cloud proporciona una clase abstracta /GOOG/CL_AUTH_WIF_BASE, que tiene la lógica para recuperar los tokens de seguridad de OAuth 2.0 del servicio de tokens de seguridad y los tokens de acceso de OAuth 2.0 desde la API de credenciales de la cuenta de servicio de IAM. Como desarrollador, debes crear una clase secundaria en tu espacio de nombres que herede de la clase abstracta /GOOG/CL_AUTH_WIF_BASE.

Para invocar Cloud Run Functions desde el SDK de ABAP para Google Cloud mediante la federación de identidades para cargas de trabajo, el SDK proporciona otra clase abstracta /GOOG/CL_AUTH_WIF_ID_TOKEN. Si configuras la autenticación con Workload Identity Federation, debes crear una clase secundaria más en tu espacio de nombres que herede de la clase abstracta /GOOG/CL_AUTH_WIF_ID_TOKEN. Debes especificar esta clase secundaria en el campo Clase de autorización mientras configuras la clave del cliente para invocar Cloud Run Functions.

Asegúrate de implementar el método GET_EXT_IDP_TOKEN en la clase secundaria y escribe la lógica para obtener el token de seguridad de tu IdP. Propaga los siguientes campos:

  • CV_TOKEN: el token recuperado de tu IdP en el formato string.
  • CV_TOKEN_TYPE: el tipo de token de seguridad recuperado de tu IdP. Los tipos de tokens compatibles son los siguientes:
    • 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

Luego, los valores propagados en CV_TOKEN y CV_TOKEN_TYPE se usan mediante métodos de la clase abstracta /GOOG/CL_AUTH_WIF_BASE para intercambiar y recuperar el token final de OAuth 2.0, que se usa en las llamadas a la API.

En el siguiente ejemplo, se muestran las implementaciones de muestra del método GET_EXT_IDP_TOKEN para otros proveedores de servicios en la nube, como AWS y 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.

Configura la clave de cliente

  1. En la GUI de SAP, ejecuta el código de transacción /GOOG/SDK_IMG.

    Como alternativa, ejecuta el código de transacción SPRO y, luego, haz clic en IMG de referencia de SAP.

  2. Haz clic en el SDK de ABAP para Google Cloud > Configuración básica > Configurar clave de cliente.
  3. Haz clic en Entradas nuevas.
  4. Ingresa los valores para los siguientes campos:

    Campo Descripción
    Nombre de la clave de Google Cloud Especifica un nombre de la configuración de la clave de cliente.
    Nombre de la cuenta de servicio de Google Cloud Especifica el nombre de la cuenta de servicio, en formato de dirección de correo electrónico, que se creó para acceder a las APIs de Google Cloud en el paso Crea una cuenta de servicio. Por ejemplo: sap-example-svc-acct@example-project-123456.iam.gserviceaccount.com.
    Permiso de Google Cloud Deja este campo en blanco.
    Identificador de proyecto de Google Cloud Especifica el ID del proyecto de Google Cloud en el que creaste el grupo de Workload Identity.
    Nombre del comando Deja este campo en blanco.
    Clase de autorización Especifica la clase secundaria, que contiene la implementación de la clase /GOOG/CL_AUTH_WIF_BASE. Si deseas obtener más información, consulta Implementa el código ABAP para recuperar tokens de seguridad de tu IdP.
    Almacenamiento en caché de tokens Deja este campo en blanco.
    Segundos de actualización de los tokens Deja este campo en blanco.
    Parámetro de autorización 1 Especifica el ID del grupo de identidades de la carga de trabajo.
    Parámetro de autorización 2 Especifica el ID del proveedor de Workload Identity.
  5. Guarda la entrada.

Obtenga asistencia

Si necesitas ayuda para resolver problemas con el SDK de ABAP para Google Cloud, haz lo siguiente: