O blog da AWS

Isolando tenants em SaaS com políticas IAM geradas dinamicamente

Por Bill Tarr, arquiteto sênior de soluções de parceiros, AWS SaaS Factory

AWS-SaaS-Factory-1

Muitas organizações de software como serviço (SaaS) utilizam o AWS Identity and Access Management (IAM) como a espinha dorsal de sua estratégia de isolamento de tenants.

O IAM permite que as organizações definam uma série de políticas e roles que podem ser usadas para garantir que os tenants não possam ultrapassar a fronteira de outros tenants ao acessar os recursos.

O desafio aqui é que, para fazer isso funcionar, muitas organizações precisam criar políticas separadas para cada tenant. Isso geralmente cria uma explosão de políticas de tenants que, por sua vez, podem ultrapassar os limites de conta do IAM.

Mais importante ainda, essa proliferação de políticas pode ser difícil de gerenciar e atualizar. Imagine mudar algum aspecto da sua política e implantar essa alteração em todos os tenants do seu sistema. Isso rapidamente começa a minar a capacidade de gerenciamento e a agilidade do seu ambiente.

Esta publicação da AWS SaaS Factory analisa como a geração dinâmica de políticas cria uma experiência de isolamento mais escalável e gerenciável. Este artigo enfoca os fundamentos dessa experiência, ilustrando técnicas para introduzir os mecanismos necessários para apoiar a geração dinâmica de políticas.

Fundamentos de isolamento com IAM

Antes de nos aprofundarmos, vamos dar uma olhada em uma versão simples e estática de como você pode implementar o isolamento de tenants com o IAM usando o Security Token Service (STS).

Figura 1 – Modelo de segurança multi-tenant com funções e políticas específicas do tenant

As solicitações recebidas incluem um cabeçalho de autenticação com um JSON Web Token (JWT) (1) que inclui dados que identificam o tenant atual. Esses tokens são assinados por um provedor de identidade, garantindo que o JWT não possa ser modificado e que a identidade do tenant seja confiável.

Recuperamos (2) uma política específica do tenant do IAM e pedimos ao STS que retorne uma credencial (3) definida pela nossa política. Quando tentamos acessar (4) o Amazon DynamoDB, nossa permissão (5) para fazer a chamada do SDK para getItem é verificada pelo IAM, permitindo ou rejeitando o acesso com base na política específica do tenant que recuperamos.

Esse modelo é simples e restringe o acesso a dados específicos do Amazon DynamoDB para nosso tenant. A política somente dá permissão ao nosso tenant para usar os dados do DynamoDB. No entanto, esse modelo exige que você crie uma política personalizada, como a abaixo, para cada tenant em seu sistema:

1   { 
2      "Effect": "Allow", 
3      "Action": [ 
4            "dynamodb:*" 
5       ], 
6       "Resource": [ 
7            "arn:aws:dynamodb:us-west-2:123456789012:table/Employee" 
8       ], 
9       "Condition": { 
10          "ForAllValues:StringEquals": { 
11              "dynamodb: LeadingKeys": [ "Tenant1" ] 
12          } 
13      } 
14   }

Examinaremos essa política com mais detalhes posteriormente, mas, por enquanto, observe que o identificador do tenant está codificado em nossa política (linha 11).

Para contextualizar melhor esse problema, considere que seu ambiente SaaS pode ter centenas ou até milhares de tenants. Nesse cenário, cada tenant exige seu próprio conjunto de políticas de isolamento quase idênticas. Você pode ver como o número de políticas se multiplica rapidamente à medida que cada novo tenant é adicionado ao sistema.

Considerando que existe um limite para a quantidade de roles no IAM (5.000), o que já criaria uma limitação na quantidade de tenants da solução, podemos afirmar também que sua equipe provavelmente teria dificuldade em gerenciar todos esses artefatos de isolamento.

Figura 2 – O número de funções do IAM se multiplica rapidamente à medida que você adiciona tenants

Outros problemas com essa solução podem afetar a escalabilidade e a segurança. Ao lançar novas funcionalidades para seu serviço, você precisa alterar seus recursos de IAM existentes e atualizar os processos de integração. Isso cria um forte acoplamento entre seus serviços e sua infraestrutura de segurança, o que pode aumentar a complexidade do seu processo de implantação.

