eclipse / hawkbit

Eclipse hawkBit™
https://projects.eclipse.org/projects/iot.hawkbit
Eclipse Public License 2.0
444 stars 186 forks source link

Hawkbit auth not working with client certificate #964

Open papipano opened 4 years ago

papipano commented 4 years ago

Hi everyone, I installed Hawkbit on an Amazon EC2 instance from sources. The database is configured with mariadb and it works fine. The hawkbit instance runs at localhost address on port 8080 without SSL encryption, behind a nginx reverse proxy that is using https protocol. The main goal is to update the devices using swupdate (rel.2019.11) and it works fine with targettoken and gatewaytoken, but not with client certificate authentication. Here are the config files:

NGINX REVERSE PROXY CONFIG FILE stored in /etc/nginx/conf.d/hawkbit.conf

server {
        listen 80;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        client_max_body_size 300M;

        listen 443 ssl;
        ssl_certificate /opt/hawkbit/cacerts/sslcert.crt;
        ssl_certificate_key /opt/hawkbit/cacerts/sslcert.key;

        if ($scheme != "https") {
           return 301 https://$host$request_uri;
        }

        server_name hawkbit.example.it;

        # client certificate
        ssl_client_certificate /etc/nginx/client_certs/caroot.cer;

        location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Server $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
            proxy_ssl_certificate         /etc/nginx/client_certs/chain.cer;
            proxy_ssl_certificate_key     /etc/nginx/client_certs/client.key;
            proxy_set_header  Host $http_host;
            proxy_pass http://localhost:8081/;
        }
}

HAWKBIT SERVICE in /etc/systemd/system/hawkbit.service

[Unit]
Description=Hawkbit
After=syslog.target
After=network.target

[Service]
Type=simple
User=admin
ExecStart=/usr/bin/java -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses -jar /opt/hawkbit/hawkbit-runtime/hawkbit-update-server/target/hawkbit-update-server-0.3.0-SNAPSHOT.jar -v
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=hawkbit

[Install]
WantedBy=multi-user.target

And the process started as expected:

admin     1251  0.1  6.8 4577092 552492 ?      Ssl  Apr30   9:00 /usr/bin/java -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses -jar /opt/hawkbit-8080/hawkbit/hawkbit-runtime/hawkbit-update-server/target/hawkbit-update-server-0.3.0-SNAPSHOT.jar -v

I am sure the certificates are correct and I tried them using openssl options.

$ openssl x509 –noout –modulus –in client.cer | openssl md5           
(stdin)= d41d8cd98f00b204e9800998ecf8427e
$ openssl rsa –noout –modulus –in client.key | openssl md5            
(stdin)= d41d8cd98f00b204e9800998ecf8427e
$ openssl req –noout –modulus –in client.csr | openssl md5            
(stdin)= d41d8cd98f00b204e9800998ecf8427e

WAY FOR CREATION OF CHAIN CERTIFICATE:

cat client.cer caroot.cer > chain.cer

SWUPDATE SURICATTA SECTION

suricatta :
{

        tenant          = "default";
        id              = "GN77500_SN100";
        confirm         = 0;
        url             = "https://hawkbit.example.it";
        polldelay       = 20;
        nocheckcert     = true;
        retry           = 4;
        retrywait       = 200;
        enable          = true;
        cafile          = "/etc/swupdate/CertificateIssuer.crt";
        sslkey          = "/etc/swupdate/client.key";
        sslcert         = "/etc/swupdate/chain.cer";
        loglevel        = 10;
};

the common name in client certificate is the same sets in the suricatta section or in the following curl command as stated below:

immagine

put fingerprint into hawkbit configuration

immagine

CURL COMMAND OUTPUT

curl -i -k --cert ./chain.cer --key ./client.key https://hawkbit.example.it/default/controller/v1/GN77500_SN100

HTTP/1.1 401 Unauthorized
Server: nginx/1.14.2
Date: Mon, 04 May 2020 09:35:15 GMT
Transfer-Encoding: chunked
Connection: keep-alive
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY

Many thanks in advance to anyone who help me to solve the above issue!

Max

embetrix commented 4 years ago

Hi,

I dealed with the same issue but there are workarounds, you could disable completely the autentication in hawkbit and let Nginx completely handle the security (probably better) :

https://github.com/eclipse/hawkbit/issues/723

hawkbit.server.ddi.security.authentication.anonymous.enabled=true

Of course you should setup the firewall to block the Hawkbit port 8081 in your case to be accessible only from the localhost!

Moreover I would Not recommend to use any security features in Hawkbit ! It's based on old Java 8 which has tons of vulnerabilities...

schabdo commented 4 years ago

@papipano it's hard to tell what's going wrong here without having any logs. Maybe you have a more detailed look at the validation here.

sleeping-barber commented 4 years ago

@papipano any news here? I am also struggling with the same issue atm.

sleeping-barber commented 4 years ago

Hi, I am just reaching out because I figured it out and @papipano problem looks like mine and I want to prevent this https://xkcd.com/979/ 😊

I found following essential information https://gitter.im/eclipse/hawkbit?at=5a72f4cc4a6b0dd32b75fbcd and I adjusted my Nginx configuration to proxy_set_header the two headers like this:

proxy_set_header X-Ssl-Client-Cn $ssl_client_s_dn_cn;
proxy_set_header X-Ssl-Issuer-Hash-1 Hawkbit;

Important is that the X-Ssl-Client-Cn must be also the ID of the target within our Hawkbit instance means, create a new Target called Target05. Then the request which is forwared to Hawkbit needs to have this value of Taget05 in it.

I solved it with the map module in Nginx to extract the CN out of the cert.

You also need to but the SSL-Issuer-Hash in your Hawkbit Settings and need to sent this value to make it work.

Cheers!

papipano commented 3 years ago

Hi all, sorry for my late response but I was busy with other tasks and I didn't work on hawkbit for a long time.

Above all, thanks to @midnightrun for posting his solution.

I am just reaching out because I figured it out and @papipano problem looks like mine and I want to prevent this https://xkcd.com/979/ 😊

I found following essential information https://gitter.im/eclipse/hawkbit?at=5a72f4cc4a6b0dd32b75fbcd and I adjusted my Nginx configuration to proxy_set_header the two headers like this:

I read that information too, and I tried to implement the solution @midnightrun posted.

proxy_set_header X-Ssl-Client-Cn $ssl_client_s_dn_cn;
proxy_set_header X-Ssl-Issuer-Hash-1 Hawkbit;

I put the 2 variables and the map in the hawkbit.conf (as in the file below)

map $ssl_client_s_dn $ssl_client_s_dn_cn
{
    default "";
    ~,CN=(?[^,]+) $CN;
};
server
{
        listen 80;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        client_max_body_size 300M;
        listen 443 ssl;
        ssl_certificate /opt/hawkbit/cacerts/sslcert.crt;
        ssl_certificate_key /opt/hawkbit/cacerts/sslcert.key;
        if ($scheme != "https") {
           return 301 https://$host$request_uri;
        }
        server_name hawkbit.example.it;
location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Server $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header  Host $http_host;
            proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
            proxy_set_header X-Ssl-Client-Cn $ssl_client_s_dn_cn;
            proxy_set_header X-Ssl-Issuer-Hash-1 Hawkbit;
            proxy_ssl_verify    on;
            proxy_pass http://localhost:8081/;
        }
}

> 
> Important is that the `X-Ssl-Client-Cn` must be also the ID of the target within our Hawkbit instance means, create a new Target called `Target05`. Then the request which is forwared to Hawkbit needs to have this value of `Taget05` in it.
> 
The client certificate CN is GN77500_SN100, and the curl command:
curl -i --cert ./chain.cer --key ./client.key https://hawkbit.example.it/default/controller/v1/GN77500_SN100
>
> I solved it with the map module in Nginx to extract the CN out of the cert.
> 
But, even I change the hawkbit.conf file as described above, I still have the same error: 401 Unauthorized, and I'm still stuck here!
> 
> You also need to but the `SSL-Issuer-Hash` in your Hawkbit Settings and need to sent this value to make it work.
> 
So all the rest is quite clear, but I don't understand what does it mean... Where have I to change the Hawkbit settings? In applcation.properties? Or where else?

Pls. help me to find a solution.
Many thanks in advance.

Max
schabdo commented 3 years ago

but I don't understand what does it mean... Where have I to change the Hawkbit settings?

I think the chat is referring to the expected hash you have to provide within the system config UI so hawkBit is able to proof that the given issuer hash is the one you expect

kyokko-nagahara commented 2 years ago

Hi,

I dealed with the same issue but there are workarounds, you could disable completely the autentication in hawkbit and let Nginx completely handle the security (probably better) :

723

hawkbit.server.ddi.security.authentication.anonymous.enabled=true

Of course you should setup the firewall to block the Hawkbit port 8081 in your case to be accessible only from the localhost!

Moreover I would Not recommend to use any security features in Hawkbit ! It's based on old Java 8 which has tons of vulnerabilities...

I can't read the link because it says "Could not find any vulnerabilities matching the requested criteria". Would you please tell me what vulnerability it is so that I can investigate if it is currently resolved?

Yusufss4 commented 11 months ago

Are there any updates on this issue? I am experiencing the same issue. Could not make it work the Mutual TLS.

Yusufss4 commented 11 months ago

@sleeping-barber Do you have a valid nginx configuration that extarct the values from the X-Ssl-Client-Cn with the target id that you mentioned?

I have tried to use the map function that provided by the @papipano but according to the nginx it is not a valid configuration.

map $ssl_client_s_dn $ssl_client_s_dn_cn
{
    default "";
    ~,CN=(?[^,]+) $CN;
};

Error while updating configuration: nginx: [emerg] pcre2_compile() failed: unrecognized character after (? or (?- in ",CN=(?[^,]+)" at "[^,]+)" in /etc/nginx/conf.d/default.conf:4

Any help is greatly appreciated.

Yusufss4 commented 10 months ago

I have successfully implemented mutual TLS using Nginx as a reverse proxy. Here's how you can do it:

Once you have obtained certificates for both the client and the Hawkbit server, either from the same or different CAs, you need to place these certificates in their respective locations.

Nginx Reverse Proxy Configurations

After obtaining your certificates, you need to deploy your proxy server and apply the provided configurations. You can apply Mutual TLS specifically to the URL given below to implement the process only for devices using the Device Integration API:

hawkbit_example_url.com/default/controller/

This ensures that other clients, like UI users, can connect to Hawkbit without requiring client certificates. They can use Username and Password in the Management API, eliminating the need for authentication and making it more user-friendly.

# Nginx Hawkbit Configurations

# Gets the Common Name of the certificate from the client certificate. 
map $ssl_client_s_dn $ssl_client_s_dn_cn {
    default "";
    ~CN=(?<CN>[^,]+) $CN;
}

server {
    listen 80;
    listen [::]:80;

    server_name hawkbit.dev.example.com www.hawkbit.dev.example.com;
    server_tokens off;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://hawkbit.dev.example.com$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name hawkbit.dev.example.com;

    ssl_certificate /etc/nginx/ssl/live/hawkbit.dev.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/hawkbit.dev.example.com/privkey.pem;

    ssl_client_certificate /etc/nginx/client-cer/BORDA-ROOTCA.crt;
    ssl_verify_client      optional;
    ssl_verify_depth 3;

   # For devices that is using device integration API, 
   # Mutual TLS is required.
   location /default/controller/ {
        if ($ssl_client_verify != SUCCESS) {
           return 403;
        }

        proxy_pass http://dev.example.com:8080;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # Client certificate Common Name and Issuer Hash is required
        # for auth in hawkbit. 
        proxy_set_header X-Ssl-Client-Cn $ssl_client_s_dn_cn;
        proxy_set_header X-Ssl-Issuer-Hash-1 Hawkbit;

        # These are required for clients to upload and download software. 
        proxy_request_buffering off;
        client_max_body_size 1000m;
   }

   location /DEFAULT/controller/ {
        if ($ssl_client_verify != SUCCESS) {
           return 403;
        }

        proxy_pass http://dev.example.com:8080;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_set_header X-Ssl-Client-Cn $ssl_client_s_dn_cn;
        proxy_set_header X-Ssl-Issuer-Hash-1 Hawkbit;

        proxy_request_buffering off;
        client_max_body_size 1000m;
   }

   # For clients that is using UI or Management API
   location / {
        proxy_pass http://dev.example.com:8080;
        proxy_set_header Host $http_host; 
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_request_buffering off;
        client_max_body_size 1000m;
   }   
}

To authenticate the request to Hawkbit itself, the common name and issuer hash of the presented client certificate are required. The issuer hash of a certificate is the hash of the certificate that signed the client certificate, which in our case is the CA.

You can use the following command to get the issuer hash:

openssl x509 -in client_certificate.crt -issuer_hash -noout

However, in the Nginx configuration, obtaining the issuer hash is not possible without addons. Therefore, this header is manually entered as Hawkbit.

When deploying Nginx, you will need a .yml file. Here's an example docker-compose.yml file for Nginx Docker.

version: '3'

services:
  webserver:
    image: nginx:latest
    ports:
      - 80:80
      - 443:443
    restart: always
    volumes:
      - ./nginx/conf/:/etc/nginx/conf.d/:ro
      - ./certbot/www:/var/www/certbot/:ro
      - ./certbot/conf/:/etc/nginx/ssl/:ro
      - ./client-cer/:/etc/nginx/client-cer/
      - ./landing-page/:/etc/webserver/landing-page
  certbot:
    image: certbot/certbot:latest
    volumes:
      - ./certbot/www/:/var/www/certbot/:rw
      - ./certbot/conf/:/etc/letsencrypt/:rw

/client-cer/:/etc/nginx/client-cer/ is the designated location for the certificate authority that has signed the client certificate. The presented client certificate will be verified against this CA.

Swupdate Suricatta Configurations

If the client is utilizing the SWUpdate Suricatta service, the configurations on the device or client side should also be adjusted as follows:

The location of the config file is /etc/swupdate/swupdate.conf

suricatta :
{
tenant = "default";
id = "[ID]";
url = "[URL]";
nocheckcert = false;
cafile = "[CAFile]";
sslkey = "/etc/ssl/certs/[ID].key";
sslcert = "/etc/ssl/certs/[ID].crt";
};

If your client service is a linux, you can use the command bellow to see the logs produced by the swupdate.

journalctl --follow -u swupdate

Hawkbit Configurations

There are also some configurations that you need update when you are deploying your hawkbit service. If your deploying it as a docker container you need to update your docker-compose.yml file such as,

version: '3'

services:
  # RabbitMQ service
  rabbitmq:
    image: "rabbitmq:3-management"
    environment:
      RABBITMQ_DEFAULT_VHOST: "/"
      RABBITMQ_DEFAULT_USER: "username"
      RABBITMQ_DEFAULT_PASS: "password"
    restart: always
    ports:
      - "15672:15672"
      - "5672:5672"
    labels:
      NAME: "rabbitmq"

  # MySQL service
  mysql:
    image: "mysql:8.0"
    environment:
      MYSQL_DATABASE: "hawkbit"
      MYSQL_ROOT_PASSWORD: "password"
    restart: always
    ports:
      - "3306:3306"
    labels:
      NAME: "mysql"

  # HawkBit service
  hawkbit:
    image: "hawkbit/hawkbit-update-server:latest-mysql"
    environment:
      SPRING_APPLICATION_JSON: '{
        "spring.datasource.url": "jdbc:mariadb://mysql:3306/hawkbit?user=root&password=password&allowPublicKeyRetrieval=true",
        "spring.datasource.username": "root",
        "spring.datasource.password": "password",

        "spring.rabbitmq.host": "rabbitmq",
        "spring.rabbitmq.username": "bordatech",
        "spring.rabbitmq.password": "password",

        "hawkbit.server.im.users[0].username": "hawkbit",
        "hawkbit.server.im.users[0].password": "{noop}password",
        "hawkbit.server.im.users[0].firstname": "MutualTLS",
        "hawkbit.server.im.users[0].lastname": "Super Admin",
        "hawkbit.server.im.users[0].permissions": "ALL",

        "hawkbit.artifact.url.protocols.download-http.rel": "download-http",
        "hawkbit.artifact.url.protocols.download-http.hostname": "hawkbit.dev.example",
        "hawkbit.artifact.url.protocols.download-http.protocol": "https",
        "hawkbit.artifact.url.protocols.download-http.supports": "DMF,DDI",
        "hawkbit.artifact.url.protocols.download-http.ref": "{protocol}://{hostnameRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}"
      }'
    restart: no
    ports:
      - "8080:8080"
    labels:
      NAME: "hawkbit"

A different aspect of this configuration involves the settings for artifact downloads. The link is generated by Hawkbit and then transmitted to the client, enabling the client to download the firmware package through this link. Remember to replace "hostname" with your actual hostname.

In Hawkbit's UI section, under system configuration, make sure to select Allow targets to authenticate via a certificate authenticated by a reverse proxy and input the issuer hash as "Hawkbit".

hawkbit_settings

After successfully generating your certificates with the correct chain, deploying your Nginx and Hawkbit services with appropriate configurations, and updating the settings on the device side, you will be able to establish a certificate-based authentication mechanism. This will eliminate the necessity of sharing a security token with the server.

Testing

You can test the communication by using the Curl command below to see if you successfully implemented Mutual TLS:

curl -L -v --cert Client.crt --key Client.key --cacert CA2.pem https://hawkbit.dev.example.com/default/controller/v1/{device-id}

In the UI, after uploading an SWU package and requesting a firmware update, you can use the link below to attempt to install the software package.

curl -L -v --cert Client.crt --key Client.key --cacert CA2.pem https://hawkbit.dev.example.com/default/controller/v1/{device-id}/softwaremodules/{artifact-id}/artifacts/hawkbit_updated_5.swu --output outputfile

References

Here are some references that can assist you in creating certificates and deploying your services: