diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1cb6d806ea86b5721390e04908cde3c30bf02410 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +*.iml +config/*.conf +config/*.html +config/*.users diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..c7bb40d88a15c96f9869f69f2d5ce9493f86468f --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,49 @@ +image: docker:latest + +services: + - docker:dind + +before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + +# +# TESTS +# + +lint:dockerfile: + stage: test + image: hadolint/hadolint:latest-debian + before_script: + - mkdir ~/.config + - cp ./.hadolint.yaml ~/.config/hadolint.yaml + script: + - hadolint ./context/Dockerfile + +lint:shell-script: + stage: test + image: koalaman/shellcheck-alpine:latest + before_script: + - shellcheck -V + script: + - shellcheck ./context/entrypoint + +# +# IMAGE BUILDS/PUSHES +# + +build:master: + stage: deploy + script: + - docker build --pull -t "$CI_REGISTRY_IMAGE:latest" ./context/ +# - docker push "$CI_REGISTRY_IMAGE:latest" + only: + - master + when: manual + +build:dev: + stage: deploy + script: + - docker build --pull -t "$CI_REGISTRY_IMAGE:dev" ./context/ +# - docker push "$CI_REGISTRY_IMAGE:dev" + except: + - master diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8f7e23e459df52ebbd66d768be22912a79d80eca --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,2 @@ +ignored: + - DL3008 diff --git a/README.md b/README.md index 801bae73fd97ba8a894f2492d64a10e2920361dc..44e4d4e0ccdd6565c69704450eb23ee73bda1fe2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ # nut-upsd +Fully-customizable `nut` instance in a container. Many images exist, but they are +feature-limited due to how `nut`'s configuration is handled. This image aims to +provide all `nut` options without making too many compromises. + +## Usage + +1. Create necessary configurations in `./config`; `.conf`, `.html`, and `.users` +files are supported. **`./config` includes sample configurations.** +1. `docker-compose up -d` +1. If `MODE` is set to `netserver`, `nut` will be available on the container's +port `3493`. Confirm using `telnet`: + ```bash + $ telnet [CONTAINER IP] 3493 + Trying [CONTAINER IP]... + Connected to [CONTAINER IP]. + Escape character is '^]'. + + $ LIST UPS + BEGIN LIST UPS + UPS test "Back-UPS XS 1500 Test" + END LIST UPS + ``` diff --git a/config/hosts.conf.sample b/config/hosts.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..24beb829560e2c82d076284ae9d925fa9e74aef1 --- /dev/null +++ b/config/hosts.conf.sample @@ -0,0 +1,29 @@ +# Network UPS Tools: example hosts.conf +# +# This file is used to control the CGI programs. If you have not +# installed them, you may safely ignore or delete this file. +# +# ----------------------------------------------------------------------- +# +# upsstats will use the list of MONITOR entries when displaying the +# default template (upsstats.html). The "FOREACHUPS" directive in the +# template will use this file to find systems running upsd. +# +# upsstats and upsimage also use this file to determine if a host may be +# monitored. This keeps evil people from using your system to annoy +# others with unintended queries. +# +# upsset presents a list of systems that may be viewed and controlled +# using this file. +# +# ----------------------------------------------------------------------- +# +# Usage: list systems running upsd that you want to monitor +# +# MONITOR <system> "<host description>" +# +# Examples: +# +# MONITOR myups@localhost "Local UPS" +# MONITOR su2200@10.64.1.1 "Finance department" +# MONITOR matrix@shs-server.example.edu "Sierra High School data room #1" diff --git a/config/nut.conf.sample b/config/nut.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..b0c2685c7d7d1498bf905226292647e7348298b1 --- /dev/null +++ b/config/nut.conf.sample @@ -0,0 +1,32 @@ +# Network UPS Tools: example nut.conf +# +############################################################################## +# General section +############################################################################## +# The MODE determines which part of the NUT is to be started, and which +# configuration files must be modified. +# +# This file try to standardize the various files being found in the field, like +# /etc/default/nut on Debian based systems, /etc/sysconfig/ups on RedHat based +# systems, ... Distribution's init script should source this file to see which +# component(s) has to be started. +# +# The values of MODE can be: +# - none: NUT is not configured, or use the Integrated Power Management, or use +# some external system to startup NUT components. So nothing is to be started. +# - standalone: This mode address a local only configuration, with 1 UPS +# protecting the local system. This implies to start the 3 NUT layers (driver, +# upsd and upsmon) and the matching configuration files. This mode can also +# address UPS redundancy. +# - netserver: same as for the standalone configuration, but also need +# some more network access controls (firewall, tcp-wrappers) and possibly a +# specific LISTEN directive in upsd.conf. +# Since this MODE is opened to the network, a special care should be applied +# to security concerns. +# - netclient: this mode only requires upsmon. +# +# IMPORTANT NOTE: +# This file is intended to be sourced by shell scripts. +# You MUST NOT use spaces around the equal sign! + +MODE=none diff --git a/config/ups.conf.sample b/config/ups.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..f7de6db5e3158505aaf53c02db68c8b94564cd02 --- /dev/null +++ b/config/ups.conf.sample @@ -0,0 +1,141 @@ +# Network UPS Tools: example ups.conf +# +# --- SECURITY NOTE --- +# +# If you use snmp-ups and set a community string in here, you +# will have to secure this file to keep other users from obtaining +# that string. It needs to be readable by upsdrvctl and any drivers, +# and by upsd. +# +# --- +# +# This is where you configure all the UPSes that this system will be +# monitoring directly. These are usually attached to serial ports, but +# USB devices and SNMP devices are also supported. +# +# This file is used by upsdrvctl to start and stop your driver(s), and +# is also used by upsd to determine which drivers to monitor. The +# drivers themselves also read this file for configuration directives. +# +# The general form is: +# +# [upsname] +# driver = <drivername> +# port = <portname> +# < any other directives here > +# +# The section header ([upsname]) can be just about anything as long as +# it is a single word inside brackets. upsd uses this to uniquely +# identify a UPS on this system. +# +# If you have a UPS called snoopy, your section header would be "[snoopy]". +# On a system called "doghouse", the line in your upsmon.conf to monitor +# it would look something like this: +# +# MONITOR snoopy@doghouse 1 upsmonuser mypassword master +# +# It might look like this if monitoring in slave mode: +# +# MONITOR snoopy@doghouse 1 upsmonuser mypassword slave +# +# Configuration directives +# ------------------------ +# +# These directives are used by upsdrvctl only and should be specified outside +# of a driver definition: +# +# maxretry: Optional. Specify the number of attempts to start the driver(s), +# in case of failure, before giving up. A delay of 'retrydelay' is +# inserted between each attempt. Caution should be taken when using +# this option, since it can impact the time taken by your system to +# start. +# +# The built-in default is 1 attempt. +# +# retrydelay: Optional. Specify the delay between each restart attempt of the +# driver(s), as specified by 'maxretry'. Caution should be taken +# when using this option, since it can impact the time taken by your +# system to start. +# +# The default is 5 seconds. +# + +# Set maxretry to 3 by default, this should mitigate race with slow devices: +maxretry = 3 + +# These directives are common to all drivers that support ups.conf: +# +# driver: REQUIRED. Specify the program to run to talk to this UPS. +# apcsmart, bestups, and sec are some examples. +# +# port: REQUIRED. The serial port where your UPS is connected. +# /dev/ttyS0 is usually the first port on Linux boxes, for example. +# +# sdorder: optional. When you have multiple UPSes on your system, you +# usually need to turn them off in a certain order. upsdrvctl +# shuts down all the 0s, then the 1s, 2s, and so on. To exclude +# a UPS from the shutdown sequence, set this to -1. +# +# The default value for this parameter is 0. +# +# nolock: optional, and not recommended for use in this file. +# +# If you put nolock in here, the driver will not lock the +# serial port every time it starts. This may allow other +# processes to seize the port if you start more than one by +# mistake. +# +# This is only intended to be used on systems where locking +# absolutely must be disabled for the software to work. +# +# maxstartdelay: optional. This can be set as a global variable +# above your first UPS definition and it can also be +# set in a UPS section. This value controls how long +# upsdrvctl will wait for the driver to finish starting. +# This keeps your system from getting stuck due to a +# broken driver or UPS. +# +# The default is 45 seconds. +# +# synchronous: optional. The driver work by default in asynchronous +# mode (i.e *synchronous=no*). This means that all data +# are pushed by the driver on the communication socket to +# upsd (Unix socket on Unix, Named pipe on Windows) without +# waiting for these data to be actually consumed. With +# some HW, such as ePDUs, that can produce a lot of data, +# asynchronous mode may cause some congestion, resulting in +# the socket to be full, and the driver to appear as not +# connected. By enabling the 'synchronous' flag +# (value = 'yes'), the driver will wait for data to be +# consumed by upsd, prior to publishing more. This can be +# enabled either globally or per driver. +# +# The default is 'no' (i.e. asynchronous mode) for backward +# compatibility of the driver behavior. +# +# Anything else is passed through to the hardware-specific part of +# the driver. +# +# Examples +# -------- +# +# A simple example for a UPS called "powerpal" that uses the blazer_ser +# driver on /dev/ttyS0 is: +# +# [powerpal] +# driver = blazer_ser +# port = /dev/ttyS0 +# desc = "Web server" +# +# If your UPS driver requires additional settings, you can specify them +# here. For example, if it supports a setting of "1234" for the +# variable "cable", it would look like this: +# +# [myups] +# driver = mydriver +# port = /dev/ttyS1 +# cable = 1234 +# desc = "Something descriptive" +# +# To find out if your driver supports any extra settings, start it with +# the -h option and/or read the driver's documentation. diff --git a/config/upsd.conf.sample b/config/upsd.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..5cb94062ce5f090f5dd368f5de8e6f84e93b07ed --- /dev/null +++ b/config/upsd.conf.sample @@ -0,0 +1,121 @@ +# Network UPS Tools: example upsd configuration file +# +# This file contains access control data, you should keep it secure. +# +# It should only be readable by the user that upsd becomes. See the FAQ. +# +# Each entry below provides usage and default value. +# +# For more information, refer to upsd.conf manual page. + +# ======================================================================= +# MAXAGE <seconds> +# MAXAGE 15 +# +# This defaults to 15 seconds. After a UPS driver has stopped updating +# the data for this many seconds, upsd marks it stale and stops making +# that information available to clients. After all, the only thing worse +# than no data is bad data. +# +# You should only use this if your driver has difficulties keeping +# the data fresh within the normal 15 second interval. Watch the syslog +# for notifications from upsd about staleness. + +# ======================================================================= +# TRACKINGDELAY <seconds> +# TRACKINGDELAY 3600 +# +# This defaults to 1 hour. When instant commands and variables setting status +# tracking is enabled, status execution information are kept during this +# amount of time, and then cleaned up. + +# ======================================================================= +# STATEPATH <path> +# STATEPATH /var/run/nut +# +# Tell upsd to look for the driver state sockets in 'path' rather +# than the default that was compiled into the program. + +# ======================================================================= +# LISTEN <IP address or name> [<port>] +# LISTEN 127.0.0.1 3493 +# LISTEN ::1 3493 +# LISTEN myhostname 83493 +# LISTEN myhostname.mydomain +# +# This defaults to the localhost listening addresses and port 3493. +# In case of IP v4 or v6 disabled kernel, only the available one will be used. +# +# You may specify each interface IP address or name that you want upsd to +# listen on for connections, optionally with a port number. +# +# You may need this if you have multiple interfaces on your machine and +# you don't want upsd to listen to all interfaces (for instance on a +# firewall, you may not want to listen to the external interface). +# +# This will only be read at startup of upsd. If you make changes here, +# you'll need to restart upsd, reload will have no effect. + +# ======================================================================= +# MAXCONN <connections> +# MAXCONN 1024 +# +# This defaults to maximum number allowed on your system. Each UPS, each +# LISTEN address and each client count as one connection. If the server +# runs out of connections, it will no longer accept new incoming client +# connections. Only set this if you know exactly what you're doing. + +# ======================================================================= +# CERTFILE <certificate file> +# CERTFILE /usr/local/ups/etc/upsd.pem +# +# When compiled with SSL support with OpenSSL backend, +# you can enter the certificate file here. +# The certificates must be in PEM format and must be sorted starting with +# the subject's certificate (server certificate), followed by intermediate +# CA certificates (if applicable_ and the highest level (root) CA. It should +# end with the server key. See 'docs/security.txt' or the Security chapter of +# NUT user manual for more information on the SSL support in NUT. +# +# See 'docs/security.txt' or the Security chapter of NUT user manual +# for more information on the SSL support in NUT. + +# ======================================================================= +# CERTPATH <certificate file or directory> +# CERTPATH /usr/local/ups/etc/cert/upsd +# +# When compiled with SSL support with NSS backend, +# you can enter the certificate path here. +# Certificates are stored in a dedicated database (split into 3 files). +# Specify the path of the database directory. +# +# See 'docs/security.txt' or the Security chapter of NUT user manual +# for more information on the SSL support in NUT. + +# ======================================================================= +# CERTIDENT <certificate name> <database password> +# CERTIDENT "my nut server" "MyPasSw0rD" +# +# When compiled with SSL support with NSS backend, +# you can specify the certificate name to retrieve from database to +# authenticate itself and the password +# required to access certificate related private key. +# +# See 'docs/security.txt' or the Security chapter of NUT user manual +# for more information on the SSL support in NUT. + +# ======================================================================= +# CERTREQUEST <certificate request level> +# CERTREQUEST REQUIRE +# +# When compiled with SSL support with NSS backend and client certificate +# validation (disabled by default, see 'docs/security.txt'), +# you can specify if upsd requests or requires client's' certificates. +# Possible values are : +# - 0 to not request to clients to provide any certificate +# - 1 to require to all clients a certificate +# - 2 to require to all clients a valid certificate +# +# See 'docs/security.txt' or the Security chapter of NUT user manual +# for more information on the SSL support in NUT. + diff --git a/config/upsd.users.sample b/config/upsd.users.sample new file mode 100644 index 0000000000000000000000000000000000000000..15a0206a32a8fe8a0801a401ac31f2d4919c6deb --- /dev/null +++ b/config/upsd.users.sample @@ -0,0 +1,75 @@ +# Network UPS Tools: Example upsd.users +# +# This file sets the permissions for upsd - the UPS network daemon. +# Users are defined here, are given passwords, and their privileges are +# controlled here too. Since this file will contain passwords, keep it +# secure, with only enough permissions for upsd to read it. + +# -------------------------------------------------------------------------- + +# Each user gets a section. To start a section, put the username in +# brackets on a line by itself. To set something for that user, specify +# it under that section heading. The username is case-sensitive, so +# admin and AdMiN are two different users. +# +# Possible settings: +# +# password: The user's password. This is case-sensitive. +# +# -------------------------------------------------------------------------- +# +# actions: Let the user do certain things with upsd. +# +# Valid actions are: +# +# SET - change the value of certain variables in the UPS +# FSD - set the "forced shutdown" flag in the UPS +# +# -------------------------------------------------------------------------- +# +# instcmds: Let the user initiate specific instant commands. Use "ALL" +# to grant all commands automatically. There are many possible +# commands, so use 'upscmd -l' to see what your hardware supports. Here +# are a few examples: +# +# test.panel.start - Start a front panel test +# test.battery.start - Start battery test +# test.battery.stop - Stop battery test +# calibrate.start - Start calibration +# calibrate.stop - Stop calibration +# +# -------------------------------------------------------------------------- +# +# Example: +# +# [admin] +# password = mypass +# actions = SET +# instcmds = ALL +# + +# +# --- Configuring for a user who can execute tests only +# +# [testuser] +# password = pass +# instcmds = test.battery.start +# instcmds = test.battery.stop + +# +# --- Configuring for upsmon +# +# To add a user for your upsmon, use this example: +# +# [upsmon] +# password = pass +# upsmon master +# or +# upsmon slave +# +# The matching MONITOR line in your upsmon.conf would look like this: +# +# MONITOR myups@localhost 1 upsmon pass master (or slave) +# +# See comments in the upsmon.conf(.sample) file for details about this +# keyword and the difference of NUT slave and master systems. diff --git a/config/upsset.conf.sample b/config/upsset.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..921a20aa528d463fa54fc49468bf194b12fa8a5c --- /dev/null +++ b/config/upsset.conf.sample @@ -0,0 +1,36 @@ +# Network UPS Tools - upsset.conf sample file +# +# This file is provided to ensure that you do not expose your upsd server +# to the world upon installing the CGI programs. Specifically, it keeps +# the upsset.cgi program from running until you have assured it that you +# have secured your web server's CGI directory. +# +# By default, your web server will probably let anyone access upsset.cgi +# once it is installed. This means that anyone could attempt to crack +# upsd logins since they would appear to be coming from your web server, +# rather than the outside world, slipping through any ACL/ACCESS definitions. +# +# For this reason, you *MUST* first secure your CGI programs before +# enabling upsset in this configuration file. If you can't do this in +# your web server, then you should *not* run this program. +# +# For Apache, the .htaccess file can be used in the directory with the +# programs. You'll need something like this: +# +# <Files upsset.cgi> +# deny from all +# allow from your.network.addresses +# </Files> +# +# You will probably have to set "AllowOverride Limit" for this directory in +# your server-level configuration file as well. +# +# If this doesn't make sense, then stop reading and leave this program alone. +# +# Assuming you have all this done (and it works), then you may uncomment +# the line below and start using upsset.cgi through your web browser. +# + +### +### I_HAVE_SECURED_MY_CGI_DIRECTORY +### diff --git a/context/Dockerfile b/context/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..221b3cc503e9e0174ecc38239e0dc3d7459e4635 --- /dev/null +++ b/context/Dockerfile @@ -0,0 +1,18 @@ +FROM debian:buster-slim + +LABEL maintainer="ethitter" +LABEL version="1.0" + +RUN echo "deb http://security.debian.org/ buster/updates main" >> /etc/apt/sources.list + +RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + nut \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +EXPOSE 3493 + +COPY entrypoint /usr/local/bin/ +ENTRYPOINT ["entrypoint"] diff --git a/context/entrypoint b/context/entrypoint new file mode 100755 index 0000000000000000000000000000000000000000..e61934990b663bb1b896af0eaa801f72e1f0eafe --- /dev/null +++ b/context/entrypoint @@ -0,0 +1,21 @@ +#!/bin/bash + +CONFIG_SOURCE="/opt/nut-config" +export CONFIG_SOURCE + +CONFIG_DEST="/etc/nut" +export CONFIG_DEST + +echo "Copying configurations from ${CONFIG_SOURCE} to ${CONFIG_DEST}..." + +find $CONFIG_SOURCE -name "*.conf" -exec cp '{}' $CONFIG_DEST \; +find $CONFIG_SOURCE -name "*.html" -exec cp '{}' $CONFIG_DEST \; +find $CONFIG_SOURCE -name "*.users" -exec cp '{}' $CONFIG_DEST \; + +echo "Restarting services..." + +upsdrvctl start +/etc/init.d/nut-server restart + +echo "Ready on port 3493" +tail -F /dev/null diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ec0ea1daf2af1b2872479b345a5111bf23f58643 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.7" +services: + nut: + image: nut-upsd:latest + build: + context: ./context + ports: + - 3493:3493 + restart: unless-stopped + volumes: + - ./config/:/opt/nut-config