Isso também limita a capacidade da sua equipe de se concentrar na entrega de novas funcionalidades. À medida que seu escopo de isolamento e segurança de tenants se torna mais difícil de manter e testar, você também aumentará a possibilidade de introduzir um erro que possa expor os dados do tenant.

Geração dinâmica de políticas

Estabelecemos alguns dos desafios que os desenvolvedores de SaaS enfrentam ao gerenciar o isolamento de tenants usando o IAM. Vamos discutir como abordamos esses problemas por meio da geração dinâmica de políticas.

Considere novamente nosso exemplo de tentar restringir o acesso de um usuário a um recurso do Amazon DynamoDB. Desta vez, porém, não armazenamos nossa política no IAM. Em vez disso, transformamos nossa política em um template em que as referências estáticas do tenant são substituídas por espaços reservados.

Os espaços reservados da tabela e do tenant no template a seguir agora podem ser hidratados com os valores apropriados em tempo de execução. Veremos algumas maneiras de facilitar os processos de hidratação do template posteriormente. Por enquanto, basta considerá-la uma string que encontraremos e substituiremos.

1  {
2    "Effect": "Allow",
3    "Action": [
4         "dynamodb:*"
5    ],
6    "Resource": [
7         "arn:aws:dynamodb:*:*:table/{{table}}"
8    ],
9    "Condition": {
10        "ForAllValues:StringEquals": {
11            "dynamodb: LeadingKeys": [ "{{tenant}}" ]
12        }
13    }
14 }

Agora que nossa política está em sua forma final, vamos examiná-la com mais detalhes. Primeiro, você notará que a ação (linha 4) neste template tem um escopo amplo. Isso nos dá a maior flexibilidade para aplicar essa permissão a uma variedade de casos de uso de segurança de tenants.

Nessa estratégia, os recursos (linha 7) não são específicos do tenant, mas observe que algumas estratégias reforçarão o isolamento do tenant no nível do recurso. O operador de condição (linha 9) limita nosso tenant a ver apenas as linhas com uma chave que começa com um valor específico do identificador do tenant (linha 11).

Para saber mais sobre essa estratégia, leia Armazenamento multi-tenant com o Amazon DynamoDB.

Assumindo uma role

Embora as políticas estejam no centro da aplicação do isolamento, ainda precisamos pensar em como essas políticas são aplicadas. É aí que entra a noção de assumir roles. A maneira mais fácil de visualizar uma role é como um conjunto de políticas, mas o verdadeiro poder das roles está em como podemos usá-las.

As roles são independentes dos usuários, portanto, um usuário pode assumir temporariamente uma role e assumir as permissões daquela role. Então, como nossa política gerada dinamicamente funciona com uma role? O STS nos permite combinar a role com a política que geramos. O STS combina as permissões em nossa role e nossa política gerada dinamicamente, para que nosso usuário receba apenas as permissões que estão presentes em nossa política e em nossa role.

Para nosso exemplo, a role deve conter uma política inline (em linha) que permita o acesso a um recurso do Amazon DynamoDB. Observe a política da role a seguir. Ela permite que qualquer pessoa acesse os recursos do DynamoDB sem nenhuma limitação específica do tenant.

1 {
2  "Version": "2012-10-17",
3  "Statement": [
4    {
5      "Action": [
6        "dynamodb:GetItem",
7        "dynamodb:BatchGetItem",
8        "dynamodb:Query",
9        "dynamodb:DescribeTable"
10      ],
11      "Resource": "arn:aws:dynamodb:us-west-2:123456789012:table/Employee",
12      "Effect": "Allow"
13    }
14  ]
15 }

Agora, examine a seguinte política gerada dinamicamente e compare-a com a política inline anterior. Essa é a política resultante depois de assumirmos nossa role usando o STS, ao mesmo tempo em que passamos nossa política gerada dinamicamente.

1 {
2  "Version": "2012-10-17",
3  "Statement": [
4    {
5      "Action": [
6        "dynamodb:GetItem",
7        "dynamodb:BatchGetItem",
8        "dynamodb:Query",
9        "dynamodb:DescribeTable"
10      ],
11      "Resource": "arn:aws:dynamodb:us-west-2:123456789012:table/Employee",
12      "Effect": "Allow",
13      "Condition": {
14        "ForAllValues:StringEquals": {
15          "dynamodb: LeadingKeys": [ "tenant1" ]
16        }
17      }
18    }
19  ]
20 }

Observe que a política inline da role reduziu o escopo de nossa lista de ações, enquanto nossa política de escopo dinâmico adicionou um operador de condição que limita o acesso aos dados codificados com nosso identificador de tenant.

