
Quem já subiu o Apache Guacamole conhece o pacote: Tomcat, o guacamole-client em Java, uma extensão JDBC para o banco, outra para LDAP, outra para TOTP, outra para gravações, cada uma com seu arquivo de configuração. O GoGuacamole é o meu projeto novo para trocar essa parte por um único binário Go, com a interface em Vue 3 embutida, mantendo o mesmo guacd da Apache por baixo. Você continua acessando servidores por RDP, SSH e VNC pelo navegador, mas ganha gravação de sessão, auditoria, SSO e MFA configurados na própria interface, e pode importar o banco do Guacamole que já tem.
Este post cobre a versão 0.6.2 (30 de outubro de 2026): o que o projeto faz, como ele se encaixa no guacd, como subir em um minuto com uma imagem só, como colocar atrás do nginx, como gravar e auditar sessões e como migrar do cliente Java. O código está em github.com/jniltinho/go-guacamole, sob licença MIT.
O que é o GoGuacamole
O Apache Guacamole tem duas metades. O guacd é o daemon em C que fala RDP, SSH e VNC com os servidores e traduz tudo para o protocolo Guacamole. O guacamole-client é a aplicação web em Java que autentica o usuário, guarda conexões e permissões e entrega a tela ao navegador. O GoGuacamole reescreve só a segunda metade:
- Backend em Go com Echo v5 e Cobra: API REST em
/api/v1, túnel WebSocket compatível com oguacamole-common-jse o protocolo Guacamole implementado direto, incluindo o handshake com o guacd (por TCP ou TLS). - Frontend em Vue 3 (Vite e Tailwind v4) embutido no binário com
embed, a mesma técnica da parte 5 da série Vue.js + Go com Echo v5. - Banco: SQLite por padrão, MariaDB/MySQL ou PostgreSQL quando crescer, com migrações embutidas que rodam ao iniciar.
- guacd oficial: a imagem
guacamole/guacd1.6 continua fazendo o trabalho pesado dos protocolos.
Usuários, grupos, pastas de conexões, permissões herdadas, histórico, gravação, auditoria, login externo e MFA vêm prontos, sem extensões separadas. A interface tem 16 temas e está em inglês e português do Brasil.

Arquitetura: quem fala com quem
O navegador só conversa com o goguacamole, por HTTPS e WebSocket. O goguacamole fala o protocolo Guacamole com o guacd, que fica numa rede privada, e o guacd abre a conexão RDP, SSH ou VNC com o servidor de destino. Host, usuário e senha da conexão ficam no servidor, cifrados com AES-256-GCM; o navegador nunca os vê.

A abertura de uma sessão segue este caminho:
- O navegador pede
POST /api/v1/sessionscom o ID da conexão. O servidor confere a sessão, as restrições do usuário (validade da conta, janela de acesso) e a permissão efetiva de leitura. - A resposta é um ticket de uso único, válido por 30 segundos, que já reserva a vaga caso a conexão tenha limite de sessões.
- O navegador abre o WebSocket
/api/v1/tunnel?ticket=…mandando só parâmetros de tela (largura, altura, DPI, fuso). O servidor valida a origem, resgata o ticket e autoriza de novo. - O goguacamole faz o handshake com o guacd usando os parâmetros guardados, ativa a entrada do histórico (criada como pendente quando o ticket foi emitido) e passa a repassar as instruções nos dois sentidos.
Se a permissão for revogada no meio do caminho, o túnel é fechado na hora. Mudanças feitas por outro processo (a CLI ou o importador) são percebidas em até 5 segundos.
Subindo em um minuto: a imagem all-in-one
Desde a versão 0.6.0 cada release publica no GHCR uma imagem que junta guacd, goguacamole e Redis em um container, com SQLite no volume /data:
docker run -d --name goguacamole -p 8080:8080 \
-v goguacamole-data:/data ghcr.io/jniltinho/go-guacamole:allinone
# a senha gerada do admin aparece uma única vez no log
docker logs goguacamole 2>&1 | grep 'bootstrap administrator'
Para fixar a versão, use a tag 0.6.2-allinone (ou 0.6-allinone). Abra http://localhost:8080, entre como admin com a senha do log e o sistema pede uma senha nova no primeiro acesso. A prontidão pode ser conferida pela API:
curl -s http://localhost:8080/ready
# {"db":"up","guacd":"up","queue":"up"}
Dentro do container, o supervisord mantém os três processos rodando como o usuário guacd (uid 1000), dono dos arquivos em /data. Por isso, comandos da CLI rodam com -u guacd:
docker exec goguacamole supervisorctl status # guacd, redis, goguacamole
docker exec -u guacd goguacamole goguacamole user passwd admin
docker exec goguacamole goguacamole version
Esse modo HTTP é para avaliação local. Para produção, veja o checklist mais abaixo.
Testando com um servidor SSH de laboratório
Para ver uma sessão de verdade sem mexer em nenhum servidor, coloque o GoGuacamole e um OpenSSH descartável na mesma rede Docker. O guacd da imagem all-in-one resolve o alvo pelo nome do container. Se o container da seção anterior ainda estiver rodando, remova-o antes (docker rm -f goguacamole), porque o laboratório usa o mesmo nome e a mesma porta:
docker network create guac-lab
docker run -d --name goguacamole --network guac-lab -p 8080:8080 \
-v goguacamole-data:/data ghcr.io/jniltinho/go-guacamole:allinone
docker run -d --name ssh-alvo --network guac-lab \
-e USER_NAME=demo -e USER_PASSWORD='troque-esta-senha' -e PASSWORD_ACCESS=true \
lscr.io/linuxserver/openssh-server:latest
Na interface, em Conexões → Nova conexão, escolha SSH, host ssh-alvo, porta 2222 (a porta dessa imagem), usuário demo e a senha. Antes de salvar, use Testar conexão: o GoGuacamole abre uma conexão real pelo guacd com os valores do formulário e diz se o host respondeu e se o login funcionou. No meu teste, a senha certa voltou como sucesso em cerca de 200 ms, e a errada voltou como falha de autenticação, sem gravar nada. Depois é só salvar e clicar em Conectar: o terminal abre numa aba do navegador.
Ao terminar, remova o laboratório:
docker rm -f goguacamole ssh-alvo
docker volume rm goguacamole-data
docker network rm guac-lab
Docker Compose com serviços separados
Para um ambiente que vai durar, o repositório traz um docker-compose.yml com o goguacamole, o guacd e o Redis em containers separados, e perfis para MariaDB, PostgreSQL e servidores de teste:
git clone https://github.com/jniltinho/go-guacamole.git
cd go-guacamole
cp .env.example .env
# no .env, defina APP_ADMIN_PASSWORD (8+ caracteres); a chave pode ser gerada assim:
printf 'APP_SECRET_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
make up # app + guacd + Redis, SQLite → http://localhost:8080
| Comando | O que sobe |
|---|---|
make up |
app, guacd e Redis com SQLite |
make up-mariadb |
o mesmo, mais MariaDB 11.4 |
make up-postgres |
o mesmo, mais PostgreSQL 17 |
make up-lab |
alvos de teste SSH, VNC e RDP |
make certs up-tls |
HTTPS nativo em https://localhost:8443 com uma CA local |
make down |
derruba tudo |
A APP_SECRET_KEY deriva a chave que cifra as senhas das conexões. Guarde-a junto com o backup do banco: sem ela, as credenciais armazenadas não podem ser recuperadas. Se você não definir a chave, o GoGuacamole gera um secret.key no primeiro start, e esse arquivo passa a ser igualmente crítico.
Também dá para rodar só o binário (Linux/amd64, nas releases) apontando para um guacd:
docker run -d -p 127.0.0.1:4822:4822 guacamole/guacd:1.6.0
APP_ADMIN_PASSWORD='troque-agora' GUACD_ADDR=127.0.0.1:4822 ./goguacamole serve
Atrás do nginx com HTTPS
O túnel da sessão é um WebSocket que pode ficar aberto por horas, então o proxy precisa de HTTP/1.1, dos cabeçalhos de upgrade e de timeouts longos. O repositório traz um exemplo completo em docker/nginx/goguacamole.conf; o essencial é isto:
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}
server {
listen 443 ssl;
http2 on;
server_name guac.example.com;
# ssl_certificate / ssl_certificate_key ...
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
# o túnel transmite a tela: sem buffer e com timeouts longos
location = /api/v1/tunnel {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_buffering off;
proxy_read_timeout 12h;
proxy_send_timeout 12h;
}
}
Do lado do GoGuacamole, três variáveis completam o par:
APP_BASE_URL=https://guac.example.com # o host aceito como Origin do WebSocket
APP_COOKIE_SECURE=true # TLS termina no proxy
APP_TRUSTED_PROXIES=127.0.0.1 # só esse IP pode informar X-Forwarded-For
Com APP_TRUSTED_PROXIES certo, o log, a auditoria e o histórico registram o IP do cliente, não o do nginx, e um X-Forwarded-For forjado vindo de outro lugar é ignorado. Se preferir o Caddy, o reverse_proxy dele já trata WebSocket sem configuração extra.
Gravação de sessões e auditoria
Aqui está boa parte do motivo do projeto. O guacd grava a sessão num volume compartilhado e o GoGuacamole cuida do resto:
- Vídeo da tela de RDP, SSH e VNC, reproduzido no próprio navegador.
- Transcrição de SSH em texto, com cada comando e sua saída, para ler ou pesquisar.
- Teclas digitadas, opcional e desligado por padrão.
- Política por conexão: vídeo, transcrição e teclas podem herdar o padrão, ser forçados ou desligados; vídeo e transcrição têm retenções próprias (as teclas fazem parte do vídeo).
- Fail-closed: se a gravação exigida não está disponível, a sessão não abre (há exceções temporárias, auditadas).
- Retenção e legal hold: a limpeza segue o histórico, e uma gravação sob retenção legal não é apagada.

Os padrões iniciais vêm de variáveis de ambiente (depois que um administrador salva em Administração → Gravação, vale o que está no banco):
RECORDING_ENABLED=true # padrão false; true no compose
RECORDING_SCREEN=true # vídeo de todos os protocolos
RECORDING_TYPESCRIPT=true # transcrição de SSH
RECORDING_INCLUDE_KEYS=false # teclas no vídeo
RECORDING_RETENTION_DAYS=90
As gravações também podem ir para object storage: AWS S3, compatíveis com S3 (MinIO, Ceph, R2, Wasabi), Oracle Cloud e Google Cloud Storage, via rclone. Cada envio é conferido por SHA-256 relendo o objeto, a cópia local pode ser removida, e o player toca direto do bucket. O botão Testar acesso grava, lê e apaga um objeto de prova e informa se o bucket tem versionamento e object lock. Essas tarefas rodam em fila no Redis com asynq, e o painel asynqmon fica em /asynqmon, só para administradores.
Por segurança, a conexão com o object storage passa por um proxy interno que recusa endereços privados, loopback, link-local e de metadados de nuvem. Um MinIO de laboratório na rede local precisa ser liberado de forma explícita:
OBJSTORE_ALLOWED_ENDPOINTS=minio
OBJSTORE_ALLOW_HTTP=true
A auditoria registra logins, sessões e alterações administrativas feitas pela interface e pela API. O painel ainda tem um relatório de acesso aos servidores: quem acessou qual servidor, quando e por quanto tempo.
Login: LDAP, OIDC, SAML e MFA
No cliente Java, cada método de login é uma extensão com seu próprio arquivo. No GoGuacamole, tudo fica em Administração → Autenticação, com os segredos guardados cifrados no banco e um botão de teste para cada método:
- LDAP / Active Directory, com grupos do diretório mapeados para grupos locais pelo nome. Se você administra AD ou Samba 4, o AD Manager faz par com ele.
- OpenID Connect (authorization code com PKCE) e SAML 2.0.
- Cabeçalho HTTP vindo de proxies confiáveis e JSON cifrado compatível com o
guacamole-auth-json. - TOTP com QR code e proteção contra reuso de código, e Duo Universal Prompt, como segundo fator dos logins por senha.
Usuários externos são criados no primeiro login. Um detalhe importante: o segundo fator do GoGuacamole vale para login por senha. Em login por SSO, exija o MFA no seu provedor de identidade.
Migrando do Apache Guacamole
O importador lê o banco do Guacamole (MySQL ou PostgreSQL) numa transação somente leitura, só com SELECT. Comece sempre com --dry-run:
# não grava nada importado; a origem nunca é alterada
goguacamole import guacamole --source-driver mysql \
--dsn 'guacamole_user:senha@tcp(db-antigo:3306)/guacamole_db' --dry-run
# importação de verdade (reexecutar é idempotente; conflitos são ignorados por padrão)
goguacamole import guacamole --source-driver mysql \
--dsn 'guacamole_user:senha@tcp(db-antigo:3306)/guacamole_db'
Um detalhe: mesmo em --dry-run, o comando abre o banco de destino do GoGuacamole e aplica as migrações pendentes do esquema, como faz o serve. Os dados importados é que não são gravados.
Vêm usuários (com o hash e o salt, então as senhas continuam funcionando e são migradas para bcrypt no próximo login), validade e janela de acesso, grupos e membros, pastas, conexões com parâmetros (segredos recifrados), limites, as permissões de sistema que têm equivalente (ADMINISTER, CREATE_CONNECTION, CREATE_USER e AUDIT) e as permissões de leitura das conexões. Grupos aninhados são achatados. Ficam de fora, e aparecem no relatório, sharing profiles e permissões que não sejam de leitura. Grupos de balanceamento viram pastas comuns.
No fim, o importador compara, usuário por usuário, as conexões e os privilégios que cada um tinha na origem e tem no destino, e imprime linhas ACCESS DIFF. Essas diferenças são relatadas, não bloqueadas: por exemplo, se já existir no destino uma conta com o mesmo nome e com ADMINISTER, o relatório mostra o ganho de privilégio. A flag --accept-expansion só decide se a leitura de grupos de balanceamento vira leitura em cada conexão filha. O caminho sensato é rodar o dry-run numa cópia do banco de produção, revisar cada ACCESS DIFF e fazer um piloto com alguns usuários lado a lado com o cliente Java, em outra porta, antes de trocar o proxy.
A CLI
goguacamole serve # servidor HTTP(S)
goguacamole migrate up|down|status # migrações do banco
goguacamole user create|passwd|list|disable
goguacamole import guacamole # importação do Apache Guacamole
goguacamole config print # configuração efetiva, segredos ocultos
goguacamole healthcheck # usado pelo HEALTHCHECK do Docker
goguacamole version
A configuração vem de config.toml (há um config.toml.example no repositório) e de variáveis de ambiente com prefixos como APP_, DB_, GUACD_, RECORDING_, REDIS_ e OBJSTORE_.
Cliente Java x GoGuacamole
| Apache guacamole-client | GoGuacamole | |
|---|---|---|
| Execução | Java, Tomcat e extensões | um binário Go com a interface embutida |
| Banco | MySQL, PostgreSQL ou SQL Server via extensão JDBC | SQLite, MariaDB/MySQL ou PostgreSQL embutidos |
| SSO e MFA | extensões separadas, configuradas em arquivos | embutidos, configurados e testados pela interface |
| Gravação | gravação em disco; player via extensão | player embutido, transcrições SSH, retenção, legal hold, object storage |
| Atualização | reimplantar o WAR e as extensões | trocar o binário ou a imagem; migrações rodam ao iniciar |
| guacd | guacamole/guacd |
o mesmo guacamole/guacd (1.6) |
Checklist de produção
APP_ENV=production: o serviço só inicia comTLS_ENABLED=trueouAPP_COOKIE_SECURE=true, com uma chave definida (APP_SECRET_KEYouAPP_SECRET_KEY_FILE), e recusaTLS_SELF_SIGNED=trueeGUACD_TLS_INSECURE=true. Ele não verifica se o proxy realmente tem TLS: isso fica por sua conta.APP_SECRET_KEYnum cofre de segredos, com backup junto do banco. Teste a restauração num host limpo.- HTTPS:
TLS_ENABLED=trueno próprio binário, ou TLS terminado no proxy comAPP_COOKIE_SECURE=trueeAPP_TRUSTED_PROXIES, mantendo o trecho proxy → goguacamole em127.0.0.1ou numa rede privada. APP_BASE_URLcom a URL pública.- guacd numa rede isolada, nunca exposto; considere
GUACD_TLS=true. - MFA (TOTP ou Duo) para logins por senha; para SSO, MFA no provedor.
- Redis em rede privada (ou com
REDIS_PASSWORDeREDIS_TLS), um banco por instalação. - Object storage com bucket ou prefixo dedicado e credenciais limitadas a put/get/list/delete nele.
Limites atuais
O projeto é novo, está na série 0.x, e vale saber o que ainda não tem:
- Uma réplica para os túneis: o registro de túneis, os limites e os encerramentos de sessão ficam na memória do processo. Escale verticalmente. As tarefas de fundo e os uploads já são seguros com várias instâncias.
- Sem compartilhamento de sessão (entrar numa sessão ativa de outro usuário) e sem grupos de balanceamento.
- Grupos de usuários planos: grupos aninhados ainda não existem.
- Binários e imagem para Linux/amd64; outras arquiteturas exigem compilar.
- Telnet e Kubernetes, que o guacd suporta, ainda não estão nos formulários.
O ROADMAP lista esses itens e outros, como métricas Prometheus, pacotes .deb/.rpm e Helm chart.
Conclusão
O guacd continua sendo a peça certa para falar RDP, SSH e VNC. O que o GoGuacamole muda é o resto: um binário, um container para começar, login externo e MFA sem extensões, gravação com player e transcrição, e um importador que traz o banco do Guacamole e relata cada diferença de acesso. Se você já usa o Guacamole, rode o importador com --dry-run numa cópia do banco e compare. Se está começando, o docker run da imagem all-in-one basta para testar.
Issues e pull requests são bem-vindos no repositório do GoGuacamole. Outros projetos em Go aqui do blog seguem a mesma ideia de binário único: go-postfixadmin, go-imapsync e mcp-wp-go. E, para acesso SSH direto, sem gateway, veja chaves de autenticação no SSH.