Compartir a través de


Migración de una aplicación para usar conexiones sin contraseña con Azure Event Hubs para Kafka

En este artículo se explica cómo migrar de métodos de autenticación tradicionales a conexiones sin contraseña más seguras con Azure Event Hubs para Kafka.

Las solicitudes de aplicación a Azure Event Hubs para Kafka deben autenticarse. Azure Event Hubs para Kafka proporciona diferentes maneras de conectar aplicaciones de forma segura. Una de las maneras es usar un cadena de conexión. Sin embargo, debe priorizar las conexiones sin contraseña en las aplicaciones siempre que sea posible.

Las conexiones sin contraseña se admiten desde Spring Cloud Azure 4.3.0. Este artículo es una guía de migración para quitar credenciales de aplicaciones kafka de Spring Cloud Stream.

Comparación de las opciones de autenticación

Cuando la aplicación se autentica con Azure Event Hubs para Kafka, proporciona una entidad autorizada para conectar el espacio de nombres de Event Hubs. Los protocolos de Apache Kafka proporcionan varios mecanismos de autenticación simple y capa de seguridad (SASL) para la autenticación. Según los mecanismos de SASL, hay dos opciones de autenticación que puede usar para autorizar el acceso a los recursos seguros: autenticación de Microsoft Entra y autenticación de firma de acceso compartido (SAS).

Autenticación de Microsoft Entra

La autenticación de Microsoft Entra es un mecanismo para conectarse a Azure Event Hubs para Kafka mediante identidades definidas en el identificador de Microsoft Entra. Con la autenticación de Microsoft Entra, puede administrar identidades de entidad de servicio y otras servicios Microsoft en una ubicación central, lo que simplifica la administración de permisos.

El uso de Microsoft Entra ID para la autenticación proporciona las siguientes ventajas:

  • Autenticación de usuarios en los servicios de Azure de manera uniforme.
  • Administración de directivas de contraseñas y rotación de contraseñas en un solo lugar.
  • Varias formas de autenticación admitidas por el identificador de Entra de Microsoft, que pueden eliminar la necesidad de almacenar contraseñas.
  • Los clientes pueden administrar los permisos de Event Hubs mediante grupos externos (Id. de Microsoft Entra).
  • Compatibilidad con la autenticación basada en tokens para aplicaciones que se conectan a Azure Event Hubs para Kafka.

Autenticación de SAS

Event Hubs también proporciona firmas de acceso compartido (SAS) para el acceso delegado a Event Hubs para recursos de Kafka.

Aunque es posible conectarse a Azure Event Hubs para Kafka con SAS, se debe usar con precaución. Debe ser diligente para no exponer nunca los cadena de conexión en una ubicación no segura. Cualquier persona que obtenga acceso a los cadena de conexión puede autenticarse. Por ejemplo, existe el riesgo de que un usuario malintencionado pueda acceder a la aplicación si un cadena de conexión se comprueba accidentalmente en el control de código fuente, se envía a través de un correo electrónico no seguro, pegado en el chat incorrecto o visto por alguien que no debe tener permiso. En su lugar, autorizar el acceso mediante el mecanismo basado en tokens de OAuth 2.0 proporciona mayor seguridad y facilidad de uso a través de SAS. Considere la posibilidad de actualizar la aplicación para usar conexiones sin contraseña.

Introducción a las conexiones sin contraseña

Con una conexión sin contraseña, puede conectarse a servicios de Azure sin almacenar credenciales en el código de la aplicación, sus archivos de configuración o en variables de entorno.

Muchos servicios de Azure admiten conexiones sin contraseña, por ejemplo a través de Azure Managed Identity. Estas técnicas proporcionan características de seguridad sólidas que puede implementar mediante DefaultAzureCredential desde las bibliotecas cliente de Azure Identity. En este tutorial, aprenderá a actualizar una aplicación existente para usarla DefaultAzureCredential en lugar de alternativas como cadena de conexión s.

DefaultAzureCredential admite varios métodos de autenticación y determina automáticamente qué se debe usar en tiempo de ejecución. Este enfoque permite que la aplicación use diferentes métodos de autenticación en distintos entornos (desarrollo local frente a producción) sin implementar código específico del entorno.

El orden y las ubicaciones en las que DefaultAzureCredential se buscan credenciales se pueden encontrar en la información general de la biblioteca de identidades de Azure. Por ejemplo, al trabajar localmente, DefaultAzureCredential normalmente se autenticará con la cuenta que el desarrollador usó para iniciar sesión en Visual Studio. Cuando la aplicación se implemente en Azure, DefaultAzureCredential cambiará automáticamente para usar una identidad administrada. No se necesitan cambios de código para esta transición.

Para asegurarse de que las conexiones no tienen contraseña, debe tener en cuenta tanto el desarrollo local como el entorno de producción. Si se requiere una cadena de conexión en cualquier lugar, la aplicación no tiene contraseña.

En el entorno de desarrollo local, puede autenticarse con la CLI de Azure, Azure PowerShell, Visual Studio o complementos de Azure para Visual Studio Code o IntelliJ. En este caso, puede usar esa credencial en la aplicación en lugar de configurar propiedades.

Al implementar aplicaciones en un entorno de hospedaje de Azure, como una máquina virtual, puede asignar una identidad administrada en ese entorno. A continuación, no tendrá que proporcionar credenciales para conectarse a los servicios de Azure.

Nota:

Una identidad administrada proporciona una identidad de seguridad para representar una aplicación o servicio. La identidad está administrada por la plataforma Azure y no requiere que aprovisione o rote los secretos. Puede obtener más información sobre las identidades administradas en la documentación de información general.

Migración de una aplicación existente para usar conexiones sin contraseña

En los pasos siguientes se explica cómo migrar una aplicación existente para usar conexiones sin contraseña en lugar de una solución SAS.

0) Preparar el entorno de trabajo para la autenticación de desarrollo local

En primer lugar, use el siguiente comando para configurar algunas variables de entorno.

export AZ_RESOURCE_GROUP=<YOUR_RESOURCE_GROUP>
export AZ_EVENTHUBS_NAMESPACE_NAME=<YOUR_EVENTHUBS_NAMESPACE_NAME>
export AZ_EVENTHUB_NAME=<YOUR_EVENTHUB_NAME>

Reemplace los marcadores de posición por los valores siguientes, que se usan a lo largo de este artículo:

  • <YOUR_RESOURCE_GROUP>: el nombre del grupo de recursos que usará.
  • <YOUR_EVENTHUBS_NAMESPACE_NAME>: el nombre del espacio de nombres de Azure Event Hubs que usará.
  • <YOUR_EVENTHUB_NAME>: el nombre del centro de eventos que usará.

1) Concesión de permiso para Azure Event Hubs

Si quiere ejecutar este ejemplo localmente con la autenticación de Microsoft Entra, asegúrese de que la cuenta de usuario se ha autenticado a través del kit de herramientas de Azure para IntelliJ, el complemento de cuenta de Azure de Visual Studio Code o la CLI de Azure. Además, asegúrese de que a la cuenta se le han concedido permisos suficientes.

  1. En Azure Portal, localice el espacio de nombres de Event Hubs mediante la barra de búsqueda principal o el panel de navegación de la izquierda.

  2. En la página de información general de Event Hubs, seleccione Control de acceso (IAM) en el menú izquierdo.

  3. En la página Control de acceso (IAM), seleccione la pestaña Asignación de roles.

  4. Seleccione Agregar en el menú superior y, a continuación, Agregar asignación de roles en el menú desplegable resultante.

    Captura de pantalla de la página Control de acceso (IAM) de Azure Portal del recurso espacio de nombres de Event Hubs con Agregar asignación de roles resaltada.

  5. Puede usar el cuadro de búsqueda para filtrar los resultados por el rol deseado. En este ejemplo, busque Remitente de datos de Azure Event Hubs y Receptor de datos de Azure Event Hubs y seleccione el resultado coincidente y, a continuación, elija Siguiente.

  6. En Asignar acceso a, seleccione Usuario, grupo o entidad de servicio y, a continuación, elija Seleccionar miembros.

  7. En el cuadro de diálogo, busque el nombre de usuario de Microsoft Entra (normalmente su dirección de correo electrónico de user@domain) y, a continuación, elija Seleccionar en la parte inferior del cuadro de diálogo.

  8. Seleccione Revisar y asignar para ir a la página final y, a continuación, de nuevo Revisar y asignar para completar el proceso.

Para obtener más información sobre cómo conceder roles de acceso, consulte Autorización del acceso a los recursos de Event Hubs mediante el identificador de Microsoft Entra.

2) Iniciar sesión y migrar el código de la aplicación para usar conexiones sin contraseña

Para el desarrollo local, asegúrese de que está autenticado con la misma cuenta de Microsoft Entra a la que asignó el rol en Event Hubs. Puede autenticarse a través de la CLI de Azure, Visual Studio, Azure PowerShell u otras herramientas, como IntelliJ.

Inicie sesión en Azure a través de la CLI de Azure mediante el comando siguiente:

az login

A continuación, siga estos pasos para actualizar la aplicación Spring Kafka para usar conexiones sin contraseña. Aunque conceptualmente es similar, cada marco usa detalles de implementación diferentes.

  1. Dentro del proyecto, abra el archivo pom.xml y agregue la siguiente referencia:

    <dependency>
       <groupId>com.azure</groupId>
       <artifactId>azure-identity</artifactId>
       <version>1.6.0</version>
    </dependency>
    
  2. Después de la migración, implemente AuthenticateCallbackHandler y OAuthBearerToken en el proyecto para la autenticación de OAuth2, como se muestra en el ejemplo siguiente.

    public class KafkaOAuth2AuthenticateCallbackHandler implements AuthenticateCallbackHandler {
    
       private static final Duration ACCESS_TOKEN_REQUEST_BLOCK_TIME = Duration.ofSeconds(30);
       private static final String TOKEN_AUDIENCE_FORMAT = "%s://%s/.default";
    
       private Function<TokenCredential, Mono<OAuthBearerTokenImp>> resolveToken;
       private final TokenCredential credential = new DefaultAzureCredentialBuilder().build();
    
       @Override
       public void configure(Map<String, ?> configs, String mechanism, List<AppConfigurationEntry> jaasConfigEntries) {
          TokenRequestContext request = buildTokenRequestContext(configs);
          this.resolveToken = tokenCredential -> tokenCredential.getToken(request).map(OAuthBearerTokenImp::new);
       }
    
       private TokenRequestContext buildTokenRequestContext(Map<String, ?> configs) {
          URI uri = buildEventHubsServerUri(configs);
          String tokenAudience = buildTokenAudience(uri);
    
          TokenRequestContext request = new TokenRequestContext();
          request.addScopes(tokenAudience);
          return request;
       }
    
       @SuppressWarnings("unchecked")
       private URI buildEventHubsServerUri(Map<String, ?> configs) {
          String bootstrapServer = Arrays.asList(configs.get(BOOTSTRAP_SERVERS_CONFIG)).get(0).toString();
          bootstrapServer = bootstrapServer.replaceAll("\\[|\\]", "");
          URI uri = URI.create("https://" + bootstrapServer);
          return uri;
       }
    
       private String buildTokenAudience(URI uri) {
          return String.format(TOKEN_AUDIENCE_FORMAT, uri.getScheme(), uri.getHost());
       }
    
       @Override
       public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
          for (Callback callback : callbacks) {
             if (callback instanceof OAuthBearerTokenCallback) {
                OAuthBearerTokenCallback oauthCallback = (OAuthBearerTokenCallback) callback;
                this.resolveToken
                        .apply(credential)
                        .doOnNext(oauthCallback::token)
                        .doOnError(throwable -> oauthCallback.error("invalid_grant", throwable.getMessage(), null))
                        .block(ACCESS_TOKEN_REQUEST_BLOCK_TIME);
             } else {
                throw new UnsupportedCallbackException(callback);
             }
          }
       }
    
       @Override
       public void close() {
          // NOOP
       }
    }
    
    public class OAuthBearerTokenImp implements OAuthBearerToken {
        private final AccessToken accessToken;
        private final JWTClaimsSet claims;
    
        public OAuthBearerTokenImp(AccessToken accessToken) {
            this.accessToken = accessToken;
            try {
                claims = JWTParser.parse(accessToken.getToken()).getJWTClaimsSet();
            } catch (ParseException exception) {
                throw new SaslAuthenticationException("Unable to parse the access token", exception);
            }
        }
    
        @Override
        public String value() {
            return accessToken.getToken();
        }
    
        @Override
        public Long startTimeMs() {
            return claims.getIssueTime().getTime();
        }
    
        @Override
        public long lifetimeMs() {
            return claims.getExpirationTime().getTime();
        }
    
        @Override
        public Set<String> scope() {
            // Referring to https://docs.microsoft.com/azure/active-directory/develop/access-tokens#payload-claims, the scp
            // claim is a String, which is presented as a space separated list.
            return Optional.ofNullable(claims.getClaim("scp"))
                    .map(s -> Arrays.stream(((String) s)
                    .split(" "))
                    .collect(Collectors.toSet()))
                    .orElse(null);
        }
    
        @Override
        public String principalName() {
            return (String) claims.getClaim("upn");
        }
    
        public boolean isExpired() {
            return accessToken.isExpired();
        }
    }
    
  3. Al crear el productor o consumidor de Kafka, agregue la configuración necesaria para admitir el mecanismo SASL/OAUTHBEARER . En los ejemplos siguientes se muestra el aspecto del código antes y después de la migración. En ambos ejemplos, reemplace el <eventhubs-namespace> marcador de posición por el nombre del espacio de nombres de Event Hubs.

    Antes de la migración, el código debe tener un aspecto similar al del ejemplo siguiente:

    Properties properties = new Properties();
    properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, "<eventhubs-namespace>.servicebus.windows.net:9093");
    properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
    properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
    properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
    properties.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
    properties.put(SaslConfigs.SASL_JAAS_CONFIG,
            String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"$ConnectionString\" password=\"%s\";", connectionString));
    return new KafkaProducer<>(properties);
    

    Después de la migración, el código debe tener un aspecto similar al del ejemplo siguiente. En este ejemplo, reemplace el <path-to-your-KafkaOAuth2AuthenticateCallbackHandler> marcador de posición por el nombre de clase completo para el implementado KafkaOAuth2AuthenticateCallbackHandler.

    Properties properties = new Properties();
    properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, "<eventhubs-namespace>.servicebus.windows.net:9093");
    properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
    properties.put(SaslConfigs.SASL_MECHANISM, "OAUTHBEARER");
    properties.put(SaslConfigs.SASL_JAAS_CONFIG, "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required");
    properties.put(SaslConfigs.SASL_LOGIN_CALLBACK_HANDLER_CLASS, "<path-to-your-KafkaOAuth2AuthenticateCallbackHandler>");
    return new KafkaProducer<>(properties);
    

Ejecución de la aplicación de forma local

Después de realizar estos cambios de código, ejecute la aplicación localmente. La nueva configuración debe recoger las credenciales locales, suponiendo que haya iniciado sesión en un IDE compatible o una herramienta de línea de comandos, como la CLI de Azure, Visual Studio o IntelliJ. Los roles que asigne al usuario de desarrollo local en Azure permitirán a la aplicación conectarse al servicio de Azure localmente.

3) Configuración del entorno de hospedaje de Azure

Una vez configurada la aplicación para usar conexiones sin contraseña y se ejecuta localmente, el mismo código se puede autenticar en los servicios de Azure después de implementarse en Azure. Por ejemplo, una aplicación implementada en una instancia de Azure Spring Apps que tiene asignada una identidad administrada puede conectarse a Azure Event Hubs para Kafka.

En esta sección, ejecutará dos pasos para permitir que la aplicación se ejecute en un entorno de hospedaje de Azure de forma sin contraseña:

  • Asigne la identidad administrada para el entorno de hospedaje de Azure.
  • Asigne roles a la identidad administrada.

Nota:

Azure también proporciona Service Connector, que puede ayudarle a conectar el servicio de hospedaje con Event Hubs. Con Service Connector para configurar el entorno de hospedaje, puede omitir el paso de asignar roles a la identidad administrada porque Service Connector lo hará automáticamente. En la sección siguiente se describe cómo configurar el entorno de hospedaje de Azure de dos maneras: una a través de Service Connector y la otra configurando directamente cada entorno de hospedaje.

Importante

Los comandos de Service Connector requieren la CLI de Azure 2.41.0 o posterior.

Asignación de la identidad administrada para el entorno de hospedaje de Azure

En los pasos siguientes se muestra cómo asignar una identidad administrada asignada por el sistema para varios servicios de hospedaje web. La identidad administrada puede conectarse de manera segura a otros servicios de Azure mediante las configuraciones de aplicación que configuró anteriormente.

  1. En la página de información general principal de la instancia de servicio de App de Azure, seleccione Identidad en el panel de navegación.

  2. En la pestaña Asignado por el sistema, asegúrese de establecer el campo Estado en activado. Azure administra internamente una identidad asignada por el sistema y controla las tareas administrativas. Los detalles y los id. de la identidad nunca se exponen en el código.

También puede asignar una identidad administrada en un entorno de hospedaje de Azure mediante la CLI de Azure.

Puede asignar una identidad administrada a una instancia de App de Azure Service con el comando az webapp identity assign, como se muestra en el ejemplo siguiente.

export AZURE_MANAGED_IDENTITY_ID=$(az webapp identity assign \
    --resource-group $AZ_RESOURCE_GROUP \
    --name <app-service-name> \
    --query principalId \
    --output tsv)

Asignación de roles a la identidad administrada

A continuación, conceda permisos a la identidad administrada que creó para acceder al espacio de nombres de Event Hubs. Puede conceder permisos mediante la asignación de un rol a la identidad administrada, como hizo con el usuario de desarrollo local.

Si ha conectado los servicios mediante Service Connector, no es necesario completar este paso. Las siguientes configuraciones necesarias se controlaron por usted:

  • Si seleccionó una identidad administrada al crear la conexión, se creó una identidad administrada asignada por el sistema para la aplicación y asignó los roles Remitente de datos de Azure Event Hubs y Receptor de datos de Azure Event Hubs en Event Hubs.

  • Si eligió usar un cadena de conexión, el cadena de conexión se agregó como una variable de entorno de aplicación.

Prueba de la aplicación

Después de realizar estos cambios de código, vaya a la aplicación hospedada en el explorador. La aplicación debe poder conectarse correctamente a Azure Event Hubs para Kafka. Tenga en cuenta que las asignaciones de roles pueden tardar varios minutos en propagarse a través del entorno de Azure. Ahora la aplicación está configurada para ejecutarse localmente y en un entorno de producción sin que los desarrolladores tengan que administrar los secretos en la propia aplicación.

Pasos siguientes

En este tutorial, ha aprendido a migrar una aplicación a conexiones sin contraseña.

Puede leer los siguientes recursos para explorar los conceptos que se describen en este artículo con más detalle: