Pular para o conteúdo principal
Página não listada
Esta página não está listada. Mecanismos de busca não irão indexá-la, e somente usuários que possuam o link direto poderão acessá-la

Segurança e Multi-tenancy

O Kikwiflow trata segurança como um ponto de extensão explícito, não como uma feature embutida e opinativa. O motor define duas SPIs pequenas em kikwi-security-api; o kikwi-core fornece implementações padrão minimalistas (uma delas deliberadamente permissiva); e é responsabilidade de cada aplicação decidir se — e como — reforçar controle de acesso real.

IdentityContext: quem está fazendo a chamada

public record IdentityContext(String actorId, String tenantId, Set<String> roles) {
public static IdentityContext system() {
return new IdentityContext("__KIKWIFLOW_SYSTEM__", "GLOBAL", Set.of("SYSTEM_ADMIN"));
}
public boolean isSystem() { return "__KIKWIFLOW_SYSTEM__".equals(this.actorId); }
}

Toda operação de comando exposta por KikwiflowEngine (startProcess, completeExternalTask, retryIncident, setVariables, deleteInstance) recebe um IdentityContext — mas o motor não o extrai de nenhum lugar automaticamente. Na camada REST (kikwi-security-spring-starter), um HttpIdentityResolver e um IdentityContextArgumentResolver injetam o IdentityContext a partir da requisição HTTP (tipicamente de um JWT ou header de autenticação) diretamente como parâmetro de controller — mas essa fiação é opcional e substituível.

IdentityContext.system() é o identity usado internamente pelo motor para operações que não têm um ator humano associado — por exemplo, o KikwiflowAutoDeployer chama processDefinitionService.deploy(content, IdentityContext.system()) ao implantar processos do classpath no boot da aplicação.

DeploymentSecurityManager: controlando quem pode implantar processos

public interface DeploymentSecurityManager {
/** @throws SecurityException se o acesso for negado. */
void validateDeployAccess(IdentityContext identity, ProcessDefinitionDeployRequest content);
}

A implementação padrão fornecida pelo kikwi-core, DefaultDeploymentSecurityManager, é um interruptor global simples:

public class DefaultDeploymentSecurityManager implements DeploymentSecurityManager {
private final boolean isDeployEnabled;
@Override
public void validateDeployAccess(IdentityContext identity, ProcessDefinitionDeployRequest content) {
if (!isDeployEnabled) throw new SecurityException("Deploys estão desabilitados");
}
}

Ligado a uma única propriedade:

kikwiflow:
security:
deploy-enabled: true # default false — se false, TODO deploy é rejeitado, independentemente de quem chama

Note que isso não é controle de acesso por identidade/role — é um interruptor de tudo-ou-nada. Se sua aplicação precisa de regras mais finas (ex.: só usuários com role PROCESS_ADMIN podem implantar, ou deploys só são permitidos para processos do próprio tenant), forneça seu próprio bean DeploymentSecurityManagerKikwiflowAutoConfiguration registra a implementação padrão apenas com @ConditionalOnMissingBean, então um bean customizado no seu contexto Spring automaticamente toma precedência, sem nenhuma configuração adicional.

@Component
public class RoleBasedDeploymentSecurityManager implements DeploymentSecurityManager {
@Override
public void validateDeployAccess(IdentityContext identity, ProcessDefinitionDeployRequest content) {
if (!identity.roles().contains("PROCESS_ADMIN")) {
throw new SecurityException("Ator " + identity.actorId() + " não tem permissão de deploy.");
}
}
}

VariableSecurityPolicyManager: mascaramento e controle de leitura/escrita de variáveis

public interface VariableSecurityPolicyManager {
boolean canWrite(String processDefinitionId, IdentityContext identity, Set<String> variableNames);
boolean canRead(String processDefinitionId, IdentityContext identity, Set<String> variableNames);
boolean canWrite(String processDefinitionId, IdentityContext identity, String variableName);
boolean canRead(String processDefinitionId, IdentityContext identity, String variableName);
Map<String, ProcessVariable> applyReadPoliciesAndMasking(String processDefinitionId, IdentityContext identity, Map<String, ProcessVariable> rawVariables);
}

:::danger A implementação padrão é um no-op — todo mundo pode ler e escrever tudo

public class DefaultVariableSecurityPolicyManager implements VariableSecurityPolicyManager {
public boolean canWrite(...) { return true; }
public boolean canRead(...) { return true; }
public Map<String, ProcessVariable> applyReadPoliciesAndMasking(String processDefinitionId, IdentityContext identity, Map<String, ProcessVariable> rawVariables) {
return rawVariables; // sem mascaramento algum
}
}

Isso é intencional e documentado no próprio CLAUDE.md do repositório: RBAC e mascaramento de variáveis é um ponto de extensão que não faz nada por padrão. Se o seu processo carrega dados sensíveis em variáveis (PII, dados financeiros, documentos) e múltiplos tenants/roles compartilham a mesma instância da aplicação, você precisa fornecer sua própria implementação de VariableSecurityPolicyManager antes de ir para produção — caso contrário, qualquer identidade autenticada que consiga chamar a API de consulta enxerga o valor bruto de todas as variáveis de qualquer instância. :::

@Component
public class TenantAwareVariableSecurityPolicyManager implements VariableSecurityPolicyManager {
@Override
public Map<String, ProcessVariable> applyReadPoliciesAndMasking(
String processDefinitionId, IdentityContext identity, Map<String, ProcessVariable> rawVariables) {
if (identity.roles().contains("VIEW_SENSITIVE_DATA")) return rawVariables;
Map<String, ProcessVariable> masked = new HashMap<>(rawVariables);
masked.computeIfPresent("cpfCliente", (k, v) -> new ProcessVariable(k, "***MASKED***"));
return masked;
}
// canRead/canWrite conforme a regra de negócio
}

Isolamento por tenant (tenantId)

tenantId é um campo de primeira classe em ProcessInstance e ExternalTask (definido na criação da instância via ProcessStarter.onTenant(tenantId)), mas o motor não aplica isolamento por tenant automaticamente em todas as operações — o único ponto onde há checagem explícita hoje é KikwiflowEngine.completeExternalTask:

if (!Objects.equals(taskToComplete.tenantId(), identityContext.tenantId())) {
throw new SecurityException("Tenant mismatch: Task " + externalTaskId + " does not belong to the provided tenant.");
}

Para consultas (createProcessInstanceQuery(), createExternalTaskQuery(), endpoints REST de busca), o filtro por tenantId/tenantIds está disponível como parâmetro de busca (ver Busca Avançada de Instâncias), mas é responsabilidade de quem monta a query incluir o filtro de tenant do identity atual — o motor não injeta esse filtro automaticamente por trás dos panos. Trate isolamento multi-tenant como uma responsabilidade da camada de API/controller que precede o motor, reforçada consistentemente em toda superfície de leitura e escrita exposta a usuários finais.

Resumo de responsabilidades

MecanismoFornecido pelo motorAplicado automaticamente?
IdentityContext propagado pelas chamadasSim (tipo)Não — extração de request HTTP é responsabilidade de kikwi-security-spring-starter ou de código próprio
Bloqueio global de deploySim (DefaultDeploymentSecurityManager)Sim, mas é tudo-ou-nada, não por identidade
RBAC/mascaramento de variáveisInterface apenas (DefaultVariableSecurityPolicyManager é no-op)Não — requer implementação própria
Isolamento por tenant em completeExternalTaskSimSim, apenas nesse método específico
Isolamento por tenant em consultasSuporte via parâmetro de filtroNão — precisa ser aplicado explicitamente pelo chamador