Showing posts with label httpd. Show all posts
Showing posts with label httpd. Show all posts

Friday, July 24, 2026

Docker, Apache HTTPd, and Perl with DBI

Okay.  I swore about crap all the day long, trying to get CGI's to write to a database using the default httpd:2.4 docker image.

NOTHING worked.

Until I found some obscure post referencing extending an image, I was at a loss.  Then, with the reference to "extending an image", I knew what to search for, and finally got it.  Here's what to do.  You can "create" a new image using a "build", e.g. create a Dockerfile containing :

    # Sample Dockerfile for extending a container
    FROM httpd:latest
    RUN apt-get update && apt-get install -y libdbd-mysql-perl
    CMD ["httpd-foreground"]

The file says to take the httpd:latest, run a command on it, then set the command to run (could already be there, I don't know - it just worked and make me very happy).

Then, run :

    docker build -t httpd:mysql .

After that, change your docker-compose.yml to reference your new image, and then you should be able to make it function.  Granted, CGI's need the REMOTE_USER if you are doing anything specific with that, so add the config where you need it to pull the authenticated user from the Cloudflare header containing the username and stuff it.

    # set the REMOTE_USER variable
    RewriteCond %{HTTP:Cf-Access-Authenticated-User-Email} ^(.*)$
    RewriteRule .* - [E=REMOTE_USER:%1]

Then stop the container and start it back up, and that part should be functional.  The bonus is you can extend all you want. 

Wednesday, July 22, 2026

Docker - Apache HTTPd for my tiny Intranet

I'm rebuilding my network stack for my little intranet, and the first thing out of the gate is the HTTPd web service.  So, let's get started.

I'm not using NginX because there will be a lot more CGI, and also because I want to implement my template mechanism, a custom module for the Apache HTTPd serer.  Converting from an existing HTTPd configuration is a bit more difficult when it comes to migrating it in to Docker.

Now, RedHat and other Linux vendors/distributors love to part the httpd.conf file out into a whole lot of directories.

Docker doesn't do that.  However, we have a little secret.  So, let's create our docker image first.  Create your docker directory and then the container for this specific image.

    mkdir -p /opt/docker/httpd

Because the docker image is actually based on a Debian version, and because it includes an httpd.conf file already (by default), we only need three files in this directory.

For the httpd.conf, I found it easier to grab the httpd.conf file out of the docker image first, then add my custom module.  And, since I wanted the image to serve content from the NAS, I needed to get the web content from that mounted up.  Let's grab a copy of the docker container httpd.conf file.

    docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > httpd.conf

Now, with that, I can add my own module using the LoadModule with any custom directives.  Then, we simply add the NFS mounts for the mount the NAS up via NFS, and then use volumes, too.  Enter the docker compose. 

    services:
      web:
        image: httpd:2.4
        container_name: httpd
        restart: unless-stopped
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - /opt/docker/storage/http/html:/usr/local/apache2/htdocs/:ro
          - /opt/docker/storage/http/cgi-bin:/usr/local/apache2/cgi-bin/:ro
          - /opt/docker/storage/http/logs:/usr/local/apache2/logs/:rw
          - ./certs:/etc/apache2/ssl
          - ./httpd.conf:/usr/local/apache2/conf/httpd.conf:ro
          - ./mod_template.so:/usr/local/apache2/modules/mod_template.so:ro

The container name/image, are fairly inconsequential - you can name them anything you want.  The default is "apache_service" for this from the Docker image, but I like to get very specific because Apache likes to do a lot of services.

The "restart" line is there in case the EliteDesk hardware loses power and reboots - I want this container to start back up immediately.

The ports are very easy to understand if you are used to TCP.  It is formatted as "HOST PORT:CONTAINER PORT".

Also, we need to set up SSL (note we have port 443 in there).  Create a certificate :

    mkdir certs
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout certs/httpd.key -out certs/httpd.crt -subj "/C=US/ST=Idaho/L=Boise/O=SilverHawk/CN=silverhawk.net"

You will need to set up the standard VirtualHost for it (make sure you uncomment the LoadModule line for the ssl extension while in there) :

    <VirtualHost *:443>
    DocumentRoot "/usr/local/apache2/htdocs"
    #ServerName www.example.com
    SSLEngine On
    SSLCertificateFile /etc/apache2/ssl/httpd.crt
    SSLCertificateKeyFile /etc/apache2/ssl/httpd.key
    </VirtualHost>

While I was there, I also added proxy and proxypass information for Grafana, (that's coming - note you need to enable xml2enc_module, proxy_http_module, and proxy_module, and make sure you disable proxy for everything except your own proxt so that someone doesn't suddenly use your site as a personal proxy to mask what they are doing on the Internet).

A grafana note :

I really strugged with proxy pass in this situation.  all of the AI engines kept telling me things that were not accurate.  So, here's what I have.  In my httpd.conf for docker, I have the following excerpt :

    ProxyRequests Off
    <VirtualHost *:443>
    DocumentRoot "/usr/local/apache2/htdocs"
    #ServerName www.example.com
    SSLEngine On
    SSLCertificateFile /etc/apache2/ssl/httpd.crt
    SSLCertificateKeyFile /etc/apache2/ssl/httpd.key

    RewriteEngine On
    RewriteRule .* - [E=PROXY_USER:%{LA-U:REMOTE_USER},NS]
    RequestHeader set X-WEBAUTH-USER "%{REMOTE_USER}e"
    ProxyPreserveHost On
    ProxyPass /grafana/ http://172.17.0.1:3000/
    ProxyPassReverse /grafana/ http://172.17.0.1:3000/
    RequestHeader unset Accept-Encoding
    <Location /charts>
    TemplateEnabled off
    </Location>
    </VirtualHost>

Then, in my Grafana docker-compose.yml file, I had to add the GF_SERVER_ROOT_URL and GF_SERVER_ENFORCE_DOMAIN environment variables (there are some others, to set up anonymous browsing since this will be front-ended by an authentication later) :

        environment:
    - GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s:%(http_port)s/grafana/
    - GF_SERVER_ENFORCE_DOMAIN=false
    - GF_AUTH_PROXY_ENABLED=true
    - GF_AUTH_PROXY_HEADER_NAME=X-WEBAUTH-USER
    - GF_AUTH_PROXY_HEADER_PROPERTY=username
    - GF_AUTH_PROXY_AUTO_SIGN_UP=true

Volumes are where the magic happens for the custom module and configuration as well as the web root.  Each row represents a specific file/directory that is mapped from the docker image.  For example, I have my mod_template.so in there so it gets mapped into the container, plus I am mapping an httpd.conf file, as well as two NFS directories so I don't have to restart the docker every time I change the website.

Then, it's simply a matter of launching it up.

    sudo docker compose up -d  

Note, if you run into errors, you can use (and there is a -f option to tail -f them) :

    docker compose logs

This will dump the containers log files (I suppose you COULD re-map log files if you want those pulled out of the image naturally, but this was easier). 

Saturday, September 25, 2021

Setting Up Apache for Federated Identity

I was loading my environmental data up on a web server through Grafana.  I'd been running LDAP-based authentication for years, and wanted to be able to shut that service down because I was growing weary of updating SSL certificates.  I had Grafana running on it's own TCP port inside, but I desired to expose it (while protecting the data).  I finally broke down and did it.

I started out with two "tutorials" :

Both of these were lacking in information.  For example, there are no instructions on setting up the Google API, and there was a configuration option of "OIDCCryptoPassphrase" that was a variable and no one explained what it needed to be set to.  But, I wanted to get it done.

First, I went through Google.  I'd not set up google cloud for my domain before, so this was new.  First, log in to the https://console.cloud.google.com/apis/dashboard?pli=1 (it's the cloud platform).  Once in there, if you don't have a project already, create one.  This is done using the drop down at the top :

Click on "New Project" in the upper right hand corner :

Now, you can create a credential.  Click on "Credentials" on the left, and then "Create Credential" at the top :

 

Follow the set up guide.  The type will be what you need, in my case, I was doing Apache's HTTPd server, so I went with "Web Application".  The redirect URI setting must match what you use for your OAuth configuration (in the configuration file, actually).  Make sure you have your domains listed, etc.


At this point, copy the ID as well as the client secret.  These need to go into your configuration file for Apache's HTTPd.

You might need to create an "OAuth Consent Screen", too.  Those three configurations in Google are all you need.

Load up your editor you use to change the HTTPd configuration.  The basic lines you are going to need are :

    OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration
    OIDCClientID CLIENT_ID_FROM_CONFIGURATION
    OIDCClientSecret CLIENT_SECRET_FROM_CONFIGURATION

    # OIDCRedirectURI is a vanity URL, and should not point to any actual content
    OIDCRedirectURI http://hostname.example.com/grafana/redirect_uri
    OIDCCryptoPassphrase PERSONALLY_CHOSEN_PASSPHRASE
    OIDCScope "openid email profile"
    OIDCRemoteUserClaim email

    <Location /grafana/>
        TemplateEnabled off
        AuthType openid-connect
        <RequireAny>
            # not just anyone signed in from google
            # Require valid-user

            # network
            Require ip 10.0.0.

            # signed in with domain
            Require claim hd:silverhawk.net

            # or, signed in with domain (e-mail fall through)
            Require claim "email~^(.*)@silverhawk.net$"

            # or Someone External
            Require claim "email~^username@gmail.com$"
        </RequireAny>

        ProxyPass http://localhost:3000/
        ProxyPassReverse http://localhost:3000/
        Order allow,deny
        Allow from all

        # grafana requires the username to be in a header
        RewriteEngine On
        RewriteRule .* - [E=PROXY_USER:%{LA-U:REMOTE_USER},NS]
        RequestHeader set X-WEBAUTH-USER "%{PROXY_USER}e"
    </Location>

The OIDCClientID and the OIDCClientSecret configuration items are where you stuff the respective items from your OAuth configuration we copied above.  The OIDCCryptoPassphrase is where I was getting lost - this is going to be something you choose, and is specific to the cluster (e.g. so that the cluster can keep state if you hit other servers).

I used a RequireAny to set up multiple options - so, if you are on the local network and sign in to google, you'll get in.  If your primary google address is a silverhawk.net domain, you can get in.  If your e-mail address is username@gmail.com, you can get in.

The rewrite directives are there specifically for Grafana so that Grafana can see the remote_user as the e-mail of the individual who just authenticated.  In your grafana.ini, locate your root_url, and make sure we've added the URI piece we are proxying, e.g. :

    root_url = %(protocol)s://%(domain)s:%(http_port)s/grafana/

Next, locate your "[users]" section, and set the following :

    allow_sign_up = false
    auto_assign_org = true

Next, locate the auth.proxy in your Grafana configuration.  Since we are rolling through HTTPd and it will be doing the authentication, we can let Grafana accept whatever HTTPd feeds to us.

    [auth.proxy]
    enabled = true
    header_name = X-WEBAUTH-USER
    header_property = username
    auto_sign_up = true

The header name should match what was in our rewrite rule, and the header property is the username that is going to get set up.  auto_sign_up needs to be set to true so that we can create accounts on the fly.

Now, restart any processes and give it a test!