iGRPIGRP 3.0Documentation

Registo de clientes OAuth

Como o workspace regista sozinho um cliente OAuth por microserviço e um callback de login por frontend, sem listas para manter.

O workspace regista sozinho tudo o que os seus serviços precisam no Access Management: um cliente OAuth por microserviço e um callback de login por frontend. Não há passo manual, e um projeto criado hoje pelo IGRP Studio é apanhado no próximo up sem se editar nenhuma lista.


Porque tem de ser automático

Na v2 não existe token M2M estático: cada serviço autentica-se com o seu par client_credentials e identifica-se no header X-Machine-Service-ID. Sem cliente registado, um serviço arranca, autentica-se com o que tem e falha a publicação dos seus recursos — normalmente com 404 IGRP_AUTH_RESOURCE_NOT_FOUND_BY_NAME, longe da causa real.

E há um detalhe que decide a arquitectura toda:

O clientSecret só é devolvido no momento da criação

Nunca mais é possível lê-lo. Logo não se pode escrever o segredo no .env antes de o cliente existir: tem de ser o próprio registo a criá-lo e a escrevê-lo. É isso que fixa a ordem de arranque do workspace.


Os dois mecanismos

Registo de clientesRegisto de redirects
O que criacliente OAuth + service account por serviçoos callbacks de login no cliente seeded
Quem precisabackends e frontendssó frontends
Tarefa-oauth-clients-oauth-client-uri-sync
Scriptregister-oauth-clients.shsync-default-client-uris.sh
Resultadosegredos escritos nos env-filesredirectUris do cliente seeded actualizados

São separados porque respondem a perguntas diferentes. O cliente é como o serviço fala com o Access Management (máquina a máquina); o callback é como o utilizador faz login na app (browser) — e todas as UIs partilham o mesmo cliente OIDC, o seeded, pelo que cada app só precisa que o seu caminho esteja na lista dele.

Onde isto vive no compose

Só nos ficheiros da raiz. Os composes do Process e dos projetos não registam nada, apenas consomem o resultado.

access-management                    <- arranca
  |- access-management-ready         portão
      |- ...-oauth-clients           clientes
      |- ...-oauth-client-uri-sync   redirects
      |- application-center          e o resto da stack

As três últimas dependem do portão com condition: service_completed_successfully. O portão é o wait-for-catalog.sh, que espera pela saúde do serviço e contorna o defeito de primeiro arranque.


Parte 1 — Registo dos clientes

O register-oauth-clients.sh corre uma vez e morre. Para cada serviço:

1. Espera pela API através do gateway. Pede token e faz GET /api/clients até 200, 30 tentativas de 10s. Em cada falha faz POST no GATEWAY_REFRESH_URL — depois do restart do portão o Access Management tem um IP novo e a rota antiga pode ficar em cache durante minutos, fazendo tudo em /gateway-api devolver 404.

2. Decide o que fazer, comparando o Access Management com o estado local:

Cliente existe?Segredo localAcção
simrealignora
simCHANGE_ME_*apaga e re-cria
nãoqualquercria

O caso do meio existe porque o segredo é irrecuperável: se o local ainda é placeholder, ninguém o está a usar e re-criar é seguro.

3. Apaga na ordem certa — primeiro o service account, só depois o cliente. O DELETE /api/clients/{uuid} não faz cascade, e a violação de chave estrangeira sai como 500, não 409.

4. Cria o cliente — POST /api/clients com clientId, clientName, description, active, requirePkce: false, os três TTLs, scopes: ["openid"] e grantTypes: ["client_credentials"]. Sem redirectUris: os backends não precisam e os frontends fazem login pelo cliente seeded.

5. Cria o service account — POST /api/service-accounts com oauthClientId igual ao UUID devolvido, nunca a string do clientId. Sem isto o token autentica mas não consegue usar a API.

6. Escreve o segredo em cada env-file indicado. Acabou de criar o cliente, logo o valor em mão é o único válido e ganha sempre.

7. Sai com 1 se algo falhou. Sair 0 com "0 criados" deixaria o instalador arrancar os projetos com placeholders.

A escrita preserva o inode

Os env-files são bind-mounted ficheiro a ficheiro, não a pasta. Um sed -i ou um mv substituiria o ficheiro e desligaria o mount — a alteração nunca chegaria ao host. Por isso a escrita é awk > tmp seguido de cat tmp > ficheiro.

Lista fixa mais descoberta

O que torna isto utilizável num workspace real: os projetos não estão listados em lado nenhum.

