Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/source/14_faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ I would like to use my favorite web server instead of the one embedded into **RP

Can you add login/password and authentication to RPI-Monitor web interface?

**No**. I'll never add such a feature to **RPi-Monitor**. Why? Just because it is
something complex to do in a clean and fully secured way. Some software are
already design to do the job and they do it well.
**Yes** but only using the basic-authentication challenge. Only a single username
and password is allowed, and be aware that if you are not in HTTPS, the username
and password may be collected by a malicious user.

A `solution is proposed in this documentation using nginx
An other `solution is proposed in this documentation using nginx
<34_autentication.html#authentication-and-secure-access>`_ frontend.

|
Expand Down
5 changes: 5 additions & 0 deletions docs/source/21_daemon.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ daemon.logfile=/var/log/rpimonitor.log
daemon.loglevel=0
Define log level (Default:0)

daemon.basicauth=<none>
Define a username and a password to access the embedded web server. (Default: no authentication)

.. important:: Syntax is ``username:password`` in plain text (note the semicolon).

SNMP configuration
------------------
``snmpagent`` is defining SNMP behavior of ``rpimonitord``.
Expand Down
5 changes: 4 additions & 1 deletion docs/source/34_autentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Authentication and secure access
================================

The purpose of **RPi-Monitor** is to... monitor. For different reason (security,
time, scope of project...) it do not add any authentication.
time, scope of project...) it do not add any high secure level authentication.

A mecanism, based on the basic-auth challenge is provided, see the daemon configuration. Be aware this kind of authentication should be enclosed in an HTTPS connexion.

We will see here how to configure a reverse proxy which will be in charge of
user authentication and ssl connections. We will also configure a firewall to
Expand All @@ -22,6 +24,7 @@ To install nginx execute the following command:

Manage authentication
---------------------
Be sure to disable the internal basic-auth by commenting the line ``daemon.basicauth`` in the config file.

To manage authentication we need to create a file gathering the username and
passwords. The following script will help you to generate new users id
Expand Down
1 change: 1 addition & 0 deletions src/etc/rpimonitor/daemon.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#daemon.datastore=/var/lib/rpimonitor
#daemon.logfile=/var/log/rpimonitor.log
#daemon.loglevel=0
#daemon.basicauth=login:password
########################################################################
# SNMP configuration
# Section 'snmpagent' is defining SNMP behavior of rpimonitord.
Expand Down
3 changes: 3 additions & 0 deletions src/etc/rpimonitor/template/raspbian.conf
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ include=/etc/rpimonitor/template/network.conf
#include=/etc/rpimonitor/template/wlan.conf
#include=/etc/rpimonitor/template/dht11.conf
#include=/etc/rpimonitor/template/entropy.conf

#include=/etc/rpimonitor/template/weather.conf

147 changes: 147 additions & 0 deletions src/etc/rpimonitor/template/weather.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
########################################################################
# Display current weather inforamte
# Page: 3
# Information Status Statistics
# - location name - yes - no
# - weather icon - yes - no
# - temperature - yes - yes
# - feels like temp - yes - yes
# - air pressure - yes - yes
# - humidity - yes - yes
# - date of the report - yes - no
# - weather description - yes - no
# - wind speed - yes - yes
# - wind direction - yes - yes
#
# Setup :
# - Request an OpenWeatherMap API key by going to https://www.openweathermap.org > API . It is free.
# Or use your favorite search engine to search for an existing API key (hint : "site:github.com api.openweathermap.org/data appid=" ), replace XXXXXXXXXXXXXXX with your appid.
# - Modify the location by entering zip code and country code, or lat/long on the following Shell command. Also,
# set the appid with the API key. Modify the "lang" parameter and the "units" to fit your need.
# See OpenWeatherMap "Current Weather API" help : https://openweathermap.org/current
#
# To reduce number of API call, the Curl command is triggered only when the minute is a multiple
# of 5. You can adjust this by changine the modulo "%" in the Shell command. The result of the API call
# is stored in /tmp/weather.dat for data processing of the dynamic section.
########################################################################
# Add new pages (number 3)
web.status.3.name=Weather
web.statistics.3.name=Weather

# The API is called in a small Shell script, but only if the RPIMonitor loop is running
# in a multiple of 5 minute. JSON result is stored in a temporary file for further data processing.
dynamic.31.name=weather_curl
# By ZIP CODE and country.
#dynamic.31.source=/bin/bash -c "m=\$(expr \$(date '+%M') % 5); if [[ \$m == 0 ]]; then curl -s \"https://api.openweathermap.org/data/2.5/weather?units=metric&zip=73500,fr&appid=XXXXXXXXXXXXXXX&lang=en\" --output /tmp/weather.dat; fi"
# By latitude, longitude
dynamic.31.source=/bin/bash -c "m=\$(expr \$(date '+%M') % 1); if [[ \$m == 0 ]]; then curl -s \"https://api.openweathermap.org/data/2.5/weather?units=metric&lat=45,2262&lon=6,7441&appid=XXXXXXXXXXXXXXX&lang=en\" --output /tmp/weather.dat; fi"

# Parsing the JSON result of the API. Because RPIMonitor do not support
# JSON natively, if the format of the result change, the parsing will fail.
dynamic.32.name=weather_location
dynamic.32.source=/tmp/weather.dat
dynamic.32.regexp="name":"(.*?)"
dynamic.32.postprocess=use HTML::Entities; use Encode;use utf8; encode_entities(Encode::decode('utf8', $1));

dynamic.33.name=weather_icon
dynamic.33.source=/tmp/weather.dat
dynamic.33.regexp="icon":"(.*?)"

dynamic.34.name=weather_temp
dynamic.34.source=/tmp/weather.dat
dynamic.34.regexp="temp":(.*?),
dynamic.34.rrd=GAUGE
dynamic.34.interval=10

dynamic.35.name=weather_feel
dynamic.35.source=/tmp/weather.dat
dynamic.35.regexp="feels_like":(.*?),
dynamic.35.rrd=GAUGE

dynamic.36.name=weather_pressure
dynamic.36.source=/tmp/weather.dat
dynamic.36.regexp="pressure":(.*?),
dynamic.36.rrd=GAUGE

dynamic.37.name=weather_hum
dynamic.37.source=/tmp/weather.dat
dynamic.37.regexp="humidity":(.*?)}
dynamic.37.postprocess=$1+0
dynamic.37.rrd=GAUGE

dynamic.38.name=weather_date
dynamic.38.source=/tmp/weather.dat
dynamic.38.regexp="dt":(.*?)}
dynamic.38.postprocess=strftime("%Y-%m-%d %H:%M:%S", localtime($1))

dynamic.39.name=weather_desc
dynamic.39.source=/tmp/weather.dat
dynamic.39.regexp="description":"(.*?)"
dynamic.39.postprocess=use HTML::Entities; use Encode;use utf8; encode_entities(Encode::decode('utf8', $1));

# Sometime, the API return also the gust speed, after the "deg", but
# sometime not. Dont rely on it.
dynamic.40.name=weather_wind,weather_wind_deg
dynamic.40.source=/tmp/weather.dat
dynamic.40.regexp="speed":(.*?),"deg":(.*?)[},]
dynamic.40.rrd=GAUGE

dynamic.41.name=weather_sunrise
dynamic.41.source=/tmp/weather.dat
dynamic.41.regexp="sunrise":(.*?),
# If the metric start with a 0, it is interpreted as an Octal value (0730 (1h30) = 472). Use "0 + ..." to convert it to an int
dynamic.41.postprocess=0 + strftime("%H%M", localtime($1))
dynamic.41.rrd=GAUGE

dynamic.42.name=weather_sunset
dynamic.42.source=/tmp/weather.dat
dynamic.42.regexp="sunset":(.*?),
dynamic.42.postprocess=0 + strftime("%H%M", localtime($1))
dynamic.42.rrd=GAUGE


#######################################
# The web page with the current weather
web.status.3.content.1.name=Weather
web.status.3.content.1.icon=cpu_temp.png
web.status.3.content.1.line.1="<b>" + data.weather_location + "</b>, at " + data.weather_date
web.status.3.content.1.line.2="<img src='https://openweathermap.org/img/w/" + data.weather_icon + ".png'/>" + data.weather_desc+", "+data.weather_temp + "&deg;C"
web.status.3.content.1.line.3="feels like " + data.weather_feel + "&deg;C, wind "+data.weather_wind+" m/s, direction " + data.weather_wind_deg + "&deg; <span style='display:inline-block;font-size:1.5em;-moz-transform: rotate("+data.weather_wind_deg+"deg)-ms-transform: rotate("+data.weather_wind_deg+"deg); -o-transform: rotate("+data.weather_wind_deg+"deg); webkit-transform: rotate("+data.weather_wind_deg+"deg); transform: rotate("+data.weather_wind_deg+"deg);'><b>&#8681;</b></span>"
web.status.3.content.1.line.4=JustGageBar("Humidity", "%", 0,data.weather_hum,100, 100,80,[ "#f0f000", "#00f000", "#0000f0" ]) + JustGageBar("Sun light", "HHMM", data.weather_sunrise, (100*new Date().getHours()) + new Date().getMinutes(), data.weather_sunset, 100,80,[ "#A0A0A0", "#ffc300", "#404040" ])
web.status.3.content.1.line.5=" pressure "+data.weather_pressure+ " hPa"


#######################################
# The graphic page with history
web.statistics.3.content.1.name=Temperature
web.statistics.3.content.1.graph.1=weather_temp
web.statistics.3.content.1.graph.2=weather_feel
web.statistics.3.content.1.graph.3=weather_hum
web.statistics.3.content.1.graph_options.tooltipOpts={ content: "<h4>%s</h4> Value: %y.1<br/>At: %x", dateFormat:"%y-%0m-%0d %H:%M" }
web.statistics.3.content.1.ds_graph_options.weather_temp.label=T°
web.statistics.3.content.1.ds_graph_options.weather_feel.label=Feels T°
web.statistics.3.content.1.ds_graph_options.weather_hum.label=Humidity %
web.statistics.3.content.1.ds_graph_options.weather_hum.yaxis=2
web.statistics.3.content.1.ds_graph_options.weather_hum.shadowSize=0
web.statistics.3.content.1.ds_graph_options.weather_hum.lines={ lineWidth:1 }
web.statistics.3.content.1.graph_options.y2axis={ position: "right", min: 0, max: 100 }

web.statistics.3.content.2.name=Wind
web.statistics.3.content.2.graph.1=weather_wind
web.statistics.3.content.2.graph.2=weather_wind_deg
web.statistics.3.content.2.graph_options.tooltipOpts={ content: "<h4>%s</h4> Value: %y.1<br/>At: %x", dateFormat:"%y-%0m-%0d %H:%M" }
web.statistics.3.content.2.ds_graph_options.weather_wind.label=m/s
web.statistics.3.content.2.ds_graph_options.weather_wind_deg.label=° direction
web.statistics.3.content.2.ds_graph_options.weather_wind_deg.yaxis=2
web.statistics.3.content.2.graph_options.y2axis={ position: "right", min: 0, max: 360 }

web.statistics.3.content.3.name=Pressure
web.statistics.3.content.3.graph.1=weather_pressure
web.statistics.3.content.3.graph_options.tooltipOpts={ content: "<h4>%s</h4> Value: %y<br/>At: %x", dateFormat:"%y-%0m-%0d %H:%M" }
web.statistics.3.content.3.ds_graph_options.weather_pressure.label=hPa
web.statistics.3.content.3.graph_options.yaxis={min: 960, max: 1060 }

web.statistics.3.content.4.name=Sun Light
web.statistics.3.content.4.graph.1=weather_sunrise
web.statistics.3.content.4.graph.2=weather_sunset
web.statistics.3.content.4.graph_options.tooltipOpts={ content: "<h4>%s</h4> Value: %y.0<br/>At: %x", dateFormat:"%y-%0m-%0d %H:%M" }
56 changes: 54 additions & 2 deletions src/usr/bin/rpimonitord
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ use IO::Handle;
use HTTP::Daemon;
use HTTP::Status;
use JSON -convert_blessed_universally;
use MIME::Base64;
#use Data::Dumper;

sub new
Expand Down Expand Up @@ -405,6 +406,38 @@ sub SendError
return 1;
}

sub SendChallenge
{
my $this=shift;
$this->Debug(2,"Challenge auth");
my $response = HTTP::Response->new(
401 => 'Authorization Required',
[ 'WWW-Authenticate' => 'Basic realm="Identity"',
'Content-Type' => "text/plain"],
"Authentication needed"
);
$this->{'connection'}->send_response($response);
$this->{'connection'}->close();
}

sub CheckChallenge
{
my $this = shift;
my $request = shift;
my $auth_received = shift;
my $configuration = shift;
my $auth = "Basic ".encode_base64($configuration->{'daemon'}->{'basicauth'}, '');
if ($auth eq $auth_received)
{
return 1;
}
else
{
$this->Debug(1, "Bad authent provided");
return 0;
}
}

sub DoGET
{
my $this = shift;
Expand Down Expand Up @@ -586,8 +619,27 @@ sub Run
for (;;){
while ( $this->{'connection'} = $this->{'server'}->accept) {
while (my $request = $this->{'connection'}->get_request) {
my $method = "Do".$request->method();
$this->can($method) and $this->$method($request,$configuration);
if ($configuration->{'daemon'}->{'basicauth'}) {
# Basic authen enabled
if ($request->header( 'Authorization' )) {
my $auth = $request->header( 'Authorization' );
if ($this->CheckChallenge($request, $auth, $configuration)) {
my $method = "Do".$request->method();
$this->can($method) and $this->$method($request,$configuration);
}
else {
$this->SendChallenge();
}
}
else {
$this->SendChallenge();
}
}
else {
# Basic auth not enabled
my $method = "Do".$request->method();
$this->can($method) and $this->$method($request,$configuration);
}
}
$this->{'connection'}->close;
undef($this->{'connection'});
Expand Down