ivanhercaz

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

jjcarstens

Nerves Core Team

Thanks @ivanhercaz !

I think there are a few steps to take:

  • Try curl https://nerveshub.DOMAIN.EXT/api/health just 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 :cacerts option as a list of DER format certs (or if you have the file, you can just use cacertfile: "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_cert signer CA with NH either in the web UI or with mix nerves_hub.ca_certificates register /path/to/some_signer_cert.crt
  • mix nerves_hub.ca_certificates will first need mix nerves_hub.user auth
    • Ensure your NervesHubUserAPI config has the ca.pem for the server in the options as well
# 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

ivanhercaz

So the walkthrough I followed is the next one:

  1. Format the server to start all the process from zero.
  2. ./bin/remote setup to setup the server.
  3. Formateo el servidor, empiezo con él desde cero.
  4. Create the database, user and assign password.
  5. Generate the ca.key with openssl genrsa -out ca.key 2048.
  6. Generate the ca.pem with openssl req -x509 -new -nodes -key ca.key -sha256 -days 1825 -out ca.pem -subj '/OU=NervesHub/'.
  7. Generate nerveshub.DOMAIN.EXT-key.pem with openssl genrsa -out nerveshub.DOMAIN.EXT-key.pem
  8. Generate nerveshub.DOMAIN.EXT.csr with openssl req -new -key nerveshub.DOMAIN.EXT-key.pem -out nerveshub.DOMAIN.EXT.csr -subj '/CN=nerveshub.DOMAIN.EXT/'
  9. Create nerveshub.DOMAIN.EXT.ext with:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keynEcipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = nerveshub.DOMAIN.EXT
  1. 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
  2. Move nerveshub.DOMAIN.EXT.pem y nerveshub.DOMAIN.EXT-key.pem to /etc/ssl.
  3. Modify the Caddyfile..
  4. 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=
  1. Run ./bin/remote deploy and 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 empty ExecStart= before the existing ExecStart=.
  2. Run ./bin/remote deploy and works!
  3. Check the NervesHub log and it shows an error because it needs the ca.pem file in /etc/ssl, so I copy the ca.pem to /etc/ssl.
  4. NervesHub web is already accesible and an API call to /api/health returns {"status": "UP"}.
  5. Run ./bin/remote iex and create an account.
  6. Download from the server the ca.pem into ~/.nerves-hub.
  7. 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
  8. Generate the signer certificate with mix nerves_key.signer create ivanhc, so now I have ivanhc.key and ivanhc.cert.
  9. Go to NervesHub web and create a product, then create a device and upload ivanhc.cert, copy the serial and generate the device certificate with mix 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
  10. Convert ca.pem to der with openssl x509 -outform der -in ca.pem -out ca.der.
  11. 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]
  ]
  1. And finally… any command I use to communicate with NervesHub fails, for example, if I run mix nerves_hub.device list it 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?

Where Next?

Popular in Questions Top

_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement