3 pontos por GN⁺ 2023-07-06 | 1 comentários | Compartilhar no WhatsApp
  • suc é o Simple Unix Chat, que implementa alguns recursos centrais de chats no estilo Slack/Mattermost com uma combinação de primitivos básicos do UNIX e Bash; o loop central tem 5 linhas, embora o script real seja mais longo
  • As mensagens são lidas uma linha por vez da entrada padrão, recebem data e nome de usuário e são acrescentadas ao arquivo do canal em /var/lib/suc/; autenticação e controle de permissões ficam a cargo do SSH e do modelo de permissões do UNIX
  • Em contraste com o servidor Mattermost, que tem cerca de 500 mil linhas só de código Go, suc destaca que é possível criar um comportamento central semelhante com muito menos código
  • usuc combina tail -f, rlwrap, pygmentize e o prefixo de comando : para lidar com leitura de canais, saída em estilo Markdown e compartilhamento de resultados de comandos
  • Como os canais são simples arquivos de texto, eles podem ser facilmente conectados a ferramentas UNIX como grep, tail, bat, lnav, hooks do Git e scripts de bots

A complexidade que suc tenta reduzir

  • suc é a sigla de Simple Unix Chat; ele tem menos recursos que Slack, Mattermost e IRC, e o total de linhas passa de 5, mas o loop central que grava mensagens é composto por 5 linhas de Bash
  • Os recursos-alvo são chat em tempo real com rich text, compartilhamento de arquivos, controle de acesso granular, automação e integração com ferramentas externas, criptografia em trânsito, criptografia opcional em repouso e autenticação de usuários
  • A implementação pequena é possível porque aproveita os primitivos consistentes e combináveis oferecidos por implementações UNIX modernas
  • O servidor Mattermost aparece como contraste, com cerca de 500 mil linhas só de código Go; a visão é que suc consegue implementar as funções centrais do Mattermost com 0,005% desse código

Loop de 5 linhas para escrever mensagens

  • O loop Bash central de suc lê uma linha da entrada padrão, adiciona a data e o nome real do usuário, e a acrescenta ao arquivo /var/lib/suc/"$1"
  • O script real é mais longo por causa de pré-processamento de segurança e do cálculo de cores para nomes de usuário, mas a operação central de escrever o histórico do chat se resume a três coisas
    • Ler uma linha da entrada padrão
    • Anexar a data e o nome real do usuário
    • Acrescentar ao arquivo do canal em /var/lib/suc/
  • Autenticação, controle de acesso, criptografia e rich text não ficam no código interno de suc, mas sim em SSH, APIs de controle de acesso do UNIX e modularidade baseada em texto

A autenticação fica a cargo do SSH

  • O processo suc é executado por um usuário já autenticado, portanto o próprio suc não tem código de autenticação
  • A autenticação remota é tratada por ssh, como em um servidor UNIX comum
    • Segredos compartilhados, como senhas
    • Desafios criptográficos
    • Dispositivos geradores de senhas de uso único (OTP)
    • MFA combinando vários métodos
  • ssh também autentica o servidor para o cliente, evitando ataques man-in-the-middle, e criptografa todos os dados entre cliente e servidor
  • A instalação de suc depende de um host UNIX e da configuração de um servidor ssh
    • O usuário precisa existir no sistema
    • O servidor ssh precisa permitir o login remoto desse usuário
  • No GNU Guix, é possível adicionar usuários e chaves SSH em uma linha com configuração declarativa; quando a linha de configuração desaparece, o usuário é removido durante a reconfiguração

O controle de acesso fica a cargo do modelo de permissões do UNIX

  • suc não inclui código de controle de acesso, assim como não inclui autenticação; essa abordagem é chamada de agnosticismo de segurança (security agnosticism)
  • O kernel UNIX conhece usuários, grupos, processos e arquivos, e decide permissões de leitura e escrita de arquivos com base no dono efetivo (effective owner) do processo
  • Os arquivos de canal em /var/lib/suc pertencem ao usuário suc, e são configurados para permitir que usuários leiam alguns canais por meio de permissões de leitura por grupo
  • Usuários comuns não escrevem diretamente nos arquivos; eles executam suc, e o kernel cuida da correspondência de permissões e do bloqueio
  • Em comunidades mais restritivas, é possível ter binários setuid por grupo
    • Por exemplo, suc_blue pertence ao usuário blue e só pode ser executado pelo grupo blue
    • Usuários do grupo blue podem escrever no canal blue por meio de suc_blue
    • Usuários do grupo red não podem ler nem escrever no canal blue
  • O script auxiliar de criação de canais suc_channel.sh tem cerca de 80 linhas, e usuários do GNU Guix podem criar o binário setuid, o usuário, o grupo e o arquivo de canal necessários com uma única linha (suc-private-channel "red" "red")
  • As atualizações transacionais do GNU Guix garantem que alterações de configuração sejam aplicadas completamente ou não sejam aplicadas, e permitem voltar a um estado anterior de funcionamento

Rich text e usuc

  • Muitos apps de chat renderizam texto com motores HTML, e o cliente desktop do Mattermost usa Electron
  • suc usa um arquivo de texto por canal, e o usuário pode visualizá-lo com ferramentas de linha de comando como tail ou cat
  • Com suporte do terminal a códigos de escape ANSI e UTF-8, é possível criar cores, emojis e uma experiência básica de rich text
  • suc apenas escreve no arquivo do canal; a leitura fica a cargo de usuc
    • Como suc é um binário privilegiado, sua lógica e dependências externas devem ser minimizadas
    • usuc roda com as permissões do usuário chamador, então pode incluir recursos com mais liberdade
  • usuc atua como cliente de chat ao encadear várias ferramentas pequenas
    • Fornece histórico e edição de linha com rlwrap
    • Escolhe o binário suc setuid adequado conforme o dono do arquivo do canal
    • Mostra as últimas 20 linhas e novas linhas posteriores com tail -f -n 20
    • Verifica se a linha de entrada começa com :
    • Processa o texto digitado pelo usuário com pygmentize
  • pygmentize interpreta a entrada padrão como Markdown e emite texto colorido ANSI, permitindo suporte a marcações como **bold** com uma linha de código

Comandos de chat e integração com shell

  • Se uma mensagem começa com :, usuc não a envia como mensagem comum; ele executa o comando e envia o resultado da saída para suc
  • Por exemplo, ao executar gum style --border=rounded --bold --foreground=#F00 "Hello World !", um texto com borda e estilo aparece no canal
  • Os comandos são executados no namespace do usuário que chamou usuc, permitindo que cada usuário tenha seus próprios macros de chat sem afetar outros usuários
  • Em the dam, isso é usado durante RPGs de mesa para rolar dados com comandos como roll 2d6

Pipes de texto e integração com ferramentas externas

  • Em vez de usar o recurso de execução de comandos de usuc, o usuário pode encaminhar diretamente a saída de comandos para suc a partir do shell
  • Ao enviar a saída colorida de bat para suc, é possível compartilhar trechos de código no canal com destaque de sintaxe
  • make test > testlog || (suc devops < testlog ; exit 1) é um exemplo de one-liner em Bash que envia o log para o canal devops quando os testes falham
  • Esse one-liner pode entrar no hook update de um repositório Git; no push, ele executa os testes e, em caso de falha, rejeita a atualização e avisa a equipe de DevOps
  • Trabalhos equivalentes às integrações do Slack ou Mattermost podem ser montados com pipes de texto UNIX e scripts

Leitura de canais, notificações e bots

  • Um canal suc é um arquivo de texto continuamente atualizado, então tail -f pode ler e processar novas linhas
  • É possível criar um pipeline que exiba notificações com notify-send quando novas mensagens chegam, e usar grep para filtrar apenas nomes ou palavras-chave como “build failure”
  • Para combinar vários canais em um único feed, é possível usar tail -f /var/lib/suc/*
  • Com lnav, é possível usar memória da posição lida, favoritos, cores por canal, parsing de data, nome de usuário e campos personalizados, filtragem e consultas SQL
  • Bots podem ser implementados se conseguirem ler e escrever no canal ao mesmo tempo
    • O exemplo procura expressões de comprimento como 33000 ft no canal
    • Converte para metros com o comando units
    • Escreve uma resposta como [metric_bot] 33000 ft is 10058.4 meters. no mesmo canal

Comparações e conclusão

  • Slack, Mattermost e Discord são citados como sistemas grandes para comparação, mas considera-se que comparações mais justas também incluiriam IRC, talk e write
  • O IRC é avaliado como tendo menos recursos que suc porque precisa de um bouncer para ler o histórico de chats
  • Segundo o texto, talk, ytalk e wall não puderam ser colocados para funcionar em distribuições Linux modernas
  • suc deixa a autenticação com o SSH, o controle de acesso e a composabilidade com o UNIX, e mantém canais como arquivos de texto
  • As ferramentas podem ser escritas em qualquer linguagem, desde que consigam ler e escrever texto

1 comentários

 
GN⁺ 2023-07-06
Comentários do Hacker News
  • Isso imediatamente me lembrou de “Master Foo and the Ten Thousand Lines”: http://catb.org/~esr/writings/unix-koans/ten-thousand.html
    É a história em que Master Foo diz a um programador visitante que “uma única linha de script de shell contém mais da natureza Unix do que dez mil linhas de C”

  • Se a funcionalidade central definida de algo pode ser recriada de forma fácil e trivial demais, então talvez essa função não seja o cerne nem a fonte real do seu valor
    Fico me perguntando quanto do desenvolvimento de UI desde os anos 1980 a engenharia de software absorveu e automatizou, ou se isso ainda pertence ao domínio da arte puramente humana

    • Verdade. Por exemplo, o principal valor agregado do Slack não é “enviar mensagens entre usuários autenticados”
      Mesmo antes de o Slack surgir e empurrar outras opções para fora, já existiam dezenas de apps, tanto comerciais quanto open source, que faziam isso
      O valor central do Slack está no passo seguinte. Ele oferece uma UI/UX visualmente agradável e muito intuitiva, de modo que quase qualquer pessoa pode sentar e usar imediatamente sem estudo, configuração, manual ou treinamento. Não é preciso conhecimento técnico e nem entender mais do que “digite nome de usuário e senha”; o resto parece autoevidente
    • Uma visão muito perspicaz. Nesse caso, o valor do Slack é a facilidade de uso, e o suc definitivamente não é para usuários comuns
      Mas isso levanta uma pergunta mais difícil. Comparado à alternativa de treinar melhor usuários que já sabem fazer chamadas básicas de linha de comando, o preço do Slack vale a pena? E considerando ainda que o Slack prende seus dados e não os devolve? Quão caro é o custo de sair do Slack?
      O suc é desconfortável de usar, sem dúvida, mas parece possível convergir para algo menos inchado e menos monopolista que o Slack
  • Gostei do resultado em si. É legal ver o que dá para construir com composabilidade
    Só que a comparação com Slack e afins não me convence muito. Nenhum engenheiro vai querer se responsabilizar em produção por algo montado assim, remendando peças. Como contratar gente para isso, e como fazer testes e depuração, logs e análise?
    Este post replica parte da funcionalidade de chat de apps do tipo Slack, mas chat não é 100% do que esses apps são. São coisas feitas em contextos completamente diferentes, então atacar o estado atual como inchado parece uma leitura torta à la Stockton Rush. Existe inchaço em software, claro, mas “olha mãe, fiz um submarino com tralha, logo submarinos caros são idiotas” não soa como a refutação que o autor pretendia fazer

    • A questão é que, se a área de superfície do codebase fica menor, boa parte dessas coisas passa a importar menos
      Utilitários individuais normalmente são fáceis de entender, testar e depurar isoladamente, e logging já vem embutido no sistema. Afinal, não existe o syslog? E por que análise seria necessária?
    • Eu também li assim. Foi uma engenharia excelente e muito divertida de ler, mas teria sido mais honesto comparar com IRC em vez de Slack
      Ainda assim, fico realmente curioso sobre como seria uma ferramenta tipo Slack se priorizasse agressivamente a simplicidade acima de novos recursos. Parece que o capitalismo como um todo não incentiva esse tipo de desenvolvimento, então há poucos exemplos de software corporativo tão simples quanto seria razoavelmente possível. Mesmo assim, é um experimento mental interessante
  • De forma parecida, também existe o ii, que roda diretamente sobre IRC: https://tools.suckless.org/ii/
    Usa um único arquivo padrão para todas as conversas, a autenticação e o controle de acesso são geridos pela própria rede IRC, e já existem muitas UIs. Assim como neste post, plugins podem ser qualquer coisa que leia um arquivo e escreva em outro

  • Seria divertido fazer um diagrama spawn of unix reunindo startups que recortaram nichos das funcionalidades do Unix, no estilo do diagrama do craigslist
    https://thegongshow.tumblr.com/post/345941486/the-spawn-of-craigslist-like-most-vcs-that-focus
    ftp/rsync => Dropbox
    suc => Slack
    ...

  • Se o título parece clickbait e você está prestes a pular, ele até tem um certo ar de isca mesmo, mas na prática era um texto muito bom
    Ele fala de um utilitário chamado “suc” (Simple Unix Chat), que implementa funcionalidades de servidor no estilo Slack e Discord com uma base de código minúscula
    A novidade é que ele aproveita ferramentas e metodologias Unix já existentes em vez de reinventá-las. A autenticação fica por conta do SSH, os canais são simplesmente arquivos, e o controle de administrador/moderador é feito com grupos de usuários e permissões de arquivo. Coisas como texto formatado ou upload de arquivos podem ser resolvidas escrevendo os dados desejados no canal e deixando o cliente interpretar. Bots também ficam muito fáceis, basta ligar um pipe ao arquivo do canal
    Não dá para fazer um clone perfeito do Slack só com as 5 linhas mostradas no texto para escrever mensagens em um canal, mas é bem impressionante até onde dá para chegar com ferramentas Unix realmente simples

    • As plataformas de chat existentes não “reinventaram” ferramentas Unix; elas simplesmente não as usaram porque elas não são adequadas para construir isso em cima
      Existe um motivo para haver praticamente zero sistemas grandes de produção escritos com o paradigma de “tudo é arquivo” e shell scripts. Para qualquer sistema minimamente não trivial que precise ser usado no mundo real, isso se torna inaceitável
    • Para a maioria dos usuários finais, Slack é um cliente
      Junto com isso vêm UI de administração, UI de gerenciamento de usuários, extensibilidade, webhooks etc. “Todo mundo faz login naquela máquina e pode conversar com outros usuários no Linux” não resolve nada disso. Todas as variantes de *nix já tinham isso desde antes de a maioria dos leitores daqui nascer
    • Parece que arquivos append-only cairiam muito bem aqui. Porque assim seria possível garantir que usuários comuns e bots não pudessem editar as postagens de outras pessoas
      Infelizmente, esse recurso do Plan 9 ainda não foi portado para Linux. É um bit de permissão parecido com “leitura” ou “escrita”, mas muito mais específico
    • “Aproveitar ferramentas e metodologias Unix existentes” quase sempre acaba significando “implementei X com um número inacreditavelmente pequeno de linhas de código”
      Não é uma crítica ao autor; o próprio texto deixa isso bem claro e com bastante honestidade
  • Slack e Discord têm compartilhamento de tela e chat por voz. Isso não é um extra, e sim algo bastante decisivo para a dominância deles no mercado

    • Decisivo, mas parece que a qualidade está piorando cada vez mais
  • O post do blog é sobre o suc, e ele admite com honestidade desde o início que o título pode induzir um pouco ao erro, mas no fim há uma mensagem importante que não tem relação direta com o suc
    Quase todo software moderno é absurdamente inchado. A ideia de que um servidor de chat rico precise de 1,7 milhão de linhas é absurda. Parafraseando Bill Gates, acho que eu não conseguiria “consumir” tantas linhas de código em um servidor de chat nem se me esforçasse
    Não foi só levantar o problema ou sugerir uma alternativa; também merece aplausos por lembrar que não é preciso reinventar a roda. Implementar algo de forma inteligente aproveitando propriedades de sistemas e subsistemas já existentes — por exemplo, usar SSH para autenticação e criptografia — mesmo que fora do propósito originalmente imaginado, mas ainda alinhado à intenção dessas propriedades, é um ótimo exemplo do espírito hacker
    Tomara que o mundo do software consiga aprender bastante com esse conceito. Custos mais baixos, prazos de desenvolvimento menores e menos tempo de engenharia desperdiçado reescrevendo código funcionalmente equivalente são bons para desenvolvedores, empresas, usuários finais e, no fim das contas, para a humanidade. Quero ver mais destruição criativa desse tipo no futuro, e também gostaria de aplicar esse tipo de técnica inteligente em projetos futuros

    • Só para constar, a maior parte das linhas de código em TypeScript está nos testes E2E[0] e no diretório webapp[1]. Como o nome diz, o webapp contém “o código cliente do aplicativo web do Mattermost”
      Então o mais correto seria contar apenas as linhas de código em Go
      [0] https://github.com/mattermost/mattermost/tree/master/e2e-tests
      [1] https://github.com/mattermost/mattermost/tree/master/webapp
    • Já participei de uma reunião para escolher um framework de autenticação web para usar em algum site, e a discussão se estendeu tanto por causa de bikeshedding que, no meio da reunião, eu implementei algo baseado nos recursos de autenticação que o próprio HTTP já oferece e ainda fiz uma demo antes de a reunião acabar
    • Em princípio eu concordo, mas boa parte do inchaço existe porque se tenta dar suporte a várias plataformas com uma única base de código
      Isso normalmente é uma boa ideia porque acelera o lançamento e reduz o tamanho da organização. Mas há um grande trade-off em complexidade e abstração. Quanto mais alto o nível de abstração, pior isso pode ficar. Então não é como se não houvesse motivo nenhum
  • Atualmente, a maioria das distribuições impede a criação de scripts bash setuid por motivos de segurança.
    Mas é possível obter quase o mesmo efeito com uma entrada específica no sudoers.

    • Na verdade, isso vale não só para bash, mas para quase todos os scripts com shebang; a exceção seria algo como scripts em Perl. Isso porque o Perl ativa automaticamente o modo taint quando uid != euid.
      https://perldoc.perl.org/perlsec#Taint-mode
      Em alguns sistemas, pode haver uma condição de corrida fundamental:
      https://perldoc.perl.org/perlsec#Shebang-Race-Condition
      Também vale a pena ver o uso de sudo -T:
      https://perldoc.perl.org/perlsec#Using-Sudo
      É uma pena que o modo taint tenha sido removido recentemente do Ruby. O rastreamento de contaminação é um mecanismo poderoso que pode ir muito além de scripts. Por exemplo, se o #html_safe do Rack/Rails fosse tratado com rastreamento de contaminação, ele funcionaria de forma mais rigorosa e poderia falhar assim que uma string de usuário contaminada fosse concatenada/interpolada/formatada em um fragmento SQL, bloqueando por projeto uma classe de injeções SQL.
      Antigamente, também havia gente que usava rastreamento de contaminação de forma muito eficaz com CGI/mod_perl. Nunca é uma solução universal, mas ainda é uma ferramenta bastante eficaz.
    • Usa-se um wrapper em C para chamar o script shell. Fica feio e exige endurecer um pouco mais o script, mas funciona :)
      https://gitlab.com/edouardklein/suc/-/blob/master/suc_wrapper.c
    • Existe mesmo alguma distribuição que permita isso? Quando tentei fazer por conta própria, tudo o que li dizia que setuid não era aplicado, porque o que é executado não é o próprio arquivo, e sim o alvo indicado no shebang.
      Até ver o arquivo wrapper no repositório do GitLab, eu estava lendo na esperança de que houvesse algum jeito.
  • Isso é outro assunto, mas achei surpreendente não terem mencionado o talk(1): https://man.netbsd.org/talk.1

    • Ou então write(2)[1]. Eu pensei imediatamente em ntalk, porque foi a última vez que usei algo assim na mesma máquina. Na época, o IRC não era criptografado.
      O lado bom é que dá para restringir o recebimento ao ambiente local, e aí passa a ser necessário ter uma conta de usuário naquela máquina. Por isso, combina bem com lugares como o SDF ou clusters maiores. Você não precisa implementar autenticação por conta própria; pode deixar isso com PAM ou BSD_Auth. Também não precisa lidar diretamente com sockets nem criptografia; pode deixar isso com TLS. Outra possibilidade é entrar em um shell seguro via SSH e usar tmux com (n)talk.
      Claro, (n)talk foi escrito em C, então não são 5 linhas de bash.
      [1] https://man7.org/linux/man-pages/man2/write.2.html