Por que é que isto nos interessa: somos a Geonode e vendemos proxies, pelo que vemos imensos comandos que contêm credenciais — muitas vezes, tanto uma palavra-passe de proxy como uma palavra-passe de destino na mesma linha. O aviso prático que vale a pena destacar desde já é que as credenciais num comando curl acabam por ficar no histórico do shell, nas listas de processos e em tudo o que colar num ticket de suporte. Já recebemos, mais do que uma vez, capturas de ecrã contendo palavras-passe ativas. A abordagem «.netrc» (O que é que eu estou a fazer?) apresentada abaixo resolve o problema em cerca de um minuto e não custa nada, sendo igualmente aplicável às credenciais de proxy, tema abordado no final.
A sintaxe básica
curl -u username:password https://api.example.com/private
O manual do curl descreve -u, --user <user:password>
: «Especifique o nome de utilizador e a palavra-passe a utilizar para a autenticação no servidor.»
«Basic» é o esquema predefinido, pelo que --basic
é normalmente redundante. O manual afirma exatamente isso: «Utilize a autenticação HTTP Basic com o anfitrião remoto. Este método é o predefinido e esta opção é normalmente desnecessária, a menos que a utilize para substituir uma opção definida anteriormente que estabeleça um método de autenticação diferente.»
O que realmente é enviado pela rede é um cabeçalho:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Trata-se de username:password
codificado em base64 — codificado, não encriptado. Qualquer pessoa que consiga ver o pedido pode descodificá-lo num único passo. É por isso que a autenticação básica através de HTTP simples equivale a enviar a sua palavra-passe em texto simples, e é por isso que só deve ser utilizada através de HTTPS.
Uma restrição sintáctica do manual: «O nome de utilizador e a palavra-passe são separados pelo primeiro dois-pontos, o que torna impossível utilizar um dois-pontos no nome de utilizador com esta opção. Na palavra-passe, ainda é possível.» Portanto, um dois-pontos na palavra-passe é permitido; um dois-pontos no nome de utilizador não é.
Por que razão a linha de comandos não é o local adequado
O manual é muito claro a este respeito:
Nos sistemas em que funciona, o curl oculta o argumento da opção especificada nas listagens de processos. Isto não é suficiente para proteger as credenciais de serem eventualmente vistas por outros utilizadores no mesmo sistema, uma vez que estas continuam visíveis por um instante antes de serem apagadas. Esses dados sensíveis devem ser obtidos a partir de um ficheiro ou equivalente e nunca utilizados em texto simples na linha de comandos.
Quatro casos de exposição distintos, todos reais:
Histórico do shell. ~/.bash_history ou o equivalente no zsh, em texto simples, por tempo indeterminado.
Listas de processos. Visíveis para outros utilizadores na máquina durante o breve intervalo antes de o curl os apagar.
Registos. Qualquer coisa que registe os comandos executados por um script.
Saída colada. Relatórios de erros, sistemas de acompanhamento de problemas, mensagens de chat, capturas de ecrã.
A última é a mais comum na prática e a menos considerada.
Três formas mais seguras
1. Deixar o curl solicitar a palavra-passe. Indique apenas o nome de utilizador e o curl solicitará a palavra-passe de forma interativa, lendo-a sem a exibir no ecrã:
curl -u username https://api.example.com/private
Nada é guardado, nada é registado. Esta é a abordagem correta para tudo o que digitar manualmente.
**2. Utilize um ficheiro ``.netrc`
.** O manual descreve ``-n, --netrc
: «Faz com que o curlprocure no ficheiro.netrc, no diretório pessoal do utilizador, o nome de utilizador e a palavra-passe... Se utilizado com HTTP, o curl` ativa a autenticação do utilizador.»
Crie um ficheiro ``~/.netrc`
`:
machine api.example.com
login myusername
password mypassword
Em seguida, restrinja-o, pois o curl não o fará por si — o manual refere que «o curl não emite qualquer aviso se esse ficheiro não tiver as permissões corretas (não deve ser legível nem pelo grupo nem por todos)»:
chmod 600 ~/.netrc
curl -n https://api.example.com/private
Três detalhes úteis do manual. «O ficheiro netrc fornece credenciais para um nome de anfitrião, independentemente do protocolo e do número de porta utilizados», pelo que uma entrada abrange um anfitrião. --netrc-file
«substitui todas as outras formas de identificar o ficheiro», o que é útil para ficheiros de credenciais específicos de cada projeto. E, a partir do curl 8.16.0, uma variável de ambiente NETRC
pode indicar o nome do ficheiro. No Windows, tanto .netrc
como _netrc
são verificados no diretório pessoal, sendo dada preferência ao primeiro.
--netrc-optional
é a variante que utiliza o ficheiro se este estiver presente e não falha se estiver ausente — mais adequada para scripts que possam ser executados em qualquer uma das situações.
3. Ler a partir de uma variável de ambiente. Quando um ficheiro não for viável, pelo menos mantenha-o fora do histórico:
read -rs API_PASS
curl -u "myuser:${API_PASS}" https://api.example.com/private
Repare no -s
em read
para que a palavra-passe não seja exibida. Isto continua visível no ambiente do processo, pelo que é uma opção intermédia e não a melhor.
Para scripts, a solução é utilizar ``.netrc`
com ``chmod 600
`. É a opção que o manual recomenda e a que elimina todos os riscos de exposição acima referidos.
Esquema Basic versus os outros esquemas
O curl suporta vários esquemas, e saber qual é qual evita uma série de confusões.
| Opção | Esquema | Palavra-passe na rede |
|---|---|---|
--basic | HTTP Basic (predefinição) | Codificada em Base64, efetivamente em texto simples |
--digest | HTTP Digest | Resposta-desafio com hash |
--ntlm | NTLM | Ambientes Windows |
--negotiate | SPNEGO / Kerberos | Baseado em bilhete |
--anyauth | Automático | Depende do que for escolhido |
--oauth2-bearer | Token de portador | O próprio token |
Digest está documentado da seguinte forma: «Ativar a autenticação HTTP Digest. Este esquema de autenticação evita o envio da palavra-passe pela rede em texto simples. Utilize esta opção em combinação com a opção normal «--user» para definir o nome de utilizador e a palavra-passe.»
--anyauth é a opção de conveniência cujo custo o manual indica claramente: «Determina automaticamente o método de autenticação e utiliza o mais seguro que o site remoto declare suportar. Isto é feito enviando primeiro um pedido e verificando os cabeçalhos da resposta, o que pode implicar uma ida e volta adicional na rede.»
Uma ida e volta adicional por pedido não é gratuita quando em grande volume. E há uma falha específica sobre a qual o manual alerta: «Não é recomendado utilizar «--anyauth» se efetuar uploads a partir de stdin, uma vez que pode exigir que os dados sejam enviados duas vezes e, nesse caso, o cliente tem de ser capaz de retroceder. Se tal for necessário ao fazer o upload a partir de stdin, a operação de upload falhará.»
Portanto: utilize «--anyauth» quando realmente não souber o que o servidor pretende e especifique o esquema assim que o souber.
Os tokens «Bearer» são o que a maioria das APIs modernas utiliza atualmente e não têm nada a ver com a autenticação básica:
curl --oauth2-bearer "mF_9.B5f-4.1JqM" https://api.example.com/me
Equivalente a definir manualmente Authorization: Bearer .... Note-se que um token «Bearer» é a credencial — qualquer pessoa que o possua pode utilizá-lo — pelo que merece o mesmo tratamento que uma palavra-passe.
Interpretar a resposta
Um breve guia de diagnóstico para quando a autenticação não funciona.
**401 Unauthorized
** significa que o servidor solicita credenciais ou rejeitou as que enviou. A resposta inclui um cabeçalho WWW-Authenticate
que indica o esquema esperado, e a sua leitura evita adivinhar:
curl -sS -o /dev/null -D - https://api.example.com/private | grep -i www-authenticate
Se indicar Digest
e tiver enviado Basic, essa é a sua resposta.
**403 Forbidden
** é diferente e é frequentemente mal interpretado. Autentificou-se com sucesso, mas não tem permissão para realizar esta ação. Alterar a sua palavra-passe não vai ajudar; alterar as suas permissões poderá ajudar.
**407 Proxy Authentication Required
** significa que o proxy solicita credenciais, e não o destino. Cabeçalho diferente, opção diferente, abordada a seguir.
**Um 200
com uma página de início de sessão** significa que o ponto final não utiliza autenticação HTTP de todo — utiliza um formulário e um cookie de sessão, e -u
não tem qualquer efeito neste caso. Verifique o Content-Type
: se esperava JSON e obteve text/html
, é provável que tenha sido isto que aconteceu.
Para confirmar o que enviou efetivamente:
curl -v -u user:pass https://api.example.com/private 2>&1 | grep -i '^> authorization'
Lembre-se do aviso do manual de que a saída detalhada «pode conter dados confidenciais, incluindo nomes de utilizador, credenciais ou conteúdo de dados secretos» — oculte esses dados antes de partilhar.
Criar o cabeçalho manualmente
Por vezes, -u
não é o que se pretende, e saber o que este endereço realmente produz permite contornar as suas limitações.
Quando o nome de utilizador contém dois pontos. -u
divide na primeira ocorrência de dois pontos, pelo que um nome de utilizador como service:reader
não pode ser expresso. Crie o cabeçalho diretamente:
CRED=$(printf '%s' 'service:reader:mypassword' | base64 -w0)
curl -H "Authorization: Basic ${CRED}" https://api.example.com/private
Repare em printf
em vez de echo
, que acrescenta uma nova linha que acaba por ficar dentro da credencial codificada e produz um enigmático erro 401. E base64 -w0
para evitar o quebra de linha — o GNU base64
quebra a linha aos 76 caracteres por predefinição, e um cabeçalho com uma nova linha incorporada é um pedido malformado. No macOS, o simples base64
não quebra a linha e o sinalizador é desnecessário.
Quando precisar das credenciais de um gestor de segredos. A maioria das ferramentas de gestão de segredos envia a saída para o stdout, e manter o valor numa variável em vez de num ficheiro limita o seu tempo de vida:
TOKEN=$(vault kv get -field=token secret/api)
curl -H "Authorization: Bearer ${TOKEN}" https://api.example.com/me
Quando quiser que o cabeçalho conste num ficheiro de configuração em vez de no comando. O curl lê as opções a partir de ~/.curlrc` ` ou de um ficheiro cujo nome termine em -K :
# api-auth.conf
--user "myuser:mypassword"
--header "Accept: application/json"
curl -K api-auth.conf https://api.example.com/private
Restrinja o ficheiro com chmod 600` `. Esta é uma solução intermédia razoável quando .netrc não se adequa — por exemplo, quando precisa de um token em vez de um nome de utilizador e palavra-passe.
Uma advertência específica sobre ~/.curlrc
. Aplica-se a todas as chamadas do curl feitas por esse utilizador, incluindo aquelas que não foram escritas por si. Colocar credenciais nesse local significa enviá-las para qualquer servidor que um script venha a solicitar. Utilize um ficheiro com nome específico com -K
para qualquer informação sensível e reserve ~/.curlrc
para predefinições inofensivas, como --show-error
e --location
.
A autenticação do proxy é independente
A distinção que causa mais confusão na nossa fila de apoio.
Podem estar em jogo dois conjuntos de credenciais independentes: um para o proxy e outro para o destino. Utilizam cabeçalhos diferentes, códigos de estado diferentes e opções curl diferentes.
curl -x http://proxy.example.com:9000 \
--proxy-user proxyuser:proxypass \
-u apiuser:apipass \
https://api.example.com/private
--proxy-user
autentica-se no proxy; -u
autentica-se no destino. Confundir os dois resulta num erro 407 quando se esperava um 401, ou vice-versa.
O esquema do proxy tem opções paralelas — --proxy-basic
, --proxy-digest
, --proxy-anyauth
, --proxy-negotiate
— que espelham as do lado do destino.
Duas notas práticas.
As credenciais na URL do proxy têm o mesmo problema de exposição, mais um. -x http://user:pass@proxy:9000
coloca a palavra-passe na linha de comandos e em qualquer variável de ambiente que contenha a URL do proxy, que é onde http_proxy
normalmente se encontra. Codifique com percentagem qualquer @
, :
ou /
na palavra-passe, ou o analisador de URL irá dividir no local errado.
Distinga qual foi o salto que rejeitou a sua ligação em vez de adivinhar:
curl -sS -o /dev/null -x "$PROXY" \
-w 'connect=%{http_connect} status=%{response_code}\n' \
https://api.example.com/private
connect=407
significa que o proxy rejeitou a sua ligação e nunca chegou ao destino. connect=200 status=401
significa que o proxy funcionou e que o destino solicita credenciais. Duas soluções diferentes.
E uma nota específica sobre a autenticação por proxy: muitos fornecedores oferecem listas de IPs autorizados como alternativa ao nome de utilizador e palavra-passe. Se o seu endereço de origem for estável, isso elimina completamente as credenciais dos seus comandos, o que é a solução mais simples disponível — sem ficheiros, sem variáveis de ambiente, nada que possa vazar.
Quando o site não utiliza, de todo, autenticação HTTP
Uma grande parte dos relatos do tipo «a autenticação básica do curl não está a funcionar» refere-se a casos em que o site nunca utilizou autenticação HTTP.
Como verificar. Envie um pedido para o URL protegido sem credenciais e observe a resposta:
curl -sS -o /dev/null -D - https://example.com/dashboard
Um 401
com um cabeçalho WWW-Authenticate
significa que há autenticação HTTP, e -u
é a ferramenta correta. Um 200
que devolve uma página de início de sessão, ou um 302
que redireciona para /login
, significa que o site utiliza um formulário e um cookie de sessão — e por mais que se utilize -u
, não servirá de nada, porque nada está a ler esse cabeçalho.
Em vez disso, o padrão de login por formulário. Envie as credenciais para o ponto final de login, guarde os cookies e reutilize-os:
curl -c jar.txt -d "username=ada&password=secret" \
https://example.com/login
curl -b jar.txt https://example.com/dashboard
-c
grava um cookie e -b
lê um. Utilize ambos em pedidos subsequentes (-b jar.txt -c jar.txt
) se o servidor alternar o cookie de sessão, o que muitos fazem.
A complicação com que se vai deparar: tokens CSRF. A maioria dos formulários de início de sessão inclui um token oculto que deve ser enviado juntamente com as credenciais e que é gerado por sessão. Isso significa uma sequência de dois passos — obter o formulário, extrair o token e enviá-lo juntamente com os cookies do primeiro passo:
TOKEN=$(curl -sS -c jar.txt https://example.com/login \
| grep -o 'name="csrf_token" value="[^"]*"' \
| cut -d'"' -f4)
curl -b jar.txt -c jar.txt \
-d "csrf_token=${TOKEN}" -d "username=ada" -d "password=secret" \
https://example.com/login
A técnica de «grep-and-cut» em HTML é frágil e serve apenas para um diagnóstico pontual. Para qualquer tarefa contínua, verifique primeiro se o serviço oferece uma API com autenticação por token — quase sempre oferece, e será significativamente menos trabalhoso do que manter um scraper do seu próprio formulário de início de sessão.
E se o login exigir JavaScript, o curl não consegue fazê-lo de todo. Isso não é uma limitação do curl que se possa contornar; é um sinal para procurar o ponto final da API que a própria página está a chamar, que pode encontrar no separador «Rede» do seu navegador e reproduzir diretamente.
Perguntas frequentes
Como utilizo a autenticação básica com o curl?
curl -u username:password URL. A autenticação básica é o esquema predefinido do curl, pelo que --basic é redundante, a menos que se esteja a substituir um método definido anteriormente. Utilize sempre HTTPS, uma vez que a autenticação básica codifica as credenciais em base64 em vez de as encriptar.
Como faço para que o curl solicite uma palavra-passe?
Indique apenas o nome de utilizador: curl -u username URL. O curl solicita a palavra-passe de forma interativa e não a exibe, pelo que nada fica registado no histórico do shell nem nas listas de processos. Esta é a abordagem correta para qualquer coisa digitada manualmente.
A autenticação básica do curl é segura?
Apenas através de HTTPS. As credenciais são codificadas em base64, o que é facilmente reversível; por isso, através de HTTP simples, ficam efetivamente em texto simples. Através de TLS, o transporte protege-as, e o risco remanescente reside no local onde as armazena na sua própria máquina.
Como posso armazenar as credenciais do curl num ficheiro?
Utilize ~/.netrc com as linhas machine, login e password; em seguida, crie um ficheiro com o comando chmod 600 e chame o curl com -n. O curl não avisa sobre permissões incorretas, pelo que cabe a si defini-las. --netrc-file aponta para uma localização alternativa, e --netrc-optional evita falhas quando não existe nenhum ficheiro.
Por que razão o curl devolve um erro 401 quando a minha palavra-passe está correta?
Existem várias possibilidades: o servidor espera um esquema diferente — verifique o cabeçalho WWW-Authenticate —, ou o ponto final utiliza o login por formulário com cookies em vez da autenticação HTTP, ou o seu nome de utilizador contém dois pontos, o que -u não consegue expressar porque divide na primeira ocorrência.
Qual é a diferença entre 401 e 403?
401 significa que não está autenticado: não tem credenciais ou estas estão erradas. 403 significa que se autenticou com sucesso, mas não tem permissão para realizar esta ação. Tentar novamente com credenciais diferentes resolve o primeiro caso, mas não o segundo.
Como me autentico num proxy com o curl?
--proxy-user user:password, que é diferente de -u para o destino. Um código de estado 407 significa que o proxy requer credenciais; um 401 significa que é o destino que as requer. Se o seu endereço de origem for estável, pergunte ao seu fornecedor sobre a inclusão na lista de IPs autorizados — isto elimina completamente as credenciais dos seus comandos.
O que faz a opção --anyauth?
Faz com que o curl detete o esquema preferido do servidor, enviando um pedido e lendo os cabeçalhos da resposta, para depois se autenticar com o método mais seguro disponibilizado. O custo é uma ida e volta adicional por pedido, e o manual alerta que pode falhar ao carregar a partir do stdin, uma vez que os dados podem ter de ser enviados duas vezes.
Conclusão
A sintaxe é rápida de entender: -u username:password e já está autenticado, com «basic» como esquema predefinido do curl. A parte que vale a pena ter em atenção é onde a palavra-passe está armazenada.
O próprio manual do curl é claro a este respeito — as credenciais «devem ser obtidas a partir de um ficheiro ou similar e nunca utilizadas em texto simples numa linha de comando» — e os riscos de exposição sobre os quais alerta são todos reais. O histórico do shell guarda-as indefinidamente, as listagens de processos expõem-nas brevemente a qualquer pessoa na máquina e a saída do terminal colada tem uma capacidade assustadora de chegar a locais onde não pretendia.
Dois hábitos resolvem o problema. Para utilização interativa, forneça apenas o nome de utilizador e deixe que o curl solicite a palavra-passe. Para scripts, coloque as credenciais num ficheiro «~/.netrc» com «chmod 600» e utilize «-n». Nenhuma das opções demora mais tempo do que digitar a palavra-passe.
E mantenha as duas camadas de autenticação bem separadas. Um código de erro 401 provém do destino e requer -u; um código de erro 407 provém do proxy e requer --proxy-user. Se o seu endereço for estável, uma lista de permissões elimina completamente a segunda credencial dos seus comandos — que é o único local verdadeiramente seguro para guardar um segredo.
