Utilizando a lib @Identity para autenticação no consumo de serviços no Azure

Utilizando a lib @Identity para autenticação no consumo de serviços no Azure

Com a proposta de facilitar a autenticação e autorização para consumo dos recursos no Azure, a Microsoft disponibiliza de uma library que permite implementar essas funcionalidades de uma maneira simples.


Entendendo a lib

Como comentado anteriormente, a proposta da lib é disponibilizar uma maneira simples de autenticar e autorizar o consumo à partir de um serviço aos recursos do Azure.

A lib foi construída de uma maneira onde a sua implementação e a promoção do software para os estágios do desenvolvimento (desenvolvimento, homologação e produção, em sua maioria) é simples e escalável.

Com ela, podemos autenticar a aplicação utilizando uma Managed Identity ou também utilizando as credenciais de uma Service Principal. As classes existentes facilitam a integração com qualquer um dos tipos de identidade.

Acima, vemos um exemplo de alto nível sobre a integração dos diferentes tipos de identidade existentes na plataforma e o consumo de diferentes recursos.

Os recursos no destino não se limitam apenas a esses. Todos os recursos da plataforma podem ter seu consumo controlado através da atribuição das roles no portal de IAM, na console do próprio recurso.

Uma informação interessante de se mencionar é: as classes disponíveis implementam as interações com o IdP (Identity Provider) baseadas nos modelos dos protocolos OAuth 2.0 e OIDC (Open ID Connect), então ao estudar a documentação da lib, você verá diversas menções a termos como "public or confidential client", que remeterão o desenvolvedor aos conceitos dos protocolos mencionados neste parágrafo. Ter um conhecimento mínimo de como esses protocolos funcionam, permitirá ao desenvolvedor compreender as interações entre aplicação de IdP de maneira mais simples, porém nenhum conhecimento prévio é necessário para utilização da lib.


Implementando ao código

Já que falamos brevemente sobre o funcionamento da lib, vamos aos exemplos em código.

Os exemplos deste artigo serão escritos utilizando Javascript, porém o conceito das classes e a utilização da lib se aplica a qualquer outra tecnologia que você desejar utilizar.

Instalando a lib

O primieiro passo para iniciarmos a utilização dos recursos, é a instalação da lib em seu projeto. Isso pode ser feito utilizando o comando:

npm install @azure/identity

Para o comando acima funcionar, é necessário que você tenha inicializado seu projeto node antes de seguir com a instalação dos pacotes.

Classes para autenticação

Após realizar a instalação da lib, é hora de decidirmos qual tipo de classe iremos utilizar.

A Microsoft disponibiliza uma "matriz" para a tomada de decisão sobre qual credencial utilizar. Para facilitar o vosso entendimento, materializei a matriz no diagrama abaixo:

Como podemos visualizar acima, apenas com duas perguntas é possível compreender qual classe deverá ser utilizada de acordo com o seu caso de uso.

A classe "DefaultAzureCredential" deve ser criada caso seu desejo seja de que o sistema interprete qual o melhor tipo de credencial a ser utilizada.

Importando as classes

Para importar as classes para o seu projeto, utilizamos a sintaxe padrão do Javascript em projetos com NodeJS:

import { 
EnvironmentCredential, 
ManagedIdentityCredential, 
DefaultAzureCredential
 } from "@azure/identity";

As classes acima foram importadas apenas como exemplo, é necessário que você importe apenas as classes que serão utilizadas em seu projeto.

Implementando as classes importadas acima

Para fins de simplicidade no tutorial, utilizaremos apenas as classes que foram importadas no trecho de código acima. Caso você precise utilizar alguma das outras classes que não tiveram sua implementação tratada neste artigo, minha sugestão é a leitura da página oficial do github para o SDK do Entra ID para Javacript: https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/using-azure-identity.md.

EnvironmentalCredential

Essa classe é completamente dependente da criação de uma Service Principal. Service Principals podem ser criadas através da linha de comando ou através dos portais adminstrativos (Portal do Entra ou Portal do Azure).

Para criar uma service principal pela linha de comando, utilize o seguinte script:

az ad sp create-for-rbac --name "name for service principal"

A execução do comando acima gerará um retorno como o exemplo abaixo:

