Skip to main content

Contas de Serviço

Introdução

Service Account ou conta de serviço, é o recurso utilizado para fornecer a uma aplicação ou serviço um meio de se identificar e a recursos que necessitam de uma autenticação e checagem de autorização dessa identidade para que esses possam ser acessados.

Caso de Uso

Permitir que o serviço DataCrusher execute operações em apenas um bucket BigBucket existente na Organização Big Organization.

Requisições contra API de Object Storage usualmente requerem que tais requests sejam identificadas por uma Key ID e assinadas por uma Key Secret. A Key ID está diretamente relacionada a uma Account que identifica quem é o responsável por aquele acesso e também o Tenant a qual o recurso pertencer.

(Nota: Recursos estarāo sempre associados a um Tenant ao qual uma ou mais Accounts (de pessoas ou serviços) podem gerenciar seus recursos).

Para o cenário desse caso de uso, uma idéia seria um Admin criar uma chave de acesso (composta por Api Key, Api Key ID e Api Key Secret ) e desse tal chave ao DataCrusher para que esse pudesse acessar (apenas) o BigBucket.

É nesse momento que temos um problema: Essa chave está associada ao Account de um Admin, o que significa que requests recebidas com essa chave serão identificadas como Admin, não permitindo as APIs saberem que a request originou a partir do DataCrusher não permitindo limitar seus acessos.

Para endereçar esse problema, foi implementada o Service Account, que nada mais é que uma Account que está associada a um Tenant e é gerenciada pelas pessoas Admins desse Tenant. Essas pessoas podem também criar Api Keys para essa Service Account que agora, quando utilizadas na execução de requests, as APIs podem identificar a Service Account (pela claim "sa" contida no token de acesso) que podem agora possuir seu próprio conjunto de autorizações (permissões).

Gerenciamento de Contas de Serviço

Em muitos cenários, pode ser conveniente o regenciamento de Contas de Serviço diretamente pelas APIs diponíveis, como por exemplo um sistema que automatiza criação de Contas de Serviço, criação de Chaves para essa conta de serviço, configuração de ambientes que utilizarão essas chaves, criação de privilégios, revogações, etc.

A maneira mais fácil de fazer isso, é ser criada uma Api Key que dê a essa aplicação escopo para gerenciar Service Accounts e utilizá-la em seus processos para obterem um Access Token e a partir daí, utilizar diretamente as APIs de Service Account

Para criarmos essa chave, devemos seguir os seguintes passos:

  • Efetuar Login no ID Magalu (https://id.magalu.com) e mudar o perfil para a Organização a qual deseja gerenciar as Service Accounts Mudança para Organização

No perfil da Organização desejada, acessar o menu Avançado -> Api Keys

Perfil Organização

  • Clicar no botão + Criar API Key Gerenciamento APIKey

  • Preencher as informações solicidas e nas permissões ir em Magalu Cloud -> ID Magalu -> Manage Service Account Criação APIKey

  • Anotar os dados da Api Key

Tela APIKey

ATENÇÃO: Essa API Key não é uma API Key de conta de serviço. Esta API Key pode ser trocada por um Access Token que poderá ser utilizada para gerenciar Service Account mas a identidade presente no Token (Subscriber) é o da pessoa que criou essa Api Key.

Python CLI

O código abaixo implementa uma CLI totalmente funcional para o gerenciamento geral de Service Account e Service Account Api Key, todavia, a intenção desse código é servir apenas de referência.

  1. Salve o código abaixo como um arquivo Python (por exemplo, samanager.py) em seu computador.

  2. Edite o arquivo: Localize a variável APIKEY e substitua o valor de exemplo pela sua chave de API pessoal (criada previamente em sua conta principal na plataforma). Esta chave será usada para autenticar as operações iniciais de gerenciamento das Contas de Serviço.

"""
Exemplo CLI (quase) completa para gerenciamento de Service Accounts.\
"""
import json

import requests # pip install requests
import jwt # pip install pyjwt
import click # pip install click


APIKEY = "9d2b3580-6cf2-44ce-8466-7d98a4ecaab3" ## Sua API Key
PA_URL = "https://id.magalu.com/account"


########################################################################
# ID Magalu Service Account SDK
########################################################################
class APIKeyBasedClientSDK:
class Client:
def __init__(self, pa_url, apikey):
self.pa_url = pa_url
self.apikey = apikey
self.access_token = None

def get_endpoint(self, path):
return f"{self.pa_url}{path}"

def check_access_token(self, key):
path = "/api/v1/validate-request"
url, headers = self.prepare_request(path, key)

resp = requests.get(url, headers=headers)
resp.raise_for_status()

return resp.json().get("accessToken")

def refresh_access_token(self):
# TODO: Implement a proper refresh.
if self.access_token:
return self.access_token

self.access_token = self.check_access_token(self.apikey)
return self.access_token

def prepare_request(self, path, key=None):
url = self.get_endpoint(path)
headers = (
{"x-api-key": key}
if key
else {"Authorization": self.refresh_access_token()}
)

return url, headers

def __init__(self, pa_url, apikey):
self.client = APIKeyBasedClientSDK.Client(pa_url, apikey)
self.sa = ServiceAccount(self.client)
self.sakey = ServiceAccountAPIKey(self.client)

def decode_access_token(self, token=None):
return jwt.decode(
token if token else self.access_token,
algorithms=["RS256"],
options={"verify_signature": False},
)


class ServiceAccount:
def __init__(self, client):
self.client = client

def create(self, name, description, email_prefix):
path = "/api/v1/service-accounts"
url, headers = self.client.prepare_request(path)
payload = dict(
name=name,
description=description,
email_prefix=email_prefix
)

resp = requests.post(url, json=payload, headers=headers)
resp.raise_for_status()

return resp.json()

def list(self):
path = "/api/v1/service-accounts"
url, headers = self.client.prepare_request(path)

resp = requests.get(url, headers=headers)
resp.raise_for_status()

return resp.json()

def edit(self, sa_uuid, name, description):
path = f"/api/v1/service-accounts/{sa_uuid}"
url, headers = self.client.prepare_request(path)

payload = {}
if name:
payload.update(dict(name=name))
if description:
payload.update(dict(description=description))

resp = requests.patch(url, json=payload, headers=headers)
resp.raise_for_status()

return resp.json()

def delete(self, sa_uuid):
path = f"/api/v1/service-accounts/{sa_uuid}"
url, headers = self.client.prepare_request(path)

resp = requests.delete(url, headers=headers)
resp.raise_for_status()

return resp.json()


class ServiceAccountAPIKey:
def __init__(self, client):
self.client = client

def create(self, sa_uuid, name, description, scopes):
path = f"/api/v1/service-accounts/{sa_uuid}/api-keys"
url, headers = self.client.prepare_request(path)

_scopes = [dict(name=s) for s in scopes.split(",")]
payload = dict(name=name, description=description, scopes=_scopes)

resp = requests.post(url, json=payload, headers=headers)
resp.raise_for_status()

return resp.json()

def edit(self, sa_uuid, key_uuid, name=None, description=None, scopes=None):
path = f"/api/v1/service-accounts/{sa_uuid}/api-keys/{key_uuid}"
url, headers = self.client.prepare_request(path)

payload = {}
if name:
payload.update(dict(name=name))
if description:
payload.update(dict(description=description))
if scopes:
_scopes = [dict(name=s) for s in scopes.split(",")]
payload.update(dict(scopes=_scopes))

resp = requests.patch(url, json=payload, headers=headers)
resp.raise_for_status()

return resp.json()

def list(self, sa_uuid):
path = f"/api/v1/service-accounts/{sa_uuid}/api-keys"
url, headers = self.client.prepare_request(path)

resp = requests.get(url, headers=headers)
resp.raise_for_status()

return resp.json()

def check(self, key):
return self.client.check_access_token(key)

def revoke(self, sa_uuid, key_uuid):
path = f"/api/v1/service-accounts/{sa_uuid}/api-keys/{key_uuid}"
url, headers = self.client.prepare_request(path)

resp = requests.delete(url, headers=headers)
resp.raise_for_status()

return resp.json()


########################################################################
########################################################################


########################################################################
# Helpers
def print_json(body):
print(json.dumps(body, indent=4))


########################################################################
# Click Config
@click.group()
@click.pass_context
def cli(ctx):
ctx.obj = APIKeyBasedClientSDK(PA_URL, APIKEY)


pass_sdk = click.make_pass_decorator(APIKeyBasedClientSDK)


@cli.group()
def sa():
pass


@cli.group()
def key():
pass


########################################################################
# CLI Root
@cli.command
@pass_sdk
def show_token(sdk):
access_t = sdk.client.refresh_access_token()
decoded = sdk.decode_access_token(access_t)
print_json(decoded)

########################################################################
# CLI Service Account
@sa.command
@click.argument("name")
@click.argument("description")
@click.argument("email_prefix")
@pass_sdk
def create(sdk, name, description, email_prefix):
print_json(sdk.sa.create(name, description, email_prefix))


@sa.command
@pass_sdk
def list(sdk):
print_json(sdk.sa.list())


@sa.command
@click.argument("sa_uuid")
@pass_sdk
def delete(sdk, sa_uuid):
print_json(sdk.sa.delete(sa_uuid))


@sa.command
@click.argument("sa_uuid")
@click.option("-n", "--name", default=None)
@click.option("-d", "--description", default=None)
@pass_sdk
def edit(sdk, sa_uuid, name, description):
print_json(sdk.sa.edit(sa_uuid, name, description))

########################################################################
# CLI Service Account APIKey
@key.command
@click.argument("sa_uuid")
@click.argument("name")
@click.argument("description")
@click.argument("scopes")
@pass_sdk
def create(sdk, sa_uuid, name, description, scopes):
print_json(sdk.sakey.create(sa_uuid, name, description, scopes))


@key.command
@click.option("-d", "--description", default=None)
@click.option("-s", "--scopes", default="")
@pass_sdk
def edit(sdk, sa_uuid, key_uuid, name, description, scopes):
print_json(sdk.sakey.edit(sa_uuid, key_uuid, name, description, scopes))


@key.command
@click.argument("sa_uuid")
@pass_sdk
def list(sdk, sa_uuid):
print_json(sdk.sakey.list(sa_uuid))


@key.command
@click.argument("key")
@pass_sdk
def check(sdk, key):
token = sdk.sakey.check(key)
print_json(sdk.decode_access_token(token))


@key.command
@click.argument("sa_uuid")
@click.argument("key_uuid")
@pass_sdk
def revoke(sdk, sa_uuid, key_uuid):
print_json(sdk.sakey.revoke(sa_uuid, key_uuid))


if __name__ == "__main__":
cli()

Exemplo de Utilização: Compartilhamento Granular de Arquivos no Object Storage

Cenário: Você possui um bucket no Object Storage com múltiplos arquivos e precisa compartilhar arquivos específicos com diferentes entidades (representadas aqui por Contas de Serviço), sem que essas entidades precisem ter uma conta principal na Magalu Cloud. Cada Conta de Serviço terá acesso apenas ao(s) arquivo(s) designado(s).

Solução: Utilizaremos Contas de Serviço e políticas de bucket (Bucket Policies) para controlar o acesso.

  1. Listar Contas de Serviço Existentes: Verifique se já existem Contas de Serviço em seu tenant. Se for a primeira vez, a saída será vazia.
# Substitua 'python' por 'python3' se necessário no seu sistema
~/service_account$ python samanager.py sa list
{
"metadata": {
"page": 1,
"limit": 50
},
"data": []
}
  1. Criar uma Conta de Serviço: Execute o comando sa create, fornecendo um nome, descrição e um prefixo para o e-mail único da conta.
~/service_account$ python samanager.py sa create sa_company_001 "Service Account for Company 001" sac001

Saída Esperada (anote o uuid e o email):

{
"uuid": "ee7402be-b630-4db8-9d93-8692e6120974",
"email": "sac001@ZYW-8995.sa.idmagalu.com"
}

(Repita este passo para criar outras Contas de Serviço, se necessário, por exemplo, sa_empresa_2)

  1. Validar a Criação: Liste novamente para confirmar que a conta foi criada.
~/service_account$ python samanager.py sa list

Saída Esperada (mostrando a(s) conta(s) criada(s)):

{
"metadata": {
"page": 1,
"limit": 50
},
"data": [
{
"uuid": "ee7402be-b630-4db8-9d93-8692e6120974",
"name": "sa_company_001",
"description": "Service Account for Company 001",
"email": "sac001@ZYW-8995.sa.idmagalu.com",
"tenant": {
"uuid": "5d1a6792-29c2-4924-afe2-3a681920dfb8",
"legal_name": "Service Account Demo"
}
}
]
}
  1. Criar API Key para a Conta de Serviço: Gere credenciais para a Conta de Serviço, definindo os escopos necessários. Para interagir com o Object Storage, os escopos comuns são object-storage.read e/ou object-storage.write.
# Use o UUID da Conta de Serviço retornado no passo 2
# Exemplo: Concedendo permissão de leitura e escrita no Object Storage
python samanager.py key create ee7402be-b630-4db8-9d93-8692e6120974 key_objst_empresa_a "Chave S3 para Empresa A" "object-storage.read,object-storage.write"

Saída Esperada (anote api_key, key_pair_id, key_pair_secret):

{
"uuid": "da5db211-4332-479a-9e68-3ed7246e417b", # UUID da API Key
"api_key": "983b5d2d-9320-4500-8f7e-9facfc52798b", # Chave API (para validar/gerar token)
"key_pair_id": "65708073-df2f-4bd3-bc1f-07d85xxxxxx", # Equivalente ao Access Key ID (S3)
"key_pair_secret": "ac902901-995b-4ba0-9038-a7a750xxxxxx", # Equivalente ao Secret Access Key (S3)
"scopes_pending_approval": []
}

Ação: Use o key_pair_id e key_pair_secret para configurar a ferramenta compatível S3 (AWS CLI, MGC CLI, etc.) que será utilizada pela entidade externa. Consulte a documentação de ferramentas compatíveis

~/service_account$ python samanager.py key create  ee7402be-b630-4db8-9d93-8692e6120974 oStorageC001 "API Key for Company 001" "object-storage.read,object-storage.write"
{
"uuid": "da5db211-4332-479a-9e68-3ed7246e417b",
"api_key": "983b5d2d-9320-4500-8f7e-9facfc52798b",
"key_pair_id": "65708073-df2f-4bd3-bc1f-07d85xxxxxx",
"key_pair_secret": "ac902901-995b-4ba0-9038-a7a750xxxxxx",
"scopes_pending_approval": []
}
  • Definir a Política do Bucket (Bucket Policy): Crie um arquivo JSON (ex: policy.json) com a política que concede as permissões desejadas.
Formato do Principal para Contas de Serviço

Ao referenciar uma Conta de Serviço em uma política, o identificador Principal deve seguir o formato: "<TENANT_ID_PROPRIETARIO>:sa/<EMAIL_CONTA_SERVICO>". Exemplo: "5d1a6792-29c2-4924-afe2-3a681920dfb8:sa/sa-empresa-a@SEU-TENANT.sa.idmagalu.com"

  • Exemplo de policy.json:

    {
    "Version": "2012-10-17",
    "Statement": [
    { // mantendo todos os acessos ao dono do bucket
    "Effect": "Allow",
    "Principal": {
    "MGC": [
    "be73c102-6c1a-45ff-8d85-016f5b44fd04"
    ]
    },
    "Action": "s3:*",
    "Resource": [
    "bucket-name",
    "bucket-name/*"
    ]
    },
    { // dando acesso para download de somente 1 arquivo para a conta de serviço 1
    "Effect": "Allow",
    "Principal": {
    "MGC": [
    "be73c102-6c1a-45ff-8d85-016f5b44fd04:sa/sac001@ZCC-1904.sa.idmagalu.com"
    ]
    },
    "Action": "s3:GetObject",
    "Resource": "bucket-name/pasta1/objeto1.txt"
    },
    { // dando acesso para download de somente 1 arquivo para a conta de serviço 2
    "Effect": "Allow",
    "Principal": {
    "MGC": [
    "be73c102-6c1a-45ff-8d85-016f5b44fd04:sa/sac002@ZCC-1904.sa.idmagalu.com"
    ]
    },
    "Action": "s3:GetObject",
    "Resource": "bucket-name/pasta1/objeto2.txt"
    }
    ]
    }

    (Lembre-se de substituir os placeholders: pelo UUID do seu Tenant, nome-do-seu-bucket pelo nome real do bucket, e os e-mails das Contas de Serviço). Para mais exemplos, consulte a documentação de políticas

  • Aplicar a Política ao Bucket:

  1. Acesse o Console Magalu Cloud.
  2. Navegue até "Object Storage" -> "Buckets".
  3. Encontre seu bucket, clique nos três pontos (...) e selecione "Editar Política".
  4. Cole o conteúdo do seu JSON na área de edição e salve.
  • Acesso pela Conta de Serviço: Agora, a entidade externa, utilizando a ferramenta S3 configurada com o key_pair_id e key_pair_secret da Conta de Serviço, poderá realizar as ações permitidas pela política. Tentativas de acessar outros arquivos ou realizar outras ações serão negadas.

Eles também podem gerar URLs pré-assinadas para os objetos aos quais têm acesso s3:GetObject, permitindo o download via navegador por um tempo limitado.