Do ponto de vista do código, assumir uma role no STS é simples. Abaixo temos uma versão abreviada do código que assume uma nova role.

AssumeRoleResponse response = sts.assumeRole (ar -> ar
2       .webIdentityToken(openIdToken)
3       .policy(scopedPolicy)
4       .roleArn(role));
5  Credentials tenantCredentials = response.credentials();
6  DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
7       .credentialsProvider(tenantCredentials)
8       .build();

Quando chamamos o STS assumeRole (linha 1), basicamente passamos nossa política gerada dinamicamente (linha 3) e nossa role (linha 4). O resultado dessa chamada para o STS é uma credencial, cujo escopo foi reduzido apenas às permissões na política mostrada acima.

Os clientes do SDK do serviço, como o DynamoDbClient (linha 5), aceitam as credenciais resultantes (linha 6). Todas as chamadas feitas com esse cliente agora têm nossas permissões de escopo aplicadas.

Os benefícios das políticas geradas dinamicamente devem estar evidentes. Começamos com um modelo que exigia que criássemos e mantivéssemos uma função e uma política para cada tenant, apenas para nosso cenário único de segurança de microsserviços. Agora, temos somente que introduzir um mecanismo para gerenciar a criação das nossas políticas e assumir as roles.

Apresentando uma token vending machine

Vamos ver como simplificar o gerenciamento da geração dinâmica de políticas introduzindo uma token vending machine (máquina de venda de tokens). A função principal de uma token vending machine é criar um caminho único para a aquisição de tokens e, ao mesmo tempo, ocultar os detalhes de como esses tokens são gerenciados e criados. Isso simplifica o código em nossos microsserviços e distancia o tema de isolamento entre tenants do desenvolvimento do dia a dia.

No entanto, é igualmente importante que uma token vending machine utilize uma coleção de templates de permissão que possamos usar para gerar políticas dinamicamente em tempo de execução. Isso significa que não estamos mais criando, mantendo e nos preocupando com políticas físicas no IAM para cada tenant que contratamos.

Aqui está um modelo conceitual de uma token vending machine.

Figura 3 – Modelo conceitual de uma token vending machine

A principal conclusão da Figura 3 deve ser que o desenvolvedor da aplicação não interage mais diretamente com políticas e roles. O código da aplicação chama a token vending machine e recebe um token, que já tem as condições de segurança exigidas para o tenant.

Vamos percorrer as etapas em nosso diagrama para ver como funciona uma token vending machine.

Os cabeçalhos das chamadas HTTP incluem um cabeçalho de autenticação com um token de portador, que neste exemplo é um JSON Web Token (JWT) que contém nossa identidade de tenant (1). O JWT Manager (2) verifica o token e extrai o tenant das reivindicações (claims). Em seguida, injetamos o tenant (3) e quaisquer outras variáveis necessárias nos templates de permissão (4). Depois de carregar os templates do arquivo, as políticas são hidratadas com as variáveis que passamos.

O resultado é uma política totalmente formada e gerada dinamicamente. Essa política dinâmica substitui as muitas políticas estáticas específicas de tenants em nosso exemplo inicial. Em seguida, nossa política é usada para assumir uma role (6), que substitui as roles específicas do tenant que exigíamos anteriormente. Finalmente, a token vending machine retorna (7, 8) o token recém-criado, ou credenciais, ao nosso desenvolvedor.

Nossa implementação usa uma AWS Lambda Layer, que tem a vantagem de ser apartada do nosso código do AWS Lambda e ser implantada separadamente. Em outros ambientes, isso pode ser implantado como um arquivo Java Archive (JAR) ou como um artefato implantável separadamente.

Examinamos o modelo teórico com alguma profundidade. Vamos mergulhar no código que orienta nosso exemplo. Estas são as poucas linhas de código necessárias para obter as credenciais com escopo definido da token vending machine:

TokenVendingMachine tokenVendorMachine = new TokenVendingMachine();
2  AwsCredentialsProvider tenantCredentials = tokenVendingMachine
    .vendToken(jwtToken);

Essa chamada simplesmente passa pelo token JWT que veio como parte da chamada HTTP. As credenciais retornadas por essa chamada podem ser usadas para acessar outros recursos e ter certeza de que as políticas do tenant serão aplicadas. Seu código, por exemplo, pode usar essas credenciais para acessar o Amazon Simple Storage Service (Amazon S3):

1 S3Client s3Client = S3Client.builder()
2             .credentialsProvider(tenantCredentials)
3             .build();

Observe como essas credenciais são aplicadas ao instanciar um cliente Amazon S3. A maioria dos clientes de serviços, se não todos, exige um AWSCredentialProvider, portanto, esses tokens podem ser usados para limitar o acesso entre tenants em vários serviços.

Templates de permissão

O núcleo da token vending machine é uma coleção de arquivos de template de permissão. Já analisamos um template em nosso exemplo de políticas geradas dinamicamente. Vamos examinar como gerenciamos esses arquivos de template, permitindo que eles evoluam independentemente do nosso código.

Cada template e serviço pode ter uma abordagem diferente para descrever as políticas de isolamento. Vamos ver como você pode criar uma política do Amazon S3, por exemplo, para restringir o acesso em nível de pasta.

O template abaixo permite o acesso à ação ListBuckets (4) para os tenants que têm um prefixo correspondente ao identificador do tenant. Isso limita a capacidade do tenant de interagir com objetos em pastas que pertencem a outros tenants.

1  {
2   "Effect": "Allow", 
3   "Action": [
4     "s3:ListBucket" 
5   ], 
6   "Resource": [
7     "arn:aws:s3:::{{bucket}}" 
8   ], 
9   "Condition": {
10    "StringLike": {
11      "s3:prefix": [
12        "{{tenant}}",       
13        "{{tenant}}/",       
14        "{{tenant}}/*" 
15      ]
16    }
17  }
18 },
19 {
20  "Effect": "Allow", 
21  "Action": [
22    "s3:GetObject",
23    "s3:PutObject",
24    "s3:DeleteObject" 
25   ], 
26   "Resource": [
27        "arn:aws:s3:::{{bucket}}/{{tenant}}/*" 
28   ]
29 }

Essas duas estratégias são apenas um ponto de partida, é claro, pois várias estratégias de segurança do tenant podem ser adicionadas ao longo do tempo.

O ciclo de vida dos seus templates de permissão também é importante. Os templates são mantidos separados do seu código e são implantados e versionados de forma independente. Por exemplo, você pode ter um repositório de templates contendo todos os seus templates com seu próprio controle de versão. Como alternativa, um servidor de configuração ou um serviço de templates pode ser uma opção para separação de interesses.

Codificamos nosso exemplo como um módulo em nosso repositório. Como nosso exemplo foi escrito em Java, adicionamos um arquivo pom.xml do Maven. Você pode implantar o template como um artefato JAR.

Como os templates são apenas arquivos JSON, talvez seja preferível pensar neles como parte de sua infraestrutura, como um processo de implantação de código. Implantá-los em um sistema de arquivos como o Amazon Elastic File System (Amazon EFS) ou o Amazon S3, ambos acessíveis em toda a sua arquitetura, se encaixaria bem em uma arquitetura de microsserviços.

Gerando uma política a partir de templates

Agora que temos nossas políticas definidas fora do IAM, podemos introduzir um código que carrega nossos templates de permissão em declarações e os adiciona às políticas com escopo dinâmico em tempo de execução. Vamos considerar como hidratamos os templates de permissão para criar políticas.

Aqui está uma classe Java simples chamada PolicyGenerator, responsável pela criação de uma política:

1 String scopedPolicy = PolicyGenerator.generator()
2                .s3FolderPerTenant(bucket)
3                .dynamoLeadingKey(tableName)
4                .tenant(tenantIdentifier)
5                .generatePolicy();

O objetivo é tornar o mais fácil possível para o desenvolvedor adicionar permissões de segurança devidamente formadas e válidas. Esse processo requer acesso ao identificador do tenant que foi extraído do JWT.

Cada método de permissão que adicionamos também usa como parâmetros os valores necessários para hidratar esse template específico. A token vending machine em nosso exemplo está configurada para adicionar as permissões de que o microsserviço precisa e localizar os valores necessários a partir de variáveis de ambiente.

Conforme mostrado na Figura 4, o PolicyGenerator também pode ser dividido para que as permissões possam ser instanciadas em uma camada (como o código da aplicação) e o tenant possa ser adicionado pela token vending machine. Não queremos que nossos desenvolvedores tenham que lidar com a identidade do tenant.

 

Figura 4 – Gerador de políticas que hidrata templates em uma política

 

Esta ilustração do nosso objeto PolicyGenerator mostra como, na verdade, estamos apenas vinculando nossos arquivos de template às variáveis necessárias para hidratá-los. Nesse caso, nosso template Amazon S3 exige uma variável de bucket, e nosso template Amazon DynamoDB exige um nome de tabela. Obviamente, ambos exigem um identificador de tenant. O resultado final do nosso PolicyGenerator é uma política gerada dinamicamente.

Você pode implementar sua solução de geração de políticas com qualquer mecanismo de template que possa substituir as variáveis em um template. Uma opção é o Mustache, um mecanismo de templates simples e independente de linguagem.

Em nosso exemplo, a lista de templates produzida pelo PolicyGenerator é combinada com os dados (bucket, tabela e tenant) em uma política usando o Mustache da seguinte forma:

1 String resolvedStatements = Mustache.compiler()
2             .compile(statements)
3             .execute(data);
4 String scopedPolicy = "{ \"Version\": \"2012-10-17\",\n 
                    \"Statement\": [\n" + resolvedStatements + " ]\n}";

O mecanismo de templates (linha 2) pega uma string carregada dos arquivos de nossos templates de permissão e a hidrata (linha 3) com os dados que fornecemos como entradas, incluindo nosso tenant. A ligação externa da política (linha 4) pode ser um template em si ou apenas adicionada manualmente, como neste exemplo.

Limitação de tamanho da política

É importante ter em mente que as políticas têm um limite de tamanho. Como você está assumindo uma role do IAM, sua política é considerada uma política embutida, que tem um limite de 2.048 caracteres. Só precisamos ter certeza de que o tamanho combinado de nossas políticas geradas dinamicamente não exceda esse limite.

Se você se deparar com esse limite, há uma boa chance de estar usando sua role em muitos contextos. Considere roles específicas de um microsserviço ou aplicação, ou roles compartilhadas por um grupo de funções semelhantes. Roles mais específicas exigem menos permissões em sua política. Limitar as permissões atribuídas às roles melhora seu modelo de isolamento de tenants.

AWS Lambda Layer

Agora que examinamos detalhadamente todas as partes móveis de nossa solução, vamos discutir nossas opções de implementação. Se você se lembra, nosso exemplo do AWS Lambda faz uma única chamada de método para uma Lambda Layer para solicitar um token. Optamos por implementar uma Lambda Layer para encapsular a segurança do tenant, permitindo que a lógica da token vending machine evolua separadamente do nosso código Lambda.

A Lambda Layer nos permite implementar uma variedade de chamadas de token de segurança separadas do nosso código Lambda, como nosso exemplo do Amazon S3 e do Amazon DynamoDB. Isso também pode ser feito com o Amazon Simple Query Service (SQS), o AWS Secrets Manager, e outros serviços da AWS.

O resultado final é um código de aplicação que pode estar em conformidade com o princípio do privilégio mínimo. A aplicação só pode acessar os serviços essenciais e os dados do tenant de que precisa. A duplicação mínima do código de segurança e isolamento do tenant facilita a vida dos desenvolvedores, ajudando-os a se concentrarem em entregar funcionalidade e melhorar a escalabilidade.

O código de exemplo a seguir mostra um método de implementação da nossa Lambda Layer:

1 JwtClaimsManager jwtClaimsManager = JwtClaimsManager.builder()
2      .headers(headers)
3      .build();
4
5 String tenant = jwtClaimsManager.getTenantId(“custom:tenant_id”);
6 PolicyGenerator PolicyGenerator = PolicyGenerator.builder()
7     .s3FolderPerTenant(bucket)
8          .tenant(tenant);
9 String scopedPolicy = policyGenerator.generatePolicy();
10         
11 AssumeRoleResponse assumeRoleResponse = sts
12     .assumeRole(assumeRoleReq -> assumeRoleReq
13                  .durationSeconds(durationSeconds)
14                  .policy(scopedPolicy)
15                  .roleArn(role)
16                  .roleSessionName(tenant)
17                );
18
19 Credentials tenantCredentials = assumeRoleResponse.credentials();

Nossa Layer cria um gerador de políticas, cria uma política com escopo e a transmite junto com nossa role. Nos bastidores, invocamos o STS para assumir nossa role com nossa política gerada dinamicamente, conforme analisamos anteriormente.

Vamos examinar as partes internas da nossa Layer para ver o que está sendo feito. Embora a implementação esteja oculta em nosso código, o JwtClaimsManager (linha 1) é responsável por processar nosso token e extrair (linha 5) o identificador do tenant, que adicionamos (linha 8) ao nosso PolicyGenerator. A política resultante (linha 10) é passada para STS assumeRole (linha 12). Finalmente, recuperamos nossas credenciais (linha 19) da resposta do STS.

As tenantCredentials retornadas são as permissões do escopo do tenant que usaremos para acessar somente os serviços que habilitamos explicitamente. Em nosso exemplo, temos templates de permissão para uma tabela do Amazon DynamoDB e uma pasta do S3, portanto, a credencial resultante só pode ser usada para recuperar dados do DynamoDB e do S3 que pertencem a esse tenant.

Outras arquiteturas

Embora nosso exemplo use AWS Lambda e Lambda Layer, com algumas mudanças, as token vending machines podem ser implementadas em uma variedade de arquiteturas. Vamos ver como isso fica em uma arquitetura que não usa JWT ou AWS Lambda.

Alguns provedores de SaaS implantam agentes em ambientes remotos que se reportam aos seus servidores. Esses agentes poderiam comunicar sua identidade de tenant por meio dos subdomínios dos certificados SSL x509. Um microsserviço implantado em instâncias do Amazon Elastic Compute Cloud (Amazon EC2) recebendo dados desses agentes ainda pode usar uma token vending machine.

Como você pode ver, nosso conceito de token vending machine permaneceria semelhante.

Figura 5 – Uma arquitetura alternativa para uma token vending machine

Um proxy reverso, talvez NGINX ou KONG, encerra a conexão e adiciona a identidade do tenant a um cabeçalho personalizado. Nossa token vending machine precisa ser ajustada para consumir esse novo cabeçalho.

Obviamente, um aplicativo Amazon EC2 poderia facilmente usar um provedor de identidade e o JWT, mas é interessante considerar essa arquitetura. Apesar de usar certificados para determinar a identidade do tenant, o conceito de token vending machine permanece basicamente o mesmo.

Conclusão

A geração dinâmica de políticas pode ajudar os provedores de SaaS a implementar o isolamento de tenants. Nosso exemplo de implementação de uma token vending machine fornece um mecanismo dinâmico e gerenciável para implementar a geração dinâmica de políticas.

Ao implementar sua própria token vending machine, lembre-se de alguns pontos:

  • Os templates de permissão devem fornecer uma separação de preocupações para suas estratégias de isolamento, permitindo que elas evoluam independentemente de suas aplicações
  • As políticas devem permitir o princípio do privilégio mínimo, limitando o acesso de seus tenants a serviços e dados da forma mais restrita possível
  • A identidade do tenant deve ser resolvida de forma consistente e verificável em sua solução
  • Sua solução deve ser encapsulada e reutilizável em todos os seus serviços de SaaS, retornando uma credencial com escopo de tenant que qualquer um de seus serviços possa usar para chamar os SDKs da AWS.

Lembre-se de que analisamos apenas algumas das arquiteturas possíveis para uma token vending machine. Nossos exemplos do AWS Lambda e do Amazon EC2 demonstram como, com apenas algumas mudanças, você pode adaptar nossa arquitetura a modelos muito diferentes de token vending machines.

Para obter mais informações sobre os exemplos desta publicação, consulte o repositório do AWS SaaS Factory no GitHub. O repositório contém um exemplo de aplicação e recursos do AWS CloudFormation para provisionar automaticamente a infraestrutura necessária em sua conta da AWS.

AWS-SaaS-Factory-Banner-1

Sobre o AWS SaaS Factory

O AWS SaaS Factory ajuda organizações em qualquer estágio da jornada de SaaS. Seja para criar novos produtos, migrar aplicativos existentes ou otimizar soluções de SaaS na AWS. Visite o AWS SaaS Factory Insights Hub para descobrir mais conteúdo técnico e comercial e melhores práticas.

Os criadores de SaaS são incentivados a entrar em contato com seu representante de conta para obter informações sobre modelos de engajamento e trabalhar com a equipe do AWS SaaS Factory.

Inscreva-se para se manter informado sobre as últimas notícias, recursos e eventos de SaaS na AWS.

 

Este artigo foi traduzido do Blog da AWS em Inglês.

 


Sobre os autores

Bill Tarr é arquiteto sênior de soluções de parceiros, AWS SaaS Factory