{
  "appId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "displayName": "name for service principal",
  "password": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "tenant": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

A única propriedade que você não fará uso é o "displayName", todas as outras serão utilizadas como variáveis de ambiente.

// Essa classe busca certas variáveis de ambiente para criação do objeto
// AZURE_TENANT_ID e AZURE_CLIENT_ID são variáveis necessárias
// AZURE_CLIENT_SECRET é a primeira variável que será buscada
// AZURE_CLIENT_CERTIFICATE_PATH é a segunda variável buscada
// AZURE_USERNAME e AZURE_PASSWORD será o par da última tentativa
const envcred = new EnvironmentCredential();

O trecho de código acima mostra a simplicidade da implementação deste tipo de classe. Bastando inicializar o objeto, o SDK contém a inteligência necessária para buscar as informações necessárias.

Em tempo de desenvolvimento, talvez seja necessário criar um arquivo ".env" para armazenar as variáveis de ambiente. Caso o projeto esteja utilizando um serviço como o Azure Functions, o recurso tem um próprio modelo de declaração de variáveis de ambiente (sendo nas Azure Functions, o arquivo local.settings.json), então não se faz necessária a criação do arquivo de variáveis.

Utilizando NodeJS, para carregar as variáveis de ambiente para o seu código, é necessário utilizar um pacote como o "dotenv", que buscará um arquivo de extensão ".env" e carregará as variáveis de ambiente no seu programa, podendo acessá-las diretamente através do objeto process.env.

Serviços recomendados para utilizar essa classe: Azure Functions, Azure App Service, Azure Virtual Machines, entre outros serviços disponíveis.

ManagedIdentityCredential

Agora falando das identidades totalmente gerenciadas, temos as Managed Identities. O interessante dessas credenciais é que não precisaremos criar nenhum tipo de segredo, muito menos de Service Principal.

Quando falamos de Managed Identities existem dois tipos: System-Assigned e User-Assigned. Os dois tipos compartilham o conceito, uma credencial completamente gerada pela plataforma, a diferença entre elas é o ciclo de vida, onde na System-Assigned temos uma credencial que será excluída no fim do ciclo de vida do recurso enquanto com as User-Assigned as credenciais devem ser excluídas de maneira explicita.

Para utilizar uma Managed Identity com a lib, criamos uma instância da seguinte classe:

// Não é necessário a configuração de nenhum tipo de parâmetro
const mngdcred = new ManagedIdentityCredential();

Essa instância de classe funcionará apenas quando o desenvolvimento estiver sendo feito diretamente no recurso, possibilitando que o objeto recupere as informações da Managed Identity atribuída.

Serviços recomendados para utilizar essa classe: Azure Functions, Azure Virtual Machines, Azure App Service, etc.

DefaultAzureCredential

Com a proposta de deixar o modelo de autenticação mais flexível, temos a classe DefaultAzureCredential. Essa classe não expõe nenhum modelo de autenticação de maneira explícita, ela abstrai o modelo de autenticação e autentica o as chamadas com as informações que tem disponível (a classe buscará informações na seguinte ordem: variáveis de ambiente, service principal, managed identity, azure cli, azure powershell e azure developer cli).

Para utilizar a classe, a instanciamos da seguinte forma:

const dfcred = new DefaultAzureCredential();

A instância da classe criada, nos permitirá consumir os recursos da plataforma passando a variável onde a armazenamos como parâmetro na chamada dos métodos!

Utilizando a identidade para consumo dos recursos (ex: Azure Service Bus)

Para exemplificarmos o funcionamento dessa lib, vamos realizar um breve teste enviando mensagens para uma instância do Azure Service Bus:

import { DefaultAzureCredential } from "@azure/identity";
import { ServiceBusClient } from "@azure/service-bus";
const messages = [
  { body: "Albert Einstein" },
  { body: "Werner Heisenberg" },
  { body: "Marie Curie" },
];
const fullyQualifiedNamespace = "<BUS NAMESPACE>.servicebus.windows.net";
const credential = new DefaultAzureCredential();
const queueName = "<QUEUE NAME>";
async function main() {
  const sbClient = new ServiceBusClient(fullyQualifiedNamespace, credential);
  const sender = sbClient.createSender(queueName);
  try {
    let batch = await sender.createMessageBatch();
    for (let i = 0; i < messages.length; i++) {
      if (!batch.tryAddMessage(messages[i])) {
        await sender.sendMessages(batch);
        batch = await sender.createMessageBatch();
        if (!batch.tryAddMessage(messages[i])) {
          throw new Error("Message too big to fit in a batch");
        }
      }
    }
    await sender.sendMessages(batch);
    console.log(`Sent a batch of messages to the queue: ${queueName}`);
    await sender.close();
  } finally {
    await sbClient.close();
  }
}
main().catch((err) => {
  console.log("Error occurred: ", err);
  process.exit(1);
});

O código acima foi retirado do repositório oficial da Microsoft para a SDK do Service Bus para Javascript. Ele contém apenas algumas modificações, para redução do tamanho do arquivo.

Neste snippet de código, utilizamos a classe "coringa" para realizar a autenticação da aplicação, porém de acordo com a sua necessidade, você pode utilizar qualquer uma das classes citadas anteriormente.

Repare que ao instânciar o objetdo do ServiceBusClient nós passamos como segundo parâmetro a instância da nossa autenticação. Esse modelo será utilizado para o consumo de qualquer outro serviço de plataforma e também para qualquer tipo de classe de autenticação escolhida.


Decidindo entre as diferentes classes

Neste texto abordamos apenas as classes que permitem a autenticação do consumo em recursos SEM interação com o usuário.

Como definido no fluxo OAuth 2.0 e OIDC, chamadas de API em nome do usuário devem ter o consentimento explícito do ator que está utilizando a aplicação, por isso que para aplicações executadas no browser, as classes utilizadas são implementadas com o fluxo diferenciado.

Agora, para as aplicações sem interação com o usuário, como é o caso deste exemplo que fizemos ao longo do artigo, as classes abordadas servirão muito bem.

O ponto central para a tomada de decisão sobre qual classe utilizar deve ser: quanto controle você deseja ter sobre o objeto de autenticação?

Utilizar uma classe que abstrai a implementação da autenticação, como é o caso da DefaultAzureCredentials, também retira uma parcela do poder de personalização do desenvolvedor, em troca, o mesmo não precisa se preocupar com nenhum tipo de configuração adicional.

Caso seja importante que você tenha acesso a certas informações fornecidas pela credencial, é recomendado que você siga com a implementação de alguma das outras classes abordadas no texto!


Espero que tenha gostado do conteúdo! O consumo seguro dos recursos em nuvem é extremamente necessário e importante, e acima de tudo, simples e efetivo, quando é possível usufruir de uma lib que permite a implementação dos métodos de maneira efetiva.

Até logo!