Configuração ACME#
O módulo ACME no Angie permite a aquisição automática de
certificados usando o protocolo ACME. O protocolo ACME suporta
vários métodos de verificação de domínio (também chamados de "validação"); este
módulo implementa validação HTTP, validação DNS, validação ALPN e
validação baseada em hooks
através de um serviço externo personalizado. Etapas gerais para habilitar solicitações de certificado na configuração: Configure um cliente ACME no bloco Especifique os domínios para os quais os certificados são solicitados: Um
único certificado será emitido para todos os nomes de domínio listados nas
diretivas server_name dentro de todos os blocos Endereços IP (IPv4 ou IPv6) especificados em server_name também são
identificadores de certificado válidos, exceto com validação DNS. Observe que autoridades certificadoras públicas podem emitir certificados
para endereços IP apenas sob perfis de certificado específicos; por
exemplo, a Let's Encrypt atualmente exige um perfil que permita
identificadores IP (consulte sua documentação de perfis). Para solicitar um, defina o
parâmetro Configure o tratamento de solicitações e callbacks ACME: Isso é necessário
para verificar a propriedade do domínio. A configuração depende do método de
validação de domínio escolhido: Método Requisitos do Usuário Multi-domínio Domínios Wildcard Abrir a porta 80 (ou a especificada em acme_http_port)
para conexões de entrada no servidor Angie. ✔ Abrir a porta 53 (ou a especificada em acme_dns_port)
para conexões de entrada no servidor Angie. Definir um registro NS para o subdomínio ✔ ✔ Abrir a porta 443 (ou a porta TLS usada pelo seu servidor)
para conexões de entrada no servidor Angie. ✔ Criar um serviço externo (script ou aplicação)
que possa, mediante solicitação do Angie, atualizar registros DNS
ou servir uma resposta especial via servidor web. ✔ ✔ Configure SSL usando o certificado e chave obtidos: O módulo torna
certificados e chaves disponíveis como variáveis incorporadas
que podem ser usadas na configuração para preencher
ssl_certificate e ssl_certificate_key. Para instruções de configuração SSL, consulte Configuração SSL. Dica O processo de aquisição e renovação de certificados depende de muitos serviços
e pode levar algum tempo. Seja paciente, e se encontrar problemas ou tiver
dúvidas, verifique o log de depuração. Chaves de cliente e certificados são armazenados em codificação PEM dentro de subdiretórios do
diretório especificado pela Nota Esses arquivos persistem no disco entre reinicializações. Na inicialização, o
cliente reutiliza um certificado armazenado que ainda é válido em vez de
solicitar um novo, evitando o atraso de emissão e requisições desnecessárias
à CA, que podem estar sujeitas a limites de taxa. Em um contêiner, coloque o
diretório de armazenamento (por padrão Nota Recarregar a configuração ( O cliente ACME requer uma conta no servidor CA. Para criar e gerenciar esta
conta, o cliente usa uma chave privada ( Nota Se você já possui uma chave de conta, coloque-a no subdiretório do cliente
antes de iniciar para reutilizar a conta. Alternativamente, especifique o
arquivo de chave usando o parâmetro Algumas CAs exigem que a conta ACME seja vinculada a uma conta externa
existente por meio de External Account Binding (EAB) antes
de emitir certificados. Para habilitar isso, configure o parâmetro O cliente ACME também usa uma chave separada ( Na inicialização, o cliente solicita um certificado se um não existir, assinando
e enviando um CSR para todos os domínios sob seu gerenciamento para o servidor
CA. O servidor verifica a propriedade do domínio usando validação HTTP ou validação DNS e emite um
certificado, que o cliente salva localmente ( Como mencionado anteriormente, um único certificado cobre todos os nomes de
domínio gerenciados pelo mesmo cliente ACME, potencialmente resultando em um
certificado multi-domínio. A lista de todos os nomes cobertos pelo certificado
pode ser encontrada na seção Subject Alternative Name (SAN) do certificado
obtido. Para verificar isso pela linha de comando: Quando um certificado está prestes a expirar ou a lista de domínios muda, o
cliente assina e envia outro CSR para o servidor CA. O servidor re-verifica a
propriedade e emite um novo certificado, que o cliente instala localmente,
substituindo o anterior. Na configuração, o certificado obtido e sua chave
correspondente estão disponíveis através das variáveis de prefixo
$acme_cert_<name> e $acme_cert_key_<name>. Seus valores são o
conteúdo dos respectivos arquivos, que devem ser usados com as diretivas
ssl_certificate e ssl_certificate_key: Nota O cliente ACME resolve o nome do host do servidor CA por meio do
resolver do mesmo contexto, que por padrão consulta os servidores
DNS listados em A diretiva acme serve apenas para coletar nomes de domínio
para solicitações de certificado.
Ela não controla onde o certificado pode ser usado:
qualquer bloco Por exemplo, se você tem um bloco Para controlar o conjunto exato de nomes de domínio em um certificado
sem depender da coleta automática a partir de todos os blocos Outros blocos Cada acme_client gerencia um único certificado. Para obter vários
certificados independentes (por exemplo, para domínios não relacionados que não
devem compartilhar um único certificado), configure um acme_client
separado para cada um no bloco A validação é automática. Quando o Angie solicita um certificado, o servidor
ACME requisita um arquivo de token via HTTP ao Angie no
endereço Neste exemplo, o cliente ACME chamado Como observado anteriormente, a porta 80 deve estar aberta para tratar chamadas
HTTP ACME. Se nenhum servidor estiver configurado para escutar na porta de
desafio HTTP, o módulo cria um listener dedicado na porta 80 (ou na definida em
acme_http_port). Um bloco server separado não é necessário. A validação é automática, mas exige registros DNS do seu lado. Quando o Angie
solicita um certificado, o servidor ACME envia uma consulta DNS por um registro
TXT sob o subdomínio Para que a consulta chegue ao Angie, seu DNS deve delegar o subdomínio
A validação DNS comprova o controle de um nome de domínio, portanto não pode
validar identificadores de endereço IP: o Angie ignora um endereço IP em
server_name para um cliente configurado com Nota O servidor Angie deve estar acessível pela internet
na porta UDP 53 (ou na especificada em acme_dns_port).
Se o servidor estiver atrás de um firewall,
certifique-se de que esta porta esteja aberta para conexões de entrada. Por exemplo, para verificar o domínio Esta configuração delega a resolução DNS para
Aviso A propagação de registros NS pode levar de alguns minutos a 48 horas
dependendo do TTL e do provedor DNS. É recomendado verificar se a
configuração está correta antes de solicitar um certificado. Para verificar se o DNS está configurado corretamente, você pode usar os seguintes comandos: Este método permite solicitar certificados wildcard, por exemplo, um certificado
que inclui a entrada Aviso A aplicabilidade deste cenário depende amplamente das capacidades
fornecidas pelo seu provedor DNS; alguns provedores não permitem tais
configurações. No geral, a configuração é similar ao exemplo da seção anterior.
Não há necessidade de configurações específicas do HTTP; em vez disso, é suficiente definir
Neste exemplo, o cliente ACME chamado A validação é tratada automaticamente. O servidor ACME se conecta usando TLS e
solicita o protocolo Para habilitar este método, configure A configuração é similar às seções anteriores; basta definir Neste exemplo, o cliente ACME chamado Diferentemente dos métodos anteriores, esta validação requer esforço adicional. O
servidor ACME executa validação HTTP padrão ou
validação DNS, mas em vez de interagir diretamente
com o servidor Angie, ele se comunica com um serviço externo gerenciado pelo
servidor Angie usando chamadas de hook (acme_hook). Este serviço configura um
servidor DNS ou HTTP separado onde o servidor ACME envia suas solicitações. Uma vez que o servidor ACME recebe a resposta esperada do servidor DNS ou
HTTP configurado, ele confirma a propriedade do domínio. Quando a emissão ou renovação de certificado requer verificação de domínio,
o Angie gera uma solicitação interna
para o O padrão geral é: Crie um Configure um manipulador de solicitações no mesmo Passe as variáveis ACME
para o manipulador usando o mecanismo que ele suporta,
por exemplo O manipulador deve retornar um código de status Independentemente do manipulador utilizado,
o Para validação DNS, o manipulador deve usar Neste exemplo, o cliente ACME O bloco Um bloco O seguinte script Perl demonstra um serviço FastCGI externo correspondente: Aqui, Outro exemplo, usando PHP-FPM: Parâmetros passados podem ser acessados em PHP via Aqui o hook é um script executado pelo módulo CGI; ele
adiciona e remove o registro TXT O módulo é carregado no contexto O octoDNS constrói uma zona a partir de suas origens, em ordem, e as mescla;
por isso o hook mantém o registro do desafio em um diretório de origem
próprio, O arquivo de zona contém os registros que você mesmo mantém: Para verificar manualmente a configuração do octoDNS, execute
octodns-sync sem O script lê as variáveis ACME do seu ambiente e grava o registro do desafio
na origem Assim que o hook Se o valor não aparecer a tempo, o script responde com O limite de espera vem da variável de ambiente Defini-lo como A variável $acme_hook_domain chega sem o prefixo O script é executado como o usuário dos processos worker do Angie,
A saída de erro padrão do script vai para o log de erro
do Angie no nível Para colocar tudo isso em prática em um sistema baseado em Debian: Instale o módulo CGI: Instale o octoDNS e o provedor Cloudflare
em um ambiente virtual próprio: Crie os diretórios; o pacote do módulo
não cria Crie Salve o script mostrado acima como Adicione a configuração mostrada acima, depois teste-a e
recarregue o Angie: O cliente solicita o certificado imediatamente após a recarga; seu
estado e o status do certificado aparecem na seção da API
/status/http/acme_clients/, e o certificado emitido é salvo no
diretório de armazenamento do cliente. O módulo stream ACME habilita emissão automatizada
de certificados e uso para tráfego TCP.
Para que funcione corretamente, você deve primeiro configurar sua contraparte HTTP:
o cliente ACME deve ser declarado no contexto Por padrão, o modo de validação HTTP é usado para obter certificados.
Como mencionado na seção Validação HTTP,
isso requer um servidor HTTP escutando na porta 80: Você também pode usar validação DNS
configurando Se você usou anteriormente o certbot
para obter e renovar certificados SSL do Let's Encrypt
antes de migrar do nginx para o Angie,
siga estes passos para fazer a transição para usar nosso módulo ACME. Suponha que você configurou os certificados da seguinte forma: A configuração criada automaticamente por este comando
está tipicamente localizada em No exemplo acima, as linhas destacadas precisam ser modificadas.
Dependendo das suas circunstâncias e preferências, configure
a validação HTTP
ou a validação DNS usando o módulo ACME. A configuração do Angie resultante
pode se parecer com isto: Lembre-se de recarregar a configuração
após fazer as alterações: Uma vez que você tenha verificado que esta configuração funciona,
você pode excluir os certificados do certbot
e desabilitar ou remover o certbot inteiramente do servidor
se ele não for mais usado em outro lugar,
por exemplo:Etapas de configuração#
http usando a diretiva
acme_client, que especifica um nome único de cliente e outros
parâmetros. Múltiplos clientes ACME podem ser configurados.server que
usam diretivas acme apontando para o mesmo cliente ACME.profile= de acme_client._acme-challenge.
apontando para seu servidor Angie.Detalhes de implementação#
--http-acme-client-path opção de compilação:$ ls /var/lib/angie/acme/example/
account.key certificate.pem private.key
/var/lib/angie/acme/) em um
volume persistente para que os certificados emitidos sobrevivam à recriação do
contêiner; consulte executando o Angie em um contêiner.angie -s reload) reavalia cada cliente
ACME em relação aos certificados armazenados em disco. Um cliente cujo
certificado não é válido no momento -- incluindo um cuja solicitação anterior
terminou em erro -- é solicitado novamente de imediato,
independentemente de retry_after_error. O atraso de
repetição (e retry_after_error=off) aplica-se apenas enquanto o Angie
continua em execução; não sobrevive a uma recarga. Portanto, recarregar
repetidamente enquanto uma solicitação continua falhando pode enviar
solicitações frequentes à CA, sujeitas aos limites de taxa mencionados acima.account.key). Se nenhuma chave
existir, ela é gerada na inicialização. O cliente então usa esta chave para
registrar a conta no servidor.account_key em acme_client.eab
de acme_client com o identificador de chave e a chave MAC fornecidos
pela CA; consulte a descrição da diretiva para a sintaxe exata.private.key) para
Solicitações de Assinatura de Certificado (CSRs). Esta chave de certificado é
automaticamente criada na inicialização se necessário.certificate.pem).$ openssl x509 -in certificate.pem -noout -text | grep -A5 "Subject Alternative Name"
server {
listen 443 ssl;
server_name example.com www.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
/etc/resolv.conf; configure a diretiva
explicitamente apenas para usar outros servidores ou parâmetros. Em um
host sem conectividade IPv6, o resolver ainda pode emitir consultas AAAA
(IPv6) para a CA e falhar ao conectar; desabilite-as com o parâmetro
ipv6=off, por exemplo resolver conf ipv6=off;.Coleta de domínios versus uso do certificado#
server pode referenciar o certificado obtido
através da variável $acme_cert_<name>,
independentemente de o bloco conter ou não uma diretiva acme.server com curinga
que já cobre todos os subdomínios,
blocos server adicionais para subdomínios específicos
não precisam da diretiva acme:http {
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
# This block lists the domains for the certificate request
server {
listen 443 ssl;
server_name example.com *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
# This block uses the same certificate but does not
# add its server_name to the certificate request
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
Lista explícita de domínios#
server,
crie um bloco server dedicado
que contenha apenas as diretivas server_name e acme.
Para evitar que esse bloco trate tráfego real,
vincule-o a um socket de domínio Unix:# Dedicated block that defines the certificate's domain list
server {
listen unix:/tmp/acme_example.sock;
server_name example.com www.example.com;
acme example;
}
server podem então usar o certificado
através da variável $acme_cert_<name>
sem afetar quais domínios são solicitados.Certificados separados para domínios diferentes#
http e aponte a diretiva acme de
cada bloco server para o cliente correspondente pelo nome:http {
# Dois clientes independentes, cada um gerenciando seu próprio certificado
acme_client shop https://acme-v02.api.letsencrypt.org/directory
challenge=http;
acme_client blog https://acme-v02.api.letsencrypt.org/directory
challenge=http;
server {
listen 443 ssl;
server_name shop.example.com www.shop.example.com;
acme shop;
ssl_certificate $acme_cert_shop;
ssl_certificate_key $acme_cert_key_shop;
}
server {
listen 443 ssl;
server_name blog.example.com;
acme blog;
ssl_certificate $acme_cert_blog;
ssl_certificate_key $acme_cert_key_blog;
}
}
Validação HTTP#
/.well-known/acme-challenge/<TOKEN>. O próprio Angie responde
a essas solicitações, então você não precisa servir esse caminho. Em seguida,
o servidor ACME compara o token recebido com o que emitiu e, se coincidirem,
aceita o domínio como seu.Exemplo de configuração#
example gerencia certificados para
example.com e www.example.com (note que certificados wildcard
não são suportados com validação HTTP):http {
acme_client example https://acme-v02.api.letsencrypt.org/directory;
server {
listen 80; # Não é necessário: se nada mais escutar na porta
# de desafio HTTP, o módulo a abre por conta própria
# (veja a diretiva 'acme_http_port')
listen 443 ssl;
server_name example.com www.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
Validação DNS#
_acme-challenge. do domínio sendo validado. O
Angie responde a essas consultas por conta própria: ele atua como o servidor
de nomes autoritativo para esse subdomínio e retorna o registro TXT que o
servidor ACME espera. O servidor ACME compara o registro recebido com o valor
emitido e, se coincidirem, aceita o domínio como seu._acme-challenge. ao servidor Angie, conforme mostrado abaixo. Por
padrão, o Angie responde com um TTL muito baixo; alguns provedores de DNS
filtram respostas com um TTL tão baixo, portanto aumente-o com
acme_dns_ttl se a validação nunca for concluída.challenge=dns,
registrando um aviso na inicialização. Um bloco server que lista um
endereço IP ainda deve nomear pelo menos um domínio que o cliente possa
validar.example.com usando um servidor Angie no
endereço IP 203.0.113.10, a configuração DNS do seu domínio deve incluir
os seguintes registros:_acme-challenge.example.com. 60 IN NS ns.example.com.
ns.example.com. 60 IN A 203.0.113.10
_acme-challenge.example.com para ns.example.com, garantindo que
ns.example.com seja acessível mapeando-o para o endereço IP
(203.0.113.10).$ dig NS _acme-challenge.example.com +short # Verifica o registro NS para o subdomínio _acme-challenge
ns.example.com.
$ dig A ns.example.com +short # Verifica o registro A para o servidor de nomes
203.0.113.10
$ nc -zv 203.0.113.10 53 # Verifica a acessibilidade do servidor DNS na porta 53
*.example.com na seção Subject Alternative Name
(SAN). Para solicitar explicitamente um certificado para um subdomínio, como
www.example.com, você deve verificar separadamente esse subdomínio usando o
método descrito acima.Exemplo de configuração#
challenge=dns para a diretiva acme_client.example gerencia certificados para
example.com e *.example.com:http {
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
server {
server_name example.com *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
Validação ALPN#
acme-tls/1 via ALPN. O módulo serve um certificado
temporário para a solicitação de validação.challenge=alpn na
diretiva acme_client e garanta que seu listener TLS esteja acessível na
porta 443 (ou a porta usada para TLS).Exemplo de configuração#
challenge=alpn
para a diretiva acme_client e garantir que o servidor TLS esteja acessível
na porta 443.example gerencia um certificado para
example.com e www.example.com:http {
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=alpn;
server {
listen 443 ssl;
server_name example.com www.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
Validação baseada em hooks#
location nomeado que contém a diretiva acme_hook.
Como essa solicitação é tratada depende inteiramente das outras diretivas
configuradas no mesmo location.location nomeado com a diretiva acme_hook.location
usando o módulo que melhor se adequar à sua configuração:
fastcgi_pass para FastCGI,
proxy_pass para HTTP,
cgi_pass para scripts CGI, etc.fastcgi_param para FastCGI
ou cgi_set_var para CGI.2xx,
que pode ser enviado via o cabeçalho Status.
Qualquer outro código do hook add interrompe a tentativa de
renovação; no caso do hook remove, o mesmo código é apenas
registrado como aviso, e a renovação continua.
A saída do manipulador é ignorada.Configuração mínima#
location do hook segue esta estrutura:location @acme_hook_location {
acme_hook example;
# Handler directive (fastcgi_pass, proxy_pass, cgi_pass, ...)
# Pass ACME variables using the handler's mechanism:
# ACME_HOOK — $acme_hook_name ("add" or "remove")
# ACME_CHALLENGE — $acme_hook_challenge ("dns" or "http")
# ACME_DOMAIN — $acme_hook_domain
# ACME_TOKEN — $acme_hook_token
# ACME_KEYAUTH — $acme_hook_keyauth
}
ACME_HOOK
para determinar a ação:
quando ele for add,
crie um registro TXT para _acme-challenge.ACME_DOMAIN
com o valor de ACME_KEYAUTH;
quando for remove, exclua esse registro.Exemplo com FastCGI#
example está configurado para
verificação de domínio usando callbacks DNS, indicado pelo parâmetro challenge=dns
na diretiva acme_client.server se aplica a todos os subdomínios de example.com (por exemplo,
*.example.com) e usa o cliente ACME example para gerenciar
certificados, conforme especificado pela diretiva acme.location nomeado trata as chamadas de hook.
A diretiva acme_hook o associa
ao cliente ACME example.
As solicitações de hook são enviadas para um servidor FastCGI local
na porta 9000 usando fastcgi_pass.
As diretivas fastcgi_param passam
as variáveis ACME para o serviço externo.acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
server {
listen 80;
server_name *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
location @acme_hook_location {
acme_hook example;
fastcgi_pass localhost:9000;
fastcgi_param ACME_CLIENT $acme_hook_client;
fastcgi_param ACME_HOOK $acme_hook_name;
fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
fastcgi_param ACME_DOMAIN $acme_hook_domain;
fastcgi_param ACME_TOKEN $acme_hook_token;
fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;
include fastcgi.conf;
}
}
#!/usr/bin/perl
use strict; use warnings;
use FCGI;
my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
while ($request->Accept() >= 0) {
print "\r\n";
my $client = $ENV{ACME_CLIENT};
my $hook = $ENV{ACME_HOOK};
my $challenge = $ENV{ACME_CHALLENGE};
my $domain = $ENV{ACME_DOMAIN};
my $token = $ENV{ACME_TOKEN};
my $keyauth = $ENV{ACME_KEYAUTH};
if ($hook eq 'add') {
DNS_set_TXT_record("_acme-challenge.$domain.", $keyauth);
} elsif ($hook eq 'remove') {
DNS_clear_TXT_record("_acme-challenge.$domain.");
}
};
FCGI::CloseSocket($socket);
DNS_set_TXT_record() e DNS_clear_TXT_record() são
funções assumidas para adicionar e remover registros TXT na configuração de um
servidor DNS externo que o servidor ACME consulta. Estes registros devem conter os
dados fornecidos pelo servidor Angie para permitir que o servidor DNS externo
passe com sucesso na validação, similar ao processo descrito em
Validação DNS. Os detalhes de implementação de tais funções estão além
do escopo deste guia; por exemplo, parâmetros também podem ser passados através da
URI da solicitação:# ...
location @acme_hook_location {
acme_hook example uri=/acme_hook/$acme_hook_name?domain=$acme_hook_domain&key=$acme_hook_keyauth;
fastcgi_pass localhost:9000;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param ACME_CLIENT $acme_hook_client;
fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
fastcgi_param ACME_TOKEN $acme_hook_token;
include fastcgi.conf;
}
Exemplo com PHP-FPM#
location @acme_hook_location {
acme_hook example;
root /var/www/dns;
fastcgi_pass unix:/run/php-fpm/php-dns.sock;
fastcgi_index hook.php;
fastcgi_param SCRIPT_FILENAME /var/www/dns/hook.php;
include fastcgi_params;
fastcgi_param ACME_CLIENT $acme_hook_client;
fastcgi_param ACME_HOOK $acme_hook_name;
fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
fastcgi_param ACME_DOMAIN $acme_hook_domain;
fastcgi_param ACME_TOKEN $acme_hook_token;
fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;
}
[dns]
listen = /run/php-fpm/php-dns.sock
listen.mode = 0666
user = angie
group = angie
chdir = /var/www/dns
# ...
$_SERVER['...'].Exemplo com CGI e octoDNS#
_acme-challenge por meio do octoDNS, que mantém o DNS como código: uma
zona é descrita em YAML e sincronizada com um provedor DNS. O Cloudflare é
usado aqui como exemplo; qualquer outro provedor octoDNS funciona da mesma
forma, uma vez declarado em providers e nomeado em targets. O
token da API precisa das permissões Zone:Read, DNS:Read e
DNS:Edit para a zona.main. O cliente ACME example
solicita um único certificado para example.com e
*.example.com, verificado por meio de callbacks DNS, conforme
indicado pelo parâmetro challenge=dns na diretiva acme_client.
Um bloco location nomeado trata as chamadas de hook: a diretiva
acme_hook o associa ao cliente, as diretivas cgi_set_var
passam as variáveis ACME sob os nomes padrão, e
cgi_pass executa o script para cada chamada. Dentre esses nomes, este
script usa ACME_CHALLENGE, ACME_HOOK, ACME_DOMAIN e
ACME_KEYAUTH.# in the main context
load_module modules/ngx_http_cgi_module.so;
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
server {
listen 443 ssl;
server_name example.com *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
location @acme_hook_location {
acme_hook example;
cgi_set_var ACME_CLIENT $acme_hook_client;
cgi_set_var ACME_HOOK $acme_hook_name;
cgi_set_var ACME_CHALLENGE $acme_hook_challenge;
cgi_set_var ACME_DOMAIN $acme_hook_domain;
cgi_set_var ACME_TOKEN $acme_hook_token;
cgi_set_var ACME_KEYAUTH $acme_hook_keyauth;
cgi_pass /usr/share/angie/cgi-bin/acme-octodns;
}
}
/etc/octodns/acme, e as renovações de certificado nunca
reescrevem o arquivo de zona em /etc/octodns/zones. A opção
ignore_missing_zones (octoDNS 1.16 e posteriores) permite que
/etc/octodns/acme permaneça vazio entre renovações. O próprio
arquivo de zona não deve definir registros _acme-challenge.providers:
config:
class: octodns.provider.yaml.YamlProvider
directory: /etc/octodns/zones
acme:
class: octodns.provider.yaml.YamlProvider
directory: /etc/octodns/acme
ignore_missing_zones: true
cloudflare:
class: octodns_cloudflare.CloudflareProvider
token: YOUR_API_TOKEN
zones:
example.com.:
sources:
- config
- acme
targets:
- cloudflare
'':
type: A
value: 203.0.113.10
www:
type: A
value: 203.0.113.10
--doit.acme:O script acme-octodns
#!/opt/octodns/bin/python3
# DNS-01 hook for the Angie ACME client: keeps the _acme-challenge TXT record
# in a separate octodns source directory and publishes it with octodns-sync.
import fcntl
import os
import subprocess
import sys
import time
import dns.message
import dns.query
import dns.resolver
import yaml
CONFIG = "/etc/octodns/octodns.yaml"
ACME_DIR = "/etc/octodns/acme"
SYNC = "/opt/octodns/bin/octodns-sync"
TTL = 120
WAIT = int(os.environ.get("ACME_WAIT", "300"))
def respond(code, message=""):
if message:
print(message, file=sys.stderr)
sys.stdout.write(f"Status: {code}\r\n\r\n")
sys.exit(0)
def find_zone(domain):
with open(CONFIG) as f:
zones = [z.rstrip(".") for z in yaml.safe_load(f)["zones"]]
match = [z for z in zones if domain == z or domain.endswith("." + z)]
if not match:
respond(500, f"no zone for {domain} in {CONFIG}")
return max(match, key=len)
def update(zone, name, value, add):
# One record may hold several values: a certificate for example.com and
# *.example.com is validated with two TXT values on the same name.
path = f"{ACME_DIR}/{zone}.yaml"
with open(f"{ACME_DIR}/.lock", "w") as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
data = {}
if os.path.exists(path):
with open(path) as f:
data = yaml.safe_load(f) or {}
values = set(data.get(name, {}).get("values", []))
(values.add if add else values.discard)(value)
if values:
data[name] = {"type": "TXT", "ttl": TTL, "values": sorted(values)}
else:
data.pop(name, None)
if data:
with open(path, "w") as f:
yaml.safe_dump(data, f)
elif os.path.exists(path):
os.remove(path)
cmd = [SYNC, f"--config-file={CONFIG}", "--doit", zone + "."]
run = subprocess.run(cmd, capture_output=True, text=True)
if run.returncode != 0:
respond(500, "octodns-sync failed:\n" + run.stdout + run.stderr)
def has_txt(server, fqdn, value):
try:
reply = dns.query.udp(dns.message.make_query(fqdn, "TXT"), server, timeout=3)
except Exception:
return False
return any(value == b"".join(getattr(r, "strings", ())).decode()
for rrset in reply.answer for r in rrset)
def wait_visible(zone, fqdn, value):
# The ACME server asks the zone's authoritative servers, so poll them
# directly: a resolver may have cached the record's absence.
deadline = time.monotonic() + WAIT
while time.monotonic() < deadline:
try:
servers = [str(a) for ns in dns.resolver.resolve(zone, "NS")
for a in dns.resolver.resolve(str(ns.target), "A")]
if servers and all(has_txt(s, fqdn, value) for s in servers):
return True
except Exception:
pass
time.sleep(5)
return False
def main():
hook = os.environ.get("ACME_HOOK")
domain = os.environ.get("ACME_DOMAIN", "")
value = os.environ.get("ACME_KEYAUTH", "")
if os.environ.get("ACME_CHALLENGE") != "dns" or hook not in ("add", "remove"):
respond(500, "the hook supports only DNS validation")
zone = find_zone(domain)
rel = domain[: -len(zone) - 1]
name = "_acme-challenge" + (f".{rel}" if rel else "")
update(zone, name, value, hook == "add")
if hook == "add" and WAIT and not wait_visible(zone, f"{name}.{zone}.", value):
update(zone, name, value, False)
respond(500, f"{name}.{zone} is not visible on the name servers after {WAIT}s")
respond(200)
try:
main()
except Exception as e:
respond(500, f"{type(e).__name__}: {e}")
add retorna um código 2xx, o Angie solicita
ao servidor ACME que valide o desafio e verifica repetidamente o resultado
durante 60 segundos. O Angie nunca espera a propagação do registro; é o
próprio script que faz isso. Ele consulta diretamente os servidores de nomes
autoritativos da zona, já que um resolver pode ter armazenado em cache a
ausência do registro, e retorna assim que cada um deles estiver servindo o
valor, aguardando até 300 segundos por padrão.Status: 500,
e o Angie interrompe a tentativa e a repete após o intervalo definido pelo
parâmetro retry_after_error. O Angie não chama o hook
remove após uma falha do add, então o script exclui seu
próprio valor antes de responder.ACME_WAIT. O
location do hook acima não a define; para alterar o limite,
adicione-a ali, passando o valor por meio de uma variável como as demais:set $acme_wait 600;
cgi_set_var ACME_WAIT $acme_wait;
0 desativa a espera, o que é adequado para um
servidor autoritativo auto-hospedado, onde uma gravação via API fica visível
imediatamente.*., então
um certificado para example.com e *.example.com produz duas
chamadas de add para o mesmo nome
_acme-challenge.example.com. O script mantém uma lista de valores em
um único registro e, em remove, remove apenas o valor daquela
chamada; quando não sobra nenhum valor, ele exclui o registro e o arquivo que
o continha em /etc/octodns/acme.angie. Ele precisa de acesso de leitura à configuração do octoDNS,
que contém o token, e de acesso de gravação a /etc/octodns/acme;
nada além disso. O processo CGI não herda o ambiente do Angie, por isso o
token fica no arquivo de configuração e o script usa caminhos absolutos.warn, onde as falhas do hook são diagnosticadas; essas
mensagens aparecem apenas quando o nível de log é warn ou menos severo.$ sudo apt-get install angie-module-cgi
$ sudo apt-get install python3-venv
$ sudo python3 -m venv /opt/octodns
$ sudo /opt/octodns/bin/pip install 'octodns>=1.16' octodns-cloudflare
/usr/share/angie/cgi-bin:$ sudo install -d /etc/octodns/zones /usr/share/angie/cgi-bin
$ sudo install -d -o angie -g angie -m 750 /etc/octodns/acme
/etc/octodns/octodns.yaml e
/etc/octodns/zones/example.com.yaml como mostrado acima, depois
restrinja a configuração ao grupo angie, já que ela contém o
token da API:$ sudo chgrp angie /etc/octodns/octodns.yaml
$ sudo chmod 640 /etc/octodns/octodns.yaml
acme-octodns, depois instale-o:$ sudo install -m 755 acme-octodns /usr/share/angie/cgi-bin/
$ sudo angie -t
$ sudo kill -HUP $(cat /run/angie.pid)
ACME no módulo stream#
http,
e o próprio bloco stream deve ser colocado após o bloco http
na configuração.Exemplo de configuração#
# Parte HTTP
http {
# Cliente ACME para a parte stream
acme_client example https://acme-v02.api.letsencrypt.org/directory;
# Servidor para validação HTTP
server {
listen 80;
return 444;
}
}
# Parte Stream
stream {
server {
listen 12345 ssl;
proxy_pass backend_upstream;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
server_name example.com www.example.com;
acme example; # referência ao cliente ACME definido na parte HTTP
}
upstream backend_upstream {
server 127.0.0.1:54321;
}
}
challenge=dns na diretiva acme_client;
nesse caso, o servidor não será necessário.Migrando do certbot#
$ sudo certbot --nginx -d example.com -d www.example.com
/etc/nginx/sites-available/example.conf
e se parece com algo assim:server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
http {
acme_client example https://acme-v02.api.letsencrypt.org/directory;
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
$ sudo kill -HUP $(cat /run/angie.pid)
$ sudo rm -rf /etc/letsencrypt
$ sudo systemctl stop certbot.timer
$ sudo systemctl disable certbot.timer
$ # -- ou --
$ sudo rm /etc/cron.d/certbot
$ sudo apt remove certbot
$ # -- ou --
$ sudo dnf remove certbot