Skip to content

About

Self-service infrastructure requests powered by Terraform and Atlantis.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

open-platform

Portal interno de autosserviço para módulos Terraform aprovados. As Fases 0–4 fornecem fundação operacional, identidade OIDC, sessão/RBAC, segurança web, catálogo versionado, criação determinística de Pull Request e pedidos auditáveis com projeção por webhooks assinados. A Fase 5 acrescenta configuração externa validada, deploy Kubernetes endurecido, métricas/alertas e governança operacional.

O portal não aprova pedidos, não executa terraform plan/apply, não faz merge automático e não reconcilia efeitos Git de forma destrutiva. Atlantis e GHE continuam fontes externas de verdade; o portal apenas acompanha eventos.

Pré-requisitos

  • Go 1.26 com o patch suportado mais recente.
  • Terraform 1.15.9 para executar os testes reais de renderização fora do container.
  • Docker com Compose para o ambiente completo.
  • Helm 4 e kubectl 1.36 para validar o deploy Kubernetes/Kustomize.
  • make em Linux/macOS ou execução direta dos comandos Go no Windows.

As ferramentas de desenvolvimento (sqlc, golangci-lint e govulncheck) estão fixadas no go.mod e são executadas com go tool; não precisam de instalação global.

Subir localmente

docker compose up --build

O Compose inicia PostgreSQL, executa migrations uma vez e só então inicia o portal. Endpoints:

  • http://localhost:8080/login: início do login corporativo.
  • http://localhost:8080/: dashboard autenticado.
  • http://localhost:8080/catalog: itens autorizados do catálogo ativo.
  • http://localhost:8080/catalog/{id}: formulário derivado do item aprovado.
  • POST http://localhost:8080/catalog/{id}/validate: validação server-side e fragmento HTMX, com CSRF.
  • POST http://localhost:8080/catalog/{id}/requests: gera os arquivos aprovados e cria branch, commit e PR, com CSRF e chave idempotente.
  • GET http://localhost:8080/requests/{id}: detalhe e timeline autorizados do pedido.
  • GET http://localhost:8080/requests/{id}/status: fragmento HTMX de status/timeline.
  • POST http://localhost:8080/webhooks/git: recepção GitHub/GHE autenticada por HMAC SHA-256; não usa sessão nem CSRF.
  • http://localhost:8080/health/live: processo HTTP ativo.
  • http://localhost:8080/health/ready: PostgreSQL acessível.
  • http://localhost:8080/metrics: métricas Prometheus.

Para encerrar, use docker compose down. O volume do PostgreSQL é preservado; removê-lo deve ser uma decisão explícita.

O provedor OIDC é descoberto apenas ao iniciar login. No Docker Desktop, o emissor local padrão é http://host.docker.internal:5556; configure as variáveis OIDC abaixo para usar o IdP real. O callback cadastrado no IdP deve ser exatamente http://localhost:8080/auth/callback no ambiente local e aceitar response_mode=form_post.

Executar sem Compose

Com PostgreSQL disponível na URL padrão:

go run ./cmd/migrate
go run ./cmd/portal

No PowerShell, os comandos de validação equivalentes ao make ci são:

go tool sqlc generate
go run ./cmd/catalog-validate -file catalog/catalog.yaml -schema catalog/schema.json
go run ./cmd/rbac-validate -file config/rbac.example.yaml -catalog catalog/catalog.yaml
gofmt -l (Get-ChildItem -Recurse -Filter *.go | Select-Object -ExpandProperty FullName)
go vet ./...
go tool golangci-lint run
go test -count=1 ./...
go test -race -count=1 ./...
go build ./cmd/portal ./cmd/migrate ./cmd/catalog-validate ./cmd/rbac-validate
go tool govulncheck ./...
helm lint deploy/helm/open-platform
kubectl kustomize --enable-helm deploy/kustomize
docker compose config --quiet

O teste PostgreSQL real é habilitado por TEST_DATABASE_URL; sem essa variável ele é ignorado. A CI sempre o executa.

Configuração

Variável Default local Finalidade
PORTAL_SERVICE_NAME open-platform Nome do serviço
PORTAL_SERVICE_VERSION development Versão em logs e traces
PORTAL_ENVIRONMENT development development, test ou production
PORTAL_LOG_LEVEL info debug, info, warn ou error
PORTAL_HTTP_ADDRESS :8080 Endereço HTTP
PORTAL_HTTP_TIMEOUT 15s Timeout por request
PORTAL_SHUTDOWN_TIMEOUT 10s Limite do shutdown gracioso
PORTAL_DATABASE_URL URL PostgreSQL local DSN; nunca é incluída nos logs
PORTAL_DATABASE_MAX_CONNECTIONS 10 Máximo do pool pgx
PORTAL_DATABASE_MIN_CONNECTIONS 2 Mínimo do pool pgx
PORTAL_DATABASE_MAX_CONN_LIFETIME 30m Vida máxima de conexão
PORTAL_DATABASE_READY_TIMEOUT 2s Timeout do readiness
PORTAL_METRICS_ENABLED true Habilita coleta e /metrics
PORTAL_OTEL_TRACE_ENDPOINT vazio Endpoint OTLP/HTTP completo
PORTAL_OTEL_TRACE_INSECURE false Permite OTLP sem TLS em ambiente controlado
PORTAL_TRACE_SAMPLE_RATE 0.1 Amostragem entre 0 e 1
PORTAL_OIDC_ISSUER_URL http://localhost:5556 Issuer usado na discovery e validação do ID token
PORTAL_OIDC_CLIENT_ID open-platform Client ID do cliente confidencial
PORTAL_OIDC_CLIENT_SECRET segredo somente local Client secret; obrigatório e distinto em produção
PORTAL_OIDC_REDIRECT_URL http://localhost:8080/auth/callback Redirect URI exata cadastrada no IdP
PORTAL_OIDC_POST_LOGOUT_REDIRECT_URL http://localhost:8080/login Retorno permitido após logout no IdP
PORTAL_OIDC_SCOPES openid,profile,email,groups Scopes separados por vírgula; deve conter openid
PORTAL_OIDC_EMAIL_CLAIM email Claim de e-mail
PORTAL_OIDC_NAME_CLAIM name Claim de nome de exibição
PORTAL_OIDC_GROUPS_CLAIM groups Claim de grupos, como string ou lista de strings
PORTAL_OIDC_FLOW_TTL 5m Validade de state, nonce e PKCE
PORTAL_OIDC_DISCOVERY_TIMEOUT 5s Timeout da discovery OIDC
PORTAL_SESSION_TTL 8h Validade máxima da sessão, limitada também pelo ID token
PORTAL_SESSION_COOKIE_SECURE true Cookie Secure; só pode ser false no HTTP local
PORTAL_CSRF_SECRET segredo somente local Chave HMAC com no mínimo 32 caracteres
PORTAL_MAX_BODY_BYTES 65536 Limite global de payload mutável, entre 1 KiB e 1 MiB
PORTAL_BRANDING_NAME Open Platform Nome exibido no portal
PORTAL_BRANDING_PRIMARY_COLOR #5b5bd6 Cor primária em hexadecimal
PORTAL_BRANDING_DEFAULT_THEME system system, light ou dark
PORTAL_BRANDING_LOGO_FILE vazio PNG, JPEG, GIF, WebP ou ICO local, até 512 KiB
PORTAL_BRANDING_FAVICON_FILE vazio Favicon local nos mesmos formatos e limite
PORTAL_CATALOG_FILE catalog/catalog.yaml Catálogo YAML versionado validado no startup
PORTAL_CATALOG_SCHEMA_FILE catalog/schema.json JSON Schema 2020-12 compilado antes do catálogo
PORTAL_RBAC_BINDINGS_FILE vazio Arquivo YAML RBAC estrito; obrigatório em produção
PORTAL_TERRAFORM_BINARY terraform Executável usado exclusivamente por terraform fmt
PORTAL_TERRAFORM_WORK_ROOT diretório temporário dedicado Raiz absoluta dos workspaces efêmeros de geração
PORTAL_GIT_PROVIDER disabled disabled ou github; obrigatório habilitar em produção
PORTAL_GIT_BASE_URL https://api.github.com API do GitHub ou GitHub Enterprise Server
PORTAL_GIT_API_VERSION 2026-03-10 Versão REST enviada ao provider
PORTAL_GIT_TOKEN vazio Token lido apenas da configuração/secret manager
PORTAL_GIT_OWNER company Organização proprietária dos repositórios aprovados
PORTAL_GIT_BASE_BRANCH main Branch base fixa para os Pull Requests
PORTAL_GIT_TIMEOUT 15s Timeout das chamadas ao provider
PORTAL_GITHUB_WEBHOOK_SECRET segredo somente local Segredo HMAC independente do token da API Git
PORTAL_GITHUB_WEBHOOK_PREVIOUS_SECRET vazio Segredo anterior temporário, apenas durante rotação HMAC
PORTAL_GITHUB_WEBHOOK_MAX_BODY_BYTES 65536 Limite do corpo bruto, nunca superior ao limite global
PORTAL_ATLANTIS_ACTORS atlantis,atlantis[bot] Logins autorizados a produzir eventos Atlantis

