mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
Explain what is needed for the acme configuration and other small changes
1. Add a request handler in ejabberd_http and explain how to configure the http listener so that the challenges can be solved. 2. Make acme configuration optional by providing defaults in ejabberd_acme. 3. Save the CA that the account has been created in so that it creates a new account when connecting to a new CA. 4. Small spec change in acme configuration.
This commit is contained in:
parent
78f494dd2e
commit
ce99db0595
@ -161,7 +161,6 @@ listen:
|
|||||||
"/ws": ejabberd_http_ws
|
"/ws": ejabberd_http_ws
|
||||||
"/bosh": mod_bosh
|
"/bosh": mod_bosh
|
||||||
"/api": mod_http_api
|
"/api": mod_http_api
|
||||||
"/.well-known": acme_challenge
|
|
||||||
## "/pub/archive": mod_http_fileserver
|
## "/pub/archive": mod_http_fileserver
|
||||||
web_admin: true
|
web_admin: true
|
||||||
## register: true
|
## register: true
|
||||||
@ -662,6 +661,17 @@ language: "en"
|
|||||||
|
|
||||||
###. ====
|
###. ====
|
||||||
###' ACME
|
###' ACME
|
||||||
|
##
|
||||||
|
## In order to use the acme certificate acquiring through "Let's Encrypt"
|
||||||
|
## an http listener has to be configured to listen to port 80 so that
|
||||||
|
## the authorization challenges posed by "Let's Encrypt" can be solved.
|
||||||
|
##
|
||||||
|
## A simple way of doing this would be to add the following in the listen
|
||||||
|
## configuration field:
|
||||||
|
## -
|
||||||
|
## port: 80
|
||||||
|
## ip: "::"
|
||||||
|
## module: ejabberd_http
|
||||||
|
|
||||||
acme:
|
acme:
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
-record(data_acc, {
|
-record(data_acc, {
|
||||||
id :: list(),
|
id :: list(),
|
||||||
|
ca_url :: url(),
|
||||||
key :: jose_jwk:key()
|
key :: jose_jwk:key()
|
||||||
}).
|
}).
|
||||||
-type data_acc() :: #data_acc{}.
|
-type data_acc() :: #data_acc{}.
|
||||||
@ -23,6 +24,9 @@
|
|||||||
%% Types
|
%% Types
|
||||||
%%
|
%%
|
||||||
|
|
||||||
|
%% Acme configuration
|
||||||
|
-type acme_config() :: [{ca_url, url()} | {contact, bitstring()}].
|
||||||
|
|
||||||
%% The main data type that ejabberd_acme keeps
|
%% The main data type that ejabberd_acme keeps
|
||||||
-type acme_data() :: proplist().
|
-type acme_data() :: proplist().
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
-export ([key_authorization/2,
|
-export ([key_authorization/2,
|
||||||
solve_challenge/3,
|
solve_challenge/3,
|
||||||
|
process/2,
|
||||||
process/2
|
acme_handler/0
|
||||||
]).
|
]).
|
||||||
%% Challenge Types
|
%% Challenge Types
|
||||||
%% ================
|
%% ================
|
||||||
@ -18,6 +18,10 @@
|
|||||||
-include("ejabberd_http.hrl").
|
-include("ejabberd_http.hrl").
|
||||||
-include("ejabberd_acme.hrl").
|
-include("ejabberd_acme.hrl").
|
||||||
|
|
||||||
|
%% This is the default endpoint for the http challenge
|
||||||
|
%% This function is called by the http_listener
|
||||||
|
acme_handler() ->
|
||||||
|
{[<<".well-known">>],acme_challenge}.
|
||||||
|
|
||||||
%% TODO: Maybe validate request here??
|
%% TODO: Maybe validate request here??
|
||||||
process(LocalPath, _Request) ->
|
process(LocalPath, _Request) ->
|
||||||
|
@ -28,6 +28,12 @@
|
|||||||
|
|
||||||
-behavior(ejabberd_config).
|
-behavior(ejabberd_config).
|
||||||
|
|
||||||
|
%%
|
||||||
|
%% Default ACME configuration
|
||||||
|
%%
|
||||||
|
|
||||||
|
-define(DEFAULT_CONFIG_CONTACT, <<"mailto:example-admin@example.com">>).
|
||||||
|
-define(DEFAULT_CONFIG_CA_URL, "https://acme-v01.api.letsencrypt.org").
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
%%
|
%%
|
||||||
@ -91,13 +97,16 @@ get_certificates0(CAUrl, Domains) ->
|
|||||||
|
|
||||||
get_certificates1(CAUrl, Domains, PrivateKey).
|
get_certificates1(CAUrl, Domains, PrivateKey).
|
||||||
|
|
||||||
|
-spec retrieve_or_create_account(url()) -> {'ok', string(), jose_jwk:key()}.
|
||||||
retrieve_or_create_account(CAUrl) ->
|
retrieve_or_create_account(CAUrl) ->
|
||||||
case read_account_persistent() of
|
case read_account_persistent() of
|
||||||
none ->
|
none ->
|
||||||
create_save_new_account(CAUrl);
|
create_save_new_account(CAUrl);
|
||||||
{ok, AccId, PrivateKey} ->
|
|
||||||
{ok, AccId, PrivateKey}
|
{ok, AccId, CAUrl, PrivateKey} ->
|
||||||
|
{ok, AccId, PrivateKey};
|
||||||
|
{ok, _AccId, _, _PrivateKey} ->
|
||||||
|
create_save_new_account(CAUrl)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -182,7 +191,7 @@ create_save_new_account(CAUrl) ->
|
|||||||
{ok, Id} = create_new_account(CAUrl, Contact, PrivateKey),
|
{ok, Id} = create_new_account(CAUrl, Contact, PrivateKey),
|
||||||
|
|
||||||
%% Write Persistent Data
|
%% Write Persistent Data
|
||||||
ok = write_account_persistent({Id, PrivateKey}),
|
ok = write_account_persistent({Id, CAUrl, PrivateKey}),
|
||||||
|
|
||||||
{ok, Id, PrivateKey}.
|
{ok, Id, PrivateKey}.
|
||||||
|
|
||||||
@ -272,14 +281,17 @@ create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) ->
|
|||||||
throw({error, DomainName, certificate})
|
throw({error, DomainName, certificate})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec ensure_account_exists() -> {ok, string(), jose_jwk:key()}.
|
-spec ensure_account_exists(url()) -> {ok, string(), jose_jwk:key()}.
|
||||||
ensure_account_exists() ->
|
ensure_account_exists(CAUrl) ->
|
||||||
case read_account_persistent() of
|
case read_account_persistent() of
|
||||||
none ->
|
none ->
|
||||||
?ERROR_MSG("No existing account", []),
|
?ERROR_MSG("No existing account", []),
|
||||||
throw({error, no_old_account});
|
throw({error, no_old_account});
|
||||||
{ok, AccId, PrivateKey} ->
|
{ok, AccId, CAUrl, PrivateKey} ->
|
||||||
{ok, AccId, PrivateKey}
|
{ok, AccId, PrivateKey};
|
||||||
|
{ok, _AccId, OtherCAUrl, _PrivateKey} ->
|
||||||
|
?ERROR_MSG("Account is connected to another CA: ~s", [OtherCAUrl]),
|
||||||
|
throw({error, account_in_other_CA})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -302,7 +314,7 @@ renew_certificates() ->
|
|||||||
-spec renew_certificates0(url()) -> string().
|
-spec renew_certificates0(url()) -> string().
|
||||||
renew_certificates0(CAUrl) ->
|
renew_certificates0(CAUrl) ->
|
||||||
%% Get the current account
|
%% Get the current account
|
||||||
{ok, _AccId, PrivateKey} = ensure_account_exists(),
|
{ok, _AccId, PrivateKey} = ensure_account_exists(CAUrl),
|
||||||
|
|
||||||
%% Find all hosts that we have certificates for
|
%% Find all hosts that we have certificates for
|
||||||
Certs = read_certificates_persistent(),
|
Certs = read_certificates_persistent(),
|
||||||
@ -883,18 +895,18 @@ data_empty() ->
|
|||||||
%% Account
|
%% Account
|
||||||
%%
|
%%
|
||||||
|
|
||||||
-spec data_get_account(acme_data()) -> {ok, list(), jose_jwk:key()} | none.
|
-spec data_get_account(acme_data()) -> {ok, list(), url(), jose_jwk:key()} | none.
|
||||||
data_get_account(Data) ->
|
data_get_account(Data) ->
|
||||||
case lists:keyfind(account, 1, Data) of
|
case lists:keyfind(account, 1, Data) of
|
||||||
{account, #data_acc{id = AccId, key = PrivateKey}} ->
|
{account, #data_acc{id = AccId, ca_url = CAUrl, key = PrivateKey}} ->
|
||||||
{ok, AccId, PrivateKey};
|
{ok, AccId, CAUrl, PrivateKey};
|
||||||
false ->
|
false ->
|
||||||
none
|
none
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec data_set_account(acme_data(), {list(), jose_jwk:key()}) -> acme_data().
|
-spec data_set_account(acme_data(), {list(), url(), jose_jwk:key()}) -> acme_data().
|
||||||
data_set_account(Data, {AccId, PrivateKey}) ->
|
data_set_account(Data, {AccId, CAUrl, PrivateKey}) ->
|
||||||
NewAcc = {account, #data_acc{id = AccId, key = PrivateKey}},
|
NewAcc = {account, #data_acc{id = AccId, ca_url = CAUrl, key = PrivateKey}},
|
||||||
lists:keystore(account, 1, Data, NewAcc).
|
lists:keystore(account, 1, Data, NewAcc).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
@ -983,13 +995,13 @@ create_persistent() ->
|
|||||||
throw({error, Reason})
|
throw({error, Reason})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec write_account_persistent({list(), jose_jwk:key()}) -> ok | no_return().
|
-spec write_account_persistent({list(), url(), jose_jwk:key()}) -> ok | no_return().
|
||||||
write_account_persistent({AccId, PrivateKey}) ->
|
write_account_persistent({AccId, CAUrl, PrivateKey}) ->
|
||||||
{ok, Data} = read_persistent(),
|
{ok, Data} = read_persistent(),
|
||||||
NewData = data_set_account(Data, {AccId, PrivateKey}),
|
NewData = data_set_account(Data, {AccId, CAUrl, PrivateKey}),
|
||||||
ok = write_persistent(NewData).
|
ok = write_persistent(NewData).
|
||||||
|
|
||||||
-spec read_account_persistent() -> {ok, list(), jose_jwk:key()} | none.
|
-spec read_account_persistent() -> {ok, list(), url(), jose_jwk:key()} | none.
|
||||||
read_account_persistent() ->
|
read_account_persistent() ->
|
||||||
{ok, Data} = read_persistent(),
|
{ok, Data} = read_persistent(),
|
||||||
data_get_account(Data).
|
data_get_account(Data).
|
||||||
@ -1060,12 +1072,13 @@ write_cert(CertificateFile, Cert, DomainName) ->
|
|||||||
throw({error, DomainName, saving})
|
throw({error, DomainName, saving})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_config_acme() -> [{atom(), bitstring()}].
|
-spec get_config_acme() -> acme_config().
|
||||||
get_config_acme() ->
|
get_config_acme() ->
|
||||||
case ejabberd_config:get_option(acme, undefined) of
|
case ejabberd_config:get_option(acme, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
?ERROR_MSG("No acme configuration has been specified", []),
|
?WARNING_MSG("No acme configuration has been specified", []),
|
||||||
throw({error, configuration});
|
%% throw({error, configuration});
|
||||||
|
[];
|
||||||
Acme ->
|
Acme ->
|
||||||
Acme
|
Acme
|
||||||
end.
|
end.
|
||||||
@ -1077,19 +1090,21 @@ get_config_contact() ->
|
|||||||
{contact, Contact} ->
|
{contact, Contact} ->
|
||||||
Contact;
|
Contact;
|
||||||
false ->
|
false ->
|
||||||
?ERROR_MSG("No contact has been specified", []),
|
?WARNING_MSG("No contact has been specified in configuration", []),
|
||||||
throw({error, configuration_contact})
|
?DEFAULT_CONFIG_CONTACT
|
||||||
|
%% throw({error, configuration_contact})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_config_ca_url() -> string().
|
-spec get_config_ca_url() -> url().
|
||||||
get_config_ca_url() ->
|
get_config_ca_url() ->
|
||||||
Acme = get_config_acme(),
|
Acme = get_config_acme(),
|
||||||
case lists:keyfind(ca_url, 1, Acme) of
|
case lists:keyfind(ca_url, 1, Acme) of
|
||||||
{ca_url, CAUrl} ->
|
{ca_url, CAUrl} ->
|
||||||
CAUrl;
|
CAUrl;
|
||||||
false ->
|
false ->
|
||||||
?ERROR_MSG("No CA url has been specified", []),
|
?ERROR_MSG("No CA url has been specified in configuration", []),
|
||||||
throw({error, configuration_ca_url})
|
?DEFAULT_CONFIG_CA_URL
|
||||||
|
%% throw({error, configuration_ca_url})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -1097,7 +1112,7 @@ get_config_ca_url() ->
|
|||||||
get_config_hosts() ->
|
get_config_hosts() ->
|
||||||
case ejabberd_config:get_option(hosts, undefined) of
|
case ejabberd_config:get_option(hosts, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
?ERROR_MSG("No hosts have been specified", []),
|
?ERROR_MSG("No hosts have been specified in configuration", []),
|
||||||
throw({error, configuration_hosts});
|
throw({error, configuration_hosts});
|
||||||
Hosts ->
|
Hosts ->
|
||||||
Hosts
|
Hosts
|
||||||
@ -1107,8 +1122,9 @@ get_config_hosts() ->
|
|||||||
get_config_cert_dir() ->
|
get_config_cert_dir() ->
|
||||||
case ejabberd_config:get_option(cert_dir, undefined) of
|
case ejabberd_config:get_option(cert_dir, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
?ERROR_MSG("No cert_dir configuration has been specified", []),
|
?WARNING_MSG("No cert_dir configuration has been specified in configuration", []),
|
||||||
throw({error, configuration});
|
mnesia:system_info(directory);
|
||||||
|
%% throw({error, configuration});
|
||||||
CertDir ->
|
CertDir ->
|
||||||
CertDir
|
CertDir
|
||||||
end.
|
end.
|
||||||
@ -1136,8 +1152,7 @@ parse_cert_dir_opt(Opt) when is_bitstring(Opt) ->
|
|||||||
true = filelib:is_dir(Opt),
|
true = filelib:is_dir(Opt),
|
||||||
Opt.
|
Opt.
|
||||||
|
|
||||||
-spec opt_type(acme) -> fun(([{ca_url, string()} | {contact, bitstring()}]) ->
|
-spec opt_type(acme) -> fun((acme_config()) -> (acme_config()));
|
||||||
([{ca_url, string()} | {contact, bitstring()}]));
|
|
||||||
(cert_dir) -> fun((bitstring()) -> (bitstring()));
|
(cert_dir) -> fun((bitstring()) -> (bitstring()));
|
||||||
(atom()) -> [atom()].
|
(atom()) -> [atom()].
|
||||||
opt_type(acme) ->
|
opt_type(acme) ->
|
||||||
|
@ -136,8 +136,9 @@ init({SockMod, Socket}, Opts) ->
|
|||||||
true -> [{[], ejabberd_xmlrpc}];
|
true -> [{[], ejabberd_xmlrpc}];
|
||||||
false -> []
|
false -> []
|
||||||
end,
|
end,
|
||||||
|
Acme = [acme_challenge:acme_handler()],
|
||||||
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
||||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
RequestHandlers = Acme ++ DefinedHandlers ++ Captcha ++ Register ++
|
||||||
Admin ++ Bind ++ XMLRPC,
|
Admin ++ Bind ++ XMLRPC,
|
||||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user