ECE 110/120 Honors Lab Section : OctoPrint & Shibboleth Setup

The following guide is designed to work with OctoPi version 0.18.0. Other versions may not be guaranteed to work. By default, OctoPi comes with with a python-based backend with haproxy running in the front to serve HTTP requests. We are going to replace haproxy with nginx, then use the nginx-http-shibboleth module to authenticate requests before passing them onto the python backend.

All of the commands below are to be run as root. If you are logged in as pi, make sure to execute "sudo bash" to switch to root.

Shibboleth SP Setup

We begin by setting up a Shibboleth Service Provider (Shibboleth SP). Install Shibboleth on the Pi:

apt-get install shibboleth-sp-utils


Next, we need to generate the key pairs used by Shibboleth SP. The -h switch specifies the hostname, while the -e switch specifies the Shibboleth SP entity ID (just the hostname with a https:// in front and /shibboleth at the end).

shib-keygen -f -h ece110-3d-01.ece.illinois.edu -e https://ece110-3d-01.ece.illinois.edu/shibboleth -y 20


We can now modify /etc/shibboleth/shibboleth2.xml to the following. Make sure to update the hostname and entity ID on lines 11 and 31, respectively. The rules on lines 17 and 18 allows us to restrict the service to specific AD groups. As many or as few AD groups can be added. If fewer than two AD groups are being used, then the <OR></OR> needs to be removed. AD groups are sent to our SP through the "member" attribute as semicolon separated values, e.g.: "group1;group2;group3". This particular regex is designed to match groups separated by semicolons, the start of a line, or the end of the line.

<SPConfig xmlns="urn:mace:shibboleth:3.0:native:sp:config"
    xmlns:conf="urn:mace:shibboleth:3.0:native:sp:config"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
    clockSkew="180">


    <RequestMapper type="XML">
        <RequestMap>
            <Host name="ece110-3d-01.ece.illinois.edu"
                    authType="shibboleth"
                    requireSession="true"
                    redirectToSSL="443">
                <AccessControl>
                    <OR>
                        <RuleRegex require="member">(^|;)urn:mace:uiuc\.edu:urbana:engineering:usersandgroups:instructional:ece:ece110:ece-110-3d-print-admin($|;)</RuleRegex>
                        <RuleRegex require="member">(^|;)urn:mace:uiuc\.edu:urbana:engineering:usersandgroups:instructional:ece:ece110:ece-110-3d-print-user($|;)</RuleRegex>
                    </OR>
                </AccessControl>
            </Host>
        </RequestMap>
    </RequestMapper>

    <!--
         Put your SP's unique entity ID in the below entityID attribute
         Optional: set attributes you want assigned to remote_user,
         space-separated, in REMOTE_USER. The first attribute in
         your list that is sent by the IDP will be used.
     -->
    <ApplicationDefaults entityID="https://ece110-3d-01.ece.illinois.edu/shibboleth"
        REMOTE_USER="eppn persistent-id targeted-id">
        <!--
             Lifetime is the max length of a session in seconds.
             Timeout is the inactivity time-out in seconds.
        -->
        <Sessions lifetime="28800" timeout="3600" relayState="ss:mem"
            checkAddress="false" handlerSSL="true" cookieProps="https">
            <!--
             Below setting will use Urbana campus
             IDP only.
            -->
            <SSO entityID="urn:mace:incommon:uiuc.edu">
                SAML2 SAML1
            </SSO>
            <Logout>SAML2 Local</Logout>
            <Handler type="MetadataGenerator" Location="/Metadata" signing="false"/>
            <Handler type="Status" Location="/Status" acl="127.0.0.1 ::1"/>
            <Handler type="Session" Location="/Session" showAttributeValues="false"/>
            <Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
        </Sessions>
        <!--
             Set the email address that's displayed in error
             templates. Also set the help URL.
        -->
        <Errors supportContact="cjsmith0@illinois.edu"
            helpLocation="/about.html"
            styleSheet="/shibboleth-sp/main.css"/>
        <!-- Configuration to consume I-Trust metadata. -->
        <MetadataProvider type="XML" url="https://md.itrust.illinois.edu/itrust-metadata/itrust-metadata.xml"
                backingFilePath="itrust-metadata.xml" reloadInterval="7200">
            <MetadataFilter type="RequireValidUntil" maxValidityInterval="2419200"/>
            <MetadataFilter type="Signature" certificate="itrust.pem"/>
            <!-- Limit users to those from Urbana's IDP -->
            <MetadataFilter type="Whitelist">
            <Include>urn:mace:incommon:uiuc.edu</Include>
            </MetadataFilter>
        </MetadataProvider>
        <AttributeExtractor type="XML" validate="true" reloadChanges="false"
            path="attribute-map.xml"/>
        <AttributeResolver type="Query" subjectMatch="true"/>
        <AttributeFilter type="XML" validate="true" path="attribute-policy.xml"/>
        <CredentialResolver type="File" key="sp-key.pem" certificate="sp-cert.pem"/>
    </ApplicationDefaults>
    <SecurityPolicyProvider type="XML" validate="true" path="security-policy.xml"/>
    <ProtocolProvider type="XML" validate="true" reloadChanges="false" path="protocols.xml"/>
</SPConfig>


We then need to modify /etc/shibboleth/attribute-map.xml to the following:

<Attributes xmlns="urn:mace:shibboleth:2.0:attribute-map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <!-- eduPersonPrincipalName -->
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" id="eppn">
        <AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
    </Attribute>

    <!-- Attributes defining user's name -->
    <Attribute name="urn:oid:2.16.840.1.113730.3.1.241" id="displayName"/>

    <!-- isMemberOf -->
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.5.1.1" id="member"/>

    <!-- scopedAffiliation -->
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" id="scopedAffiliation"/>

    <!-- entitlement -->
    <Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.7" id="entitlement"/>

</Attributes>


Now, download the iTrust certificate:

wget https://md.itrust.illinois.edu/itrust-certs/itrust.pem -O /etc/shibboleth/itrust.pem


Shibboleth SP is now configured. We can enable it and (re)start it:

systemctl enable shibd
systemctl restart shibd


Our Shibboleth SP now must be registered with the University iTrust before it can begin servicing login requests. Registration can be requested at https://itrust.illinois.edu/federationregistry/membership/serviceprovider/create.


  • Enter the corresponding information into the "Service Provider Description" section.
  • For "SAML configuration," use the "Easy registration using defaults" option and enter the entity ID from earlier. It should auto-fill the remainder of the advanced configuration.
  • Paste the PEM certificate we generated earlier from /etc/shibboleth/sp-cert.pem in the "Public Key Certificate" section.
  • The following attributes need to be requested (check both "requested" and "required"):
    • eduPersonPrincipalName (oid:1.3.6.1.4.1.5923.1.1.1.6) - Reason: Logging interactions with the service
    • isMemberOf (oid:1.3.6.1.4.1.5923.1.5.1.1) - Reason: Verify authorization to use the service
  • Submit the request to add the SP to the iTrust. The request now needs to be approved by iTrust administrators. This can take anywhere from a few days to a few weeks.


Supervisor Setup

Shibboleth SP was originally designed to work with Apache. Since we are using it with nginx, we must setup supervisor for shibauthorizer and shibresponder.


Begin by installing supervisor:

apt install supervisor


Create a new file /etc/supervisor/conf.d/shibboleth.conf:

[fcgi-program:shibauthorizer]
command=/usr/lib/arm-linux-gnueabihf/shibboleth/shibauthorizer
socket=unix:///opt/shibboleth/shibauthorizer.sock
socket_owner=www-data:www-data
socket_mode=0600
user=_shibd
stdout_logfile=/var/log/supervisor/shibauthorizer.log
stderr_logfile=/var/log/supervisor/shibauthorizer.error.log

[fcgi-program:shibresponder]
command=/usr/lib/arm-linux-gnueabihf/shibboleth/shibresponder
socket=unix:///opt/shibboleth/shibresponder.sock
socket_owner=www-data:www-data
socket_mode=0600
user=_shibd
stdout_logfile=/var/log/supervisor/shibresponder.log
stderr_logfile=/var/log/supervisor/shibresponder.error.log


Supervisor is now configured. We can enable and (re)start it:

systemctl restart supervisor
systemctl enable supervisor


SSL Configuration

Shibboleth SP requires that we set up SSL on the server. However, because of the server's closed firewall group within the university network, we need to request a certificate from the university rather than using something like LetsEncrypt. A caveat to this is that the certificate must be manually renewed once every year and reinstalled into the server.


Begin by generating a certificate request (CSR). The common name should be set appropriately to the server's hostname. The extra attributes (challenge password and optional company name) can be left blank.

mkdir -p /etc/nginx/ssl
openssl req -newkey rsa:2048 -nodes -keyout /etc/nginx/ssl/server.key -out /etc/nginx/ssl/server.csr


A SSL certificate now needs to be requested using the form http://go.illinois.edu/sslrequest. The CSR can be found at /etc/nginx/ssl/server.csr. Once the certificate has been issued, place it in /etc/nginx/ssl/server.crt.


After installing the certificate, make sure the file permissions are properly set:

chown -R root:root /etc/nginx/ssl
chmod -R go-rwx /etc/nginx/ssl


Nginx Installation & Configuration

We will be using nginx with the nginx-http-shibboleth module as a reverse proxy. This module isn't included with nginx by default, nor is it an installable package, so we must compile nginx from the source.


Begin by downloading and unzipping the nginx-http-shibboleth module:

apt install zip
cd /tmp
wget https://github.com/nginx-shib/nginx-http-shibboleth/archive/refs/tags/v2.0.1.zip -O v2.0.1.zip
unzip v2.0.1.zip


Download, compile, and install pcre (another dependency required by nginx):

cd /tmp
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.44.tar.gz
tar -zxf pcre-8.44.tar.gz
cd pcre-8.44
./configure
make
sudo make install


Download, compile, and install nginx:

cd /tmp
wget https://nginx.org/download/nginx-1.18.0.tar.gz
tar zxf nginx-1.18.0.tar.gz
cd nginx-1.18.0
./configure \
    --sbin-path=/usr/local/nginx/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --pid-path=/usr/local/nginx/nginx.pid \
    --with-http_ssl_module \
    --with-stream \
    --with-mail=dynamic \
    --add-module=/tmp/nginx-http-shibboleth-2.0.1/
make
sudo make install


Now, we can create a startup script for nginx. Edit the file /etc/systemd/system/nginx.service (or create it if it doesn't already exist):

[Unit]
Description=OctoPrint NGINX
Documentation=man:nginx(8)
After=network.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/local/nginx/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/local/nginx/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/local/nginx/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed
Nice=10

[Install]
WantedBy=multi-user.target


Configure nginx by placing the following in /etc/nginx/nginx.conf. Make sure to modify the hostname configuration on lines 34, 39, and 44.

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections  1024;
}


http {
    log_format  main  '$remote_addr - $shib_eppn [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    error_log   /var/log/nginx/error.log;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen                      0.0.0.0:80 default_server;
        server_name                 ece110-3d-01.ece.illinois.edu;

        server_tokens               off;
        charset                     utf-8;

        rewrite ^ https://ece110-3d-01.ece.illinois.edu$request_uri? redirect;
    }

    server {
        listen                      0.0.0.0:443 ssl default_server;
        server_name                 ece110-3d-01.ece.illinois.edu;

        server_tokens               off;
        charset                     utf-8;

        add_header                  X-Frame-Options DENY;
        add_header                  X-Content-Type-Options nosniff;
        add_header                  X-XSS-Protection "1; mode=block";

        ssl_certificate             /etc/nginx/ssl/server.crt;
        ssl_certificate_key         /etc/nginx/ssl/server.key;
        ssl_session_timeout         5m;
        ssl_session_cache           shared:TLS:50m;
        ssl_session_tickets         off;

        ssl_protocols               TLSv1.3 TLSv1.2;
        ssl_ciphers                 EECDH+AESGCM:EDH+AESGCM;
        ssl_prefer_server_ciphers   on;
        ssl_ecdh_curve              secp384r1;

        gzip                        on;
        gzip_disable                "msie6";

        gzip_vary                   on;
        gzip_proxied                any;
        gzip_comp_level             6;
        gzip_buffers                16 8k;
        gzip_http_version           1.1;
        gzip_types                  text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        root                        /var/www/html;
        index                       index.html index.htm;

        location / {
            error_page 403 /403.html;
            shib_request /shibauthorizer;

            shib_request_set $shib_eppn $upstream_http_variable_eppn;
            fastcgi_param Shib-EPPN $shib_eppn;

            proxy_pass_request_headers on;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host;
            proxy_set_header REMOTE_USER $shib_eppn;
            proxy_cache_bypass $http_upgrade;
            proxy_pass http://localhost:5000;
        }

        location /webcam {
            rewrite /webcam/(.*) /$1  break;
            proxy_pass_request_headers on;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_pass http://localhost:8080;
        }

        location /webcam_hls {
            proxy_pass_request_headers on;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_pass http://localhost:28126;
        }

        location = /403.html {
            internal;
            root /usr/share/shibboleth;
        }

        location = /shibauthorizer {
            internal;

            # A huge buffer is needed for headers on this shib authorizer endpoint since all AD groups
            # are passed as HTTP headers. Not increasing the header size will result in a 502 at this
            # endpoint due to buffer exhaustion.
            fastcgi_buffer_size     256k;
            fastcgi_buffers         8 128k;
            proxy_buffer_size       256k;
            proxy_buffers           8 128k;

            include fastcgi_params;
            fastcgi_pass unix:/opt/shibboleth/shibauthorizer.sock;
        }

        location /Shibboleth.sso {
            include fastcgi_params;
            fastcgi_pass unix:/opt/shibboleth/shibresponder.sock;
        }

    }
}


Add a custom 403 error page under /usr/share/shibboleth/403.html. This page will be shown to users when they are not within the AD group(s) we've previously defined in /etc/shibboleth/shibboleth2.xml.

<!DOCTYPE html>
<html>
<head><title>Access Denied</title></head>
<body>
<h1>Access Denied</h1>
<p>Access for your account is not enabled for this application. Please contact course staff to request access.</p>
</body>
</html>


Reload systemctl, then stop/disable haproxy and enable nginx:

systemctl daemon-reload
systemctl stop haproxy
systemctl disable haproxy
systemctl restart nginx
systemctl enable nginx


OctoPrint Configuration

In our nginx proxy config, we added a header REMOTE_USER that passes the EPPN received from shibboleth to the OctoPrint server. We now need to configure OctoPrint to automatically authenticate users when it sees this header.


Edit the file /home/pi/.octoprint/config.yaml and add the following lines under the accessControl section:

accessControl:
    addRemoteUsers: true
    remoteUserHeader: REMOTE_USER
    trustRemoteUser: true


Restart the OctoPrint server for changes to take effect:

systemctl restart octoprint


Miscellaneous Notes

Maintenance Events

  • The Shibboleth certificate located at /etc/shibboleth/sp-cert.pem and /etc/shibboleth/sp-cert.key needs to be renewed every ~20 years.
  • The Shibboleth iTrust certificate at /etc/shibboleth/itrust.pem expires on November 10, 2032 and will need to be updated.
  • The SSL certificate at /etc/nginx/ssl/ needs to be renewed yearly.

OctoPrint Plugins

The following 3rd party plugins have been installed in OctoPrint:

  • Exclude Region
  • Fullscreen Plugin
  • M73 ETA Override
  • Navbar Temperature Plugin
  • PrintJobHistory

Security

  • OctoPrint will show the username (EPPN) of who started the current print job.
  • There is currently no way of telling who stopped a print job through the web interface. However, if detected soon enough, you could look at nginx access logs located at /var/log/nginx access.log (it might be gzipped if some time has passed). Nginx will log the EPPN associated with each HTTP request, so you could sort through those to see who might have made a suspicious HTTP POST to the API.
  • PrintJobHistory is set to record the EPPN associated with each print job and take a picture (using the camera) once the print has completed (or has been cancelled). However, users can currently choose to delete the record. An issue is currently open with the author on GitHub - https://github.com/OllisGit/OctoPrint-PrintJobHistory/issues/33
  • The files uploaded to OctoPrint have no users associated with them. Anyone can view, download, or delete another person's files.