OrigemO que cobrePorquê
oauth-clients.specserviços de plataformaos env-files deles vivem fora de projects/
descoberta em runtimetudo em projects/são criados dinamicamente pelo IGRP Studio

A spec tem prioridade: uma linha lá escrita sobrepõe-se à descoberta, pelo que fixar um projeto à mão continua possível.

O sinal da descoberta é o compose do próprio projeto, que já declara as variáveis de que precisa:

- IGRP_ACCESS_M2M_CLIENT_ID=${DEMO_NOVO_M2M_CLIENT_ID}   # backend
- IGRP_M2M_CLIENT_ID=${THEQUE_M2M_CLIENT_ID}             # frontend

Dessa única linha sai tudo:

DerivadoDe onde
variável do segredo..._CLIENT_ID → ..._CLIENT_SECRET
tipoIGRP_ACCESS_M2M_* é backend, IGRP_M2M_* é frontend
clientIdvalor de ..._M2M_CLIENT_ID no .env; se ainda não existir, o nome do directório, escrito de volta nos dois env-files da raiz

O último ponto fecha o ciclo para um projeto acabado de criar, e o nome do directório mantém, por construção, a igualdade obrigatória clientId == spring.application.name.

Um projeto sem variáveis M2M é ignorado — não fala com o Access Management, não precisa de cliente.

A regra anti-cópia

Um directório só é considerado projeto se tiver igrp-compose-<nome-do-directório>.yaml. Um directório cujo compose nomeia outro projeto é uma cópia, e registá-la reclamaria o cliente de outro:

[OAuth Clients]   . gestao-utente has no igrp-compose-gestao-utente.yaml — skipping

Como os outros stacks recebem o resultado

Não há comunicação entre composes: o canal é o ficheiro .env.

Backends Spring — o SPRING_APPLICATION_NAME tem de ser o clientId. Frontends Next.js — o IGRP_SERVICE_ID vai no header X-Machine-Service-ID.

Dois serviços ficam fora deste mecanismo, de propósito:

  • o access-management é o próprio servidor: semeia o seu cliente no arranque a partir de IGRP_OAUTH_DEFAULT_CLIENT_ID, e é com ele que as duas tarefas se autenticam;
  • o app-center reutiliza esse mesmo cliente seeded.

Parte 2 — Registo dos redirects

O problema

Todas as UIs fazem login pelo mesmo cliente OIDC, o seeded. O Spring Authorization Server só aceita um redirect_uri registado nesse cliente; se o callback de uma app não estiver lá, o login falha antes de o utilizador ver seja o que for. A imagem do Access Management semeia um callback numa base de dados nova — todos os outros têm de ser acrescentados.

O que a tarefa faz

O sync-default-client-uris.sh:

  1. obtém token com o cliente seeded e encontra o UUID dele;
  2. lê os redirectUris e postLogoutRedirectUris actuais;
  3. faz merge, não substitui — acrescenta o que falta e preserva o resto;
  4. se nada mudou, sai 0 sem escrever;
  5. caso contrário faz PUT /api/clients/{uuid}, depois de remover os campos que o modelo de leitura tem e o endpoint de actualização rejeita (id, clientSecret, applicationCode, createdAt, updatedAt).

Lista fixa mais descoberta, outra vez

O caminho base vem do descritor que o IGRP Studio escreve em cada projeto — projects/theque/.igrp.theque.env → NEXT_PUBLIC_BASE_PATH="/apps/theque". Daí saem os dois URIs, na forma que o NextAuth constrói:

URIConstrução
callback${BASE_URL}<basePath>/api/auth/callback/${AUTH_PROVIDER}
pós-logout${BASE_URL}<basePath>/login

A ausência de NEXT_PUBLIC_BASE_PATH identifica um backend

Não tem callback de browser. Há um fallback: sem o descritor, o caminho é extraído do NEXTAUTH_URL do compose do projeto. Aplica-se a mesma regra anti-cópia.

Discovering project frontends under /workspace/projects
  . bibliotheque declares no base path — treating it as a backend (no callback)
  . gestao-utente has no igrp-compose-gestao-utente.yaml — skipping
  + theque -> /apps/theque
  + utente -> /apps/utente
Default OAuth client callback URIs reconciled.

Limite conhecido: o merge é aditivo

A tarefa nunca remove. Um projeto apagado deixa o seu callback no cliente seeded indefinidamente. Não é grave — aponta para um caminho que já não responde — mas suja a configuração, e a limpeza é manual.


Acrescentar um projeto novo

Não há nada a configurar; basta seguir a convenção:

FicheiroNecessário para
projects/<nome>/igrp-compose-<nome>.yamlser reconhecido como projeto
…com IGRP_ACCESS_M2M_CLIENT_ID (backend) ou IGRP_M2M_CLIENT_ID (frontend)ter cliente registado
projects/<nome>/.igrp.<nome>.env com NEXT_PUBLIC_BASE_PATHter callback registado (só frontends)

Depois:

docker compose --env-file .env -f igrp-compose.yaml up -d
docker wait demoteste-access-management-oauth-clients

A armadilha da ordem de arranque

O docker wait não é decorativo

docker compose up -d arranca tarefas one-shot mas só espera por elas quando outro serviço declara service_completed_successfully. Nada nos composes separados o declara — não pode, são ficheiros diferentes.

Arrancar um projeto demasiado cedo faz com que leia o .env antes da escrita e apanhe um CHANGE_ME_*. O sintoma aparece longe da causa: um backend que autentica e falha o sync com 404. É por isso que se espera pela tarefa de registo (docker wait <slug>-access-management-oauth-clients) antes de arrancar os projetos — ver Stack principal.


Nomes: três coisas diferentes

Confusão frequente, e a causa de 404s difíceis de ler:

NomeÉDefinido por
clientIdo cliente OAuth e o nome do resource no Access Managementa spec ou a descoberta
spring.application.nameo nome por que o AuthorizationSyncRunner procura o resourceSPRING_APPLICATION_NAME, igual ao clientId
nome no Eurekao segmento da rota /gateway-api/<x>eureka.instance.appname + os dois virtual-host names

O nome no Eureka apenas por omissão coincide com o spring.application.name. É por isso que o workspace consegue ter a rota demoteste-process-management-api com o cliente igrp-process-management-api.


Verificação

docker logs demoteste-access-management-oauth-clients
docker logs demoteste-access-management-oauth-client-uri-sync

Os clientes registados, pela API:

CID=$(sed -n 's/^ACCESS_MANAGEMENT_OAUTH_CLIENT_ID=//p' .env | head -1 | tr -d '\r')
CSEC=$(sed -n 's/^ACCESS_MANAGEMENT_OAUTH_CLIENT_SECRET=//p' .env | head -1 | tr -d '\r')
AM=http://localhost:2575/gateway-api/demoteste-access-management
TOK=$(curl -s -X POST "$AM/oauth2/token" -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode grant_type=client_credentials \
  --data-urlencode "client_id=$CID" --data-urlencode "client_secret=$CSEC" \
  | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
curl -s -H "Authorization: Bearer $TOK" "$AM/api/clients" \
  | sed 's/},{/}\n{/g' | sed -n 's/.*"clientId":"\([^"]*\)".*/\1/p' | sort

A prova final é o que cada serviço publicou. O GET /api/applications responde 403 ao service account seeded, pelo que a via fiável é a base de dados:

docker exec demoteste-database-postgres psql -U igrp -d igrp_db -tAc \
  "SELECT name FROM t_resource ORDER BY name;"

Com os quatro projetos e a stack Process a correr devolve 9: os 8 serviços mais o igrp-access-management.


Equivalência com o Helm chart

No igrp-stack cada serviço traz o seu Job oauth-register com hook post-install,post-upgrade. Aqui um único one-shot faz o mesmo para todos.

PassoJob do chartTarefa do compose
já registado?sai se o Secret alvo existirsai se o cliente existir e o segredo local for real
espera pelo AMGET /.well-known/openid-configurationPOST /oauth2/token + GET /api/clients até 200
rota do gateway em faltaPOST /actuator/gateway/refresh a cada tentativaidem, via GATEWAY_REFRESH_URL
cliente sem segredofalha, salvo autoRegister.forceRecreate=trueapaga e re-cria quando o valor local é placeholder
apagar o clienteDELETE /api/clients/{uuid}apaga primeiro o service account
payloadclientId, clientName, description, active, requirePkce, TTLs, scopes, grantTypeso mesmo
service accountPOST /api/service-accounts, oauthClientId = UUIDo mesmo
onde guarda o segredokubectl create secretenv-files, preservando o inode
lista de serviçosum Job por chart, no values.yamlspec para a plataforma + descoberta em projects/

A diferença de fundo é o destino do segredo. A última linha é a divergência deliberada: no chart cada serviço é uma release Helm conhecida à instalação, enquanto num workspace os projetos aparecem a qualquer momento.

How is this guide?

Última atualização: