ivanhercaz
What is the proper certificates configuration for NervesHub 2.0 and NervesHubLink?
Recently it was merged a really wonderful PR in the current main branch of NervesHub, that eases the self hosting setup and, I can confirm after test it, that it really eases the deployment. It is friendly and easy to manage.
https://github.com/nerves-hub/nerves_hub_web/pull/932
It is part of the development of the NervesHub 2.0: Introducing NervesHub 2.0. Thus it is important to keep in mind that the documentation is also in progress of update, although the NervesHub team is doing a really great work with both parts, the development and the documentation about it and the deployment process.
So, thanks to this PR and the documentation about the self hosting setup provided in the readme of the repository, I got it deployed on my cloud server!
Now I am in the step of trying to link my Nerves devices to my NervesHub instance and, although I understand the theory and practically everything about the related and necessary stuff to use NervesHubLink, I feel bit lost with the certificates configuration. At this moment I am getting this error on the server side:
May 11 03:01:05 nerveshub nerves_hub[529627]: 03:01:05.118 level=notice TLS :server: In state :wait_cert received CLIENT ALERT: Fatal - Unknown CA
And this error with any mix task of NervesHub (for example, mix nerves_hub.ca_certificate list):
$ mix nerves_hub.ca_certificate list
NervesHub server: nerveshub.DOMAIN.EXT:4001
NervesHub organization: ORG
13:45:47.397 [notice] TLS :client: In state :wait_cert at ssl_handshake.erl:2111 gener
ated CLIENT ALERT: Fatal - Unknown CA
Failed to list CA certificates
reason: {:error, {:tls_alert, {:unknown_ca, 'TLS client: In state wait_cert at ssl_han
dshake.erl:2111 generated CLIENT ALERT: Fatal - Unknown CA\n'}}}
In my environment variables I have NERVES_HUB_HOST pointing to my instance, NERVES_HUB_PORT as 4001, the one for the devices endpoint and then I have NERVES_HUB_KEY and NERVES_HUB_CERT with the key and cert generated in the NervesHub setup I did in the server. Of course, I also have a generated token for my user in NERVES_HUB_TOKEN.
But I think the problem is that I am mixing concepts related with the certificates and I am configuring it in the wrong way. Let’s see what I did in the NervesHub setup.
Before the deployment step I had to generate the certificates for my NervesHub instance, so I follow the next instructions, but of course adapted to my domain:
$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -sha256 -days 1825 -out ca.pem -subj '/OU=NervesHub/'
$ openssl genrsa -out device.nerves-hub.org-key.pem
$ openssl req -new -key device.nerves-hub.org-key.pem -out device.nerves-hub.org.csr -subj '/CN=device.nerves-hub.org/'
$ nvim device.nerves-hub.org.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = device.nerves-hub.org
$ openssl x509 -req -in device.nerves-hub.org.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out device.nerves-hub.org.pem -days 825 -sha256 -extfile device.nerves-hub.org.ext
$ sudo mv device.nerves-hub.org-key.pem /etc/ssl/
$ sudo mv device.nerves-hub.org.pem /etc/ssl/
So I replaced all device.nerves-hub.org by device.nerveshub.DOMAIN.EXT (DOMAIN.EXT is correct, I am just hiding it because I prefer to not have links to it). And in my nerves_hub.env I have the next related with the host/ports for each endpoint, that I think is correct after read several times the instructions:
HOST=nerveshub.DOMAIN.EXT
PORT=4000
URL_SCHEME=https
URL_PORT=443
DEVICE_HOST=device.nerveshub.DOMAIN.EXT
DEVICE_PORT=4001
API_HOST=nerveshub.DOMAIN.EXT
API_PORT=4002
API_URL_PORT=443
And finally, in my Nerves device I added NervesHubLink package poiting to its git repository and specifying the branch nerves-hub-2.0 (I also tested it with main and the latest version available in Hex). In config.exs I set the next:
config :nerves_hub_link,
device_api_host: "nerveshub.DOMAIN.EXT",
device_api_port: 4001,
device_api_sni: "nerveshub.DOMAIN.EXT",
ssl: [
cert: "priv/ca.pem",
keyfile: "priv/ca.key",
]
cert: "priv/ca.pem" corresponds with NERVES_HUB_CERT and key: priv/ca.key corresponds with NERVES_HUB_KEY, generated with the previous openssl commands in the server:
$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -sha256 -days 1825 -out ca.pem -subj '/OU=NervesHub/'
And I know that I should provide cacerts option too, but I am really lost with the certificates hehe.
My theory is:
- I am not understanding what cert, key and ca I must use in my Nerves device.
- I may be confused in the certificates generation step of NervesHub and I may be misunderstood something important for the step of linking a device with the hub.
This has become a long thread and probably quite confusing and with different problems, sorry about that hehe. I just hope someone can enlighten me in the darkness that I have been involved in with the certificates.
Edit: I am reading the next threads looking for a possible answer to my error/confusion:
Most Liked
jjcarstens
Thanks @ivanhercaz !
I think there are a few steps to take:
- Try
curl https://nerveshub.DOMAIN.EXT/api/healthjust as a simple connectivity check - The
NERVES_HUB_CERT(ca.pem) is your server certificate that it is present to the device on connect (like any other HTTPS server/website you visit). On device, that needs to go in:cacertsoption as a list of DER format certs (or if you have the file, you can just usecacertfile: "ca.pem") - You need to create a signer CA certificate used for creating device certificates. Admittedly, the documentation around this is dated and sparse. The easiest steps would be to use the task in GitHub - nerves-hub/nerves_key: NervesKey info and support for Elixir · GitHub
mix nerves_key.signer create some_signer_name
- Then, create a device certificate
mix nerves_hub.device cert create SERIAL --signer-cert /path/to/some_signer_name.cert --signer-key /path/to/some_signer_name.key- This device SERIAL needs to be created in NH with the newly generated certificate. Its probably easiest to do this in the UI and manually upload the certificate to it
- Register your
some_signer_certsigner CA with NH either in the web UI or withmix nerves_hub.ca_certificates register /path/to/some_signer_cert.crt mix nerves_hub.ca_certificateswill first needmix nerves_hub.user auth- Ensure your NervesHubUserAPI config has the
ca.pemfor the server in the options as well
- Ensure your NervesHubUserAPI config has the
# cacerts needs to be DER formatted. Probably easiest to use
# `cacerts =Enum.map(X509.from_pem("ca.pem"), &X509.Certificate.to_der/1)` and put that
# result in the cacerts key below
config :nerves_hub_user_api, ssl: [cacerts: cacerts]
ivanhercaz
So the walkthrough I followed is the next one:
- Format the server to start all the process from zero.
./bin/remote setupto setup the server.- Formateo el servidor, empiezo con él desde cero.
- Create the database, user and assign password.
- Generate the
ca.keywithopenssl genrsa -out ca.key 2048. - Generate the
ca.pemwithopenssl req -x509 -new -nodes -key ca.key -sha256 -days 1825 -out ca.pem -subj '/OU=NervesHub/'. - Generate
nerveshub.DOMAIN.EXT-key.pemwithopenssl genrsa -out nerveshub.DOMAIN.EXT-key.pem - Generate
nerveshub.DOMAIN.EXT.csrwithopenssl req -new -key nerveshub.DOMAIN.EXT-key.pem -out nerveshub.DOMAIN.EXT.csr -subj '/CN=nerveshub.DOMAIN.EXT/' - Create
nerveshub.DOMAIN.EXT.extwith:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keynEcipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = nerveshub.DOMAIN.EXT
- Generate
nerveshub.DOMAIN.EXT.pem:openssl x509 -req -in nerveshub.DOMAIN.EXT.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out nerveshub.DOMAIN.EXT.pem -days 825 -sha256 -extfile nerveshub.DOMAIN.EXT.ext - Move
nerveshub.DOMAIN.EXT.pemynerveshub.DOMAIN.EXT-key.pemto /etc/ssl. - Modify the Caddyfile..
- Fill the environment variables in
/etc/nerves_env.
DATABASE_URL=postgresql://user:password@localhost/nerveshub
LOCAL_IPV4=127.0.0.1
ERL_COOKIE=
LIVE_VIEW_SIGNING_SALT=generated with phx.gen.secrets...
SECRET_KEY_BASE=generated with phx.gen.secrets...
HOST=nerveshub.DOMAIN.EXT
PORT=4000
URL_SCHEME=https
URL_PORT=443
DEVICE_HOST=nerveshub.DOMAIN.EXT
DEVICE_PORT=4001
API_HOST=nerveshub.DOMAIN.EXT
API_PORT=4002
API_URL_PORT=443
FROM_EMAIL=ivan@DOMAIN.EXT
NERVES_HUB_APP=all
FIRMWARE_UPLOAD_BACKEND=local
FIRMWARE_UPLOAD_PATH=
- Run
./bin/remote deployand fail, it is because the systemd service file was wrong (Feedback issues with the self hosting · Issue #952 · nerves-hub/nerves_hub_web · GitHub), so I fix it with an emptyExecStart=before the existingExecStart=. - Run
./bin/remote deployand works! - Check the NervesHub log and it shows an error because it needs the
ca.pemfile in/etc/ssl, so I copy theca.pemto/etc/ssl. - NervesHub web is already accesible and an API call to
/api/healthreturns{"status": "UP"}. - Run
./bin/remote iexand create an account. - Download from the server the
ca.peminto~/.nerves-hub. - Set the next environment variables:
export NERVES_HUB_HOST=nerveshub.DOMAIN.EXT
export NERVES_HUB_PORT=4001
export NERVES_HUB_TOKEN=$token_of_my_nerveshub_account$
export NERVES_HUB_CERT=cat /home/ivanhercaz/.nerves-hub/ca.pem
export NERVES_HUB_ORG=organization - Generate the signer certificate with
mix nerves_key.signer create ivanhc, so now I haveivanhc.keyandivanhc.cert. - Go to NervesHub web and create a product, then create a device and upload
ivanhc.cert, copy the serial and generate the device certificate withmix nerves_hub.device cert create SERIAL --signer-cert ivanhc.cert --signer-key ivanhc.key:
NervesHub server: nerveshub.DOMAIN.EXT:4001
NervesHub organization: asi
Creating certificate for SERIAL
Signer cert path: ivanhc.cert
Signer key path: ivanhc.key
Finished - Convert
ca.pemtoderwithopenssl x509 -outform der -in ca.pem -out ca.der. - Set the configuration in
config/config.exs:
cacerts =
"/home/ivanhercaz/.nerves-hub/ca.der"
|> File.read!()
config :nerves_hub_user_api,
ssl: [
cacerts: [cacerts]
]
config :nerves_hub_link,
device_api_host: "nerveshub.DOMAIN.EXT",
device_api_port: 4001,
device_api_sni: "nerveshub.DOMAIN.EXT",
ssl: [
certfile:
"/home/ivanhercaz/.nerves-hub/SERIAL-cert.pem",
keyfile:
"/home/ivanhercaz/.nerves-hub/SERIAL-key.pem",
cacerts: [cacerts]
]
- And finally… any command I use to communicate with NervesHub fails, for example, if I run
mix nerves_hub.device listit returns:
22:58:44.081 [notice] TLS :client: In state :wait_cert at ssl_handshake.erl:2113 gener
ated CLIENT ALERT: Fatal - Handshake Failure
- {:bad_cert, :hostname_check_failed}
Unhandled error: {:error, {:tls_alert, {:handshake_failure, 'TLS client: In state wait
_cert at ssl_handshake.erl:2113 generated CLIENT ALERT: Fatal - Handshake Failure\n {b
ad_cert,hostname_check_failed}'}}}
About the config/config.exs and ca.pem. My ca.pem only has one certificate inside, I mean only one pair of -----BEGIN CERTIFICATE-----CERT-----END CERTIFICATE-----. Thus I use the File.read!() and then insert into a list in :cacerts.
But based on what @jjcarstens suggested and another examples I have seen, like Poser repository, I understand the normal situation is that ca.pem has more than one. Am I doing something wrong with the ca.pem file in any of the steps I described?
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