Credenciais locais, URLs OIDC sem HTTPS e cookies sem Secure são rejeitados quando PORTAL_ENVIRONMENT=production.

RBAC local

Claims OIDC identificam a pessoa e seus grupos, mas não concedem acesso diretamente. Em produção, bindings vêm exclusivamente do arquivo externo PORTAL_RBAC_BINDINGS_FILE, validado e carregado no startup sem persistir grants. Use config/rbac.example.yaml como estrutura, substituindo os grupos genéricos. Sem o arquivo, ambientes não produtivos preservam os bindings PostgreSQL das fases anteriores. O caso de uso exige simultaneamente:

  1. um role_binding local com catalog.request, global ou com escopo catalog_item;
  2. participação em pelo menos um authorization.request_groups do item.

Binding global:

- subject: group:platform-users
  role: catalog.request

Também é possível associar uma identidade específica no formato subject:<issuer>#<sub>. Sem uma associação local válida, o menu não mostra o catálogo e acesso direto a /catalog retorna 403.

Binding restrito a um item:

- subject: group:database-developers
  role: catalog.request
  scope_type: catalog_item
  scope_value: aws-rds-postgres

Catálogo e templates versionados

catalog/catalog.yaml é a única fonte para autorização e UX dos módulos. catalog/schema.json documenta a estrutura formal; o loader também aplica regras semânticas mais restritivas. Ambos são validados no startup e explicitamente na CI.

Cada item possui ID e versão, metadados, owner/risco, source/ref fixos, target controlado pelo servidor, grupos, ambientes, campos tipados, valores fixos e um conjunto versionado de templates. Tipos publicados: string, integer, boolean e select.

O formulário envia apenas environment e os nomes presentes em fields. Campos extras, valores múltiplos, enum fora da allowlist, tipo inválido e ambiente não permitido são rejeitados no caso de uso. module, source, ref, repository, path, HCL e valores fixos nunca são aceitos do navegador.

Os templates ficam em catalog/templates/<item>/<versão>/ e possuem outputs .tf declarados explicitamente no catálogo. O loader rejeita placeholders desconhecidos, arquivos fora dessa raiz e links simbólicos. HTMX 2.0.10 é carregado por CDN com SRI e hash fixado na CSP. Sem JavaScript, os mesmos formulários retornam páginas completas.

Geração Terraform e Git

O caso de uso repete RBAC e validação do payload antes de gerar qualquer arquivo. O destino é derivado apenas de target.path_template; paths absolutos, .., barras invertidas, segmentos inesperados e escapes do workspace são rejeitados. A geração usa somente source/ref, valores fixos e campos tipados do item autorizado, executa terraform fmt e verifica que o workspace contém exatamente os outputs aprovados.

O endpoint de criação recebe apenas os campos publicados, environment, CSRF e uma chave idempotente gerada pelo servidor. A chave bruta não é persistida: o banco guarda somente seu hash, o fingerprint do payload técnico, estado e referência do PR. Reenvios concluídos devolvem o mesmo PR; colisões de payload e operações ainda em processamento falham sem criar outra mudança.

O adaptador inicial usa a API REST do GitHub/GitHub Enterprise para criar ref, tree, commit e PR, com labels padronizados. Configure um token limitado ao repositório de destino com Contents: write e Pull requests: write; ele deve vir de secret manager como PORTAL_GIT_TOKEN. Em desenvolvimento, o provider permanece disabled por padrão e nenhum Git externo é alterado.

Pedidos, status e webhooks

Cada submissão reservada cria um UUID em requests e um evento request_submitted na mesma transação. O pedido persiste solicitante, catálogo/item/versões, input normalizado sanitizado, ambiente, cliente, target calculado e referências Git. A UI nunca carrega ou renderiza normalized_input.

request_events é append-only no próprio PostgreSQL: UPDATE e DELETE são bloqueados por trigger. Eventos guardam origem, tipo, ator opcional, payload derivado por allowlist, horário da origem e ID externo deduplicável. Entregas ficam em webhook_deliveries somente com headers mínimos, hash do corpo e estado; assinatura e corpo bruto não são armazenados.

Configure o webhook do repositório com content type application/json, URL /webhooks/git, segredo vindo do secret manager e somente os eventos pull_request, pull_request_review, check_run, check_suite e issue_comment. A aplicação valida X-Hub-Signature-256 sobre o corpo bruto antes do parsing, exige X-GitHub-Delivery único e aceita apenas o owner configurado. Consulte a validação oficial de deliveries e os contratos de eventos.

Checks com nome/app contendo terraform ou atlantis alimentam estados de plan. Comentários são classificados somente quando publicados por um ator em PORTAL_ATLANTIS_ACTORS; o texto original nunca é persistido. Eventos atrasados entram na timeline, mas só alteram a projeção quando seu timestamp não é anterior ao último status. Um PR fechado sem merge vira cancelled; merge por si só não significa applied.

O solicitante consulta o próprio pedido. Para leitura administrativa, associe request.read globalmente ou nos escopos catalog_item e client:

- subject: group:platform-admins
  role: request.read
  scope_type: client
  scope_value: acme

Proteções da aplicação web

  • Sessão opaca persistida apenas pelo hash; cookies HttpOnly, SameSite=Lax e Secure em produção.
  • state de uso único, nonce, PKCE S256 e validação de issuer, audience, assinatura e expiração no fluxo OIDC.
  • CSRF HMAC vinculado à sessão em todas as mutações. O callback OIDC usa state de uso único como proteção equivalente.
  • CSP restritiva, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, X-Frame-Options: DENY e frame-ancestors 'none'.
  • Login, callback e logout existem apenas como POST; não há mutação via GET.
  • A validação do formulário é POST, exige sessão/CSRF e repete autorização e validação no caso de uso.
  • A criação do PR também é somente POST, exige CSRF e repete autorização, validação e idempotência no caso de uso.
  • O token Git nunca é aceito pelo navegador, persistido no banco ou incluído em logs/erros.
  • O webhook é a única mutação sem CSRF; usa autenticação HMAC no corpo bruto, limite próprio, deduplicação e persistência anterior à projeção.

Comandos principais

make generate         # gera o pacote sqlc
make catalog-validate # compila o schema e valida a fixture versionada
make rbac-validate    # valida o RBAC externo contra o catálogo
make deploy-validate  # valida Helm e renderiza o overlay Kustomize
make lint             # go vet e golangci-lint
make test             # testes com race detector
make build            # portal, migration runner e validadores
make migrate          # aplica migrations pendentes
make docker-build     # imagem OCI local
make ci               # aceite completo das Fases 0–5

O chart está em deploy/helm/open-platform, os runbooks em docs/runbooks e o roteiro corporativo em docs/e2e-corporate.md. As decisões arquiteturais permanecem em docs/adr.

About

Self-service infrastructure requests powered by Terraform and Atlantis.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages