Skip to content

Initial version #1

Merged
merged 3 commits into from Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions Dockerfile
@@ -0,0 +1,25 @@
FROM tier/shibboleth_sp

LABEL version="0.0.1"

RUN yum update -y && \
yum clean all

COPY container_files/httpd/httpd.conf /etc/httpd/conf/httpd.conf
COPY container_files/httpd/proxy.conf /etc/httpd/conf.d/proxy.conf
COPY container_files/httpd/shib.conf /etc/httpd/conf.d/shib.conf
COPY container_files/httpd/ssl.conf /etc/httpd/conf.d/ssl.conf
COPY container_files/httpd/vhost.conf /etc/httpd/conf.d/vhost.conf
COPY container_files/httpd/00-mpm.conf /etc/httpd/conf.modules.d/00-mpm.conf

COPY container_files/shibboleth/attribute-map.xml /etc/shibboleth/attribute-map.xml
COPY container_files/shibboleth/shibboleth2.xml.tpl /etc/shibboleth/shibboleth2.xml.tpl

COPY container_files/system/supervisord.conf /etc/supervisor/supervisord.conf

COPY --chown=root:root container_files/proxy-entrypoint.sh /usr/local/bin/proxy-entrypoint.sh
RUN chmod 755 /usr/local/bin/proxy-entrypoint.sh
ENTRYPOINT [ "/usr/local/bin/proxy-entrypoint.sh" ]

# From base image
CMD [ "/usr/local/bin/startup.sh" ]
131 changes: 131 additions & 0 deletions README.md
@@ -1,2 +1,133 @@
# docker-shib-proxy
A SAML-authenticating HTTPS reverse proxy using Shibboleth


## Introduction

This repository builds a Docker container image that acts as an HTTPS reverse
proxy in front of a web application. The proxy authenticates a visitor using
SAML, and passes the username and group memberships to the web application as
HTTP headers:

* `X-Forwarded-User`: Contains the `eppn` attribute value for the
user

* `X-Forwarded-Groups`: Contains the `isMemberOf` attribute values for the
user, separated by semicolons

IMPORTANT: To prevent visitors from spoofing the HTTP headers, you
MUST ensure that your web application *only* allows incoming requests
from the reverse proxy, or otherwise that it doesn't accept the headers above
from any clients _except_ the reverse proxy. A typical configuration would be
to run the proxy as a sidecar container to your web application, such that all
HTTP(S) requests must flow through the proxy.


## Quick Start

To try the container in a test environment:

1. Change to the `tests` directory, and bring up the `proxy` service and its
dependencies from `docker-compose.yml`:
```
cd tests
docker-compose up -d proxy
```

2. Update `/etc/hosts` to add `idp.example.edu` and
`sptest.example.edu` as aliases for `localhost`:
```
# /etc/hosts
127.0.0.1 localhost idp.example.edu sp.example.edu
```

3. In a web browser, visit https://sptest.example.edu:8443. You should be
redirected to the test IdP at https://idp.example.edu.

4. In the IdP login form, use `banderson` for the username, and
`password` for the password.

5. After logging in, you should see a PHP information page. Under the
"HTTP Headers Information" heading, yous should see the following
headers set:

| Header | Value |
|--------------------|------------------------------------------------------------------------------------|
| X-Forwarded-User | banderson@example.edu |
| X-Forwarded-Groups | cn=developers,ou=Groups,dc=internet2,dc=edu;cn=users,ou=Groups,dc=internet2,dc=edu |

6. When finished, shut down the services from `docker-compose.yml`:
```
docker-compose down
```


## Configuration

The container expects several environment variables at runtime. See
`tests/docker-compose.yml` for a working example. In a production environment,
these variables would be set by your orchestration system (for example, AWS
Elastic Container Service or Kubernetes), and sensitive information (such as
private keys) would be stored securely (for example, using AWS Systems Manager
Parameter Store, AWS Secrets Manager, or HashiCorp Vault).

### Required Variables

_(Note: To establish trust with the IdP, you must either specify
`SAML_IDP_METADATA`, or specify `SAML_IDP_METADATA_URL` and
`SAML_IDP_SIGNING_CERT`.)_

* `FRONT_HTTPS_PORT`: Set this to the canonical front-end HTTPS port, to ensure
that URLs are constructed correctly. (This may be different from `HTTPS_PORT`,
if the proxy is behind a load balancer or another HTTPS proxy.)

* `FRONT_HOSTNAME`: Set this to the canonical, fully-qualified domain name for
the proxy, for example "app.example.edu". (This may be different from the
proxy's hostname, if the proxy is behind a load balancer or another HTTPS
proxy.)

* `HTTPS_PORT`: The port on which the proxy should listen for HTTPS connections

* `SAML_ENCRYPTION_CERT`: A base-64 encoded SAML encryption certificate,
beginning with "-----BEGIN CERTIFICATE-----"

* `SAML_ENCRYPTION_KEY`: The base-64 encoded private key for
`SAML_ENCRYPTION_CERT`, beginning with "-----BEGIN PRIVATE KEY-----"

* `SAML_ENTITYID`: The SAML entity ID for the proxy, for example
"https://app-proxy.example.edu/shibboleth"

* `SAML_IDP_ENTITYID`: The SAML entity ID for a trusted SAML identity provider
or SAML proxy, for example "https://idp.example.edu/idp/shibboleth"

* `SAML_IDP_METADATA` (Optional): SAML metadata for `SAML_IDP_ENTITYID`; should
include a single `<EntityDescriptor>` element. Required if
`SAML_IDP_METADATA_URL` is not set.

* `SAML_IDP_METADATA_URL` (Optional): URL from which to retrieve the IdP
metadata file. Required if `SAML_IDP_METADATA` is not set.

* `SAML_IDP_SIGNING_CERT` (Optional): The base-64 encoded SAML signing
certificate used to verify the IdP metadata signature, beginning with
"-----BEGIN CERTIFICATE-----". Required if `SAML_IDP_METADATA_URL` is set.

* `SAML_SIGNING_CERT`: A base-64 encoded SAML signing certificate
beginning with "-----BEGIN CERTIFICATE-----"

* `SAML_SIGNING_KEY`: The base-64 encoded private key for
`SAML_SIGNING_CERT`, beginning with "-----BEGIN PRIVATE KEY-----"

* `TLS_CERT`: A base-64 encoded TLS server certificate used for HTTPS
connections, beginning with "-----BEGIN CERTIFICATE-----"

* `TLS_PRIVATE_KEY`: The base-64 encoded private key for `TLS_CERT`, beginning
with "-----BEGIN RSA PRIVATE KEY-----"

### Optional Variables

* `HTTPD_ARGS`: Additional arguments to be passed to `httpd` by `supervisord`.
If the proxy is behind a load balancer or another HTTP proxy, set this to
"-DProxied"

* `TIER_BEACON_OPT_OUT`: Opt out of reporting telemetry to Internet2 (from
the `tier/shibboleth_sp` base container image). Set to "true" or "false".
15 changes: 15 additions & 0 deletions ci/build.sh
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

echo "[INFO] $0 started at `date`"

# Read the commit hash
read -r commit_hash<<<$(git rev-parse --short HEAD)
echo "[INFO] Commit hash: \"$commit_hash\""

image_name=shib-proxy

# Build the container image
echo "[INFO] Building the container image"
docker build -t $image_name:$commit_hash .

echo "[INFO] $0 finished at `date`"
38 changes: 38 additions & 0 deletions ci/deploy.sh
@@ -0,0 +1,38 @@
#!/usr/bin/env bash

echo "[INFO] $0 started at `date`"

# Read the commit hash
read -r commit_hash<<<$(git rev-parse --short HEAD)
echo "[INFO] Commit hash: \"$commit_hash\""

image_name=shib-proxy

# Read the version
read -r version<<<$(docker inspect --format='{{.Config.Labels.version}}' $image_name:$commit_hash)
if [[ "$version" == "" ]]; then
echo "[ERROR] Unable to inspect version label for image $image_name:$commit_hash"
exit 1
fi
echo "[INFO] Version: \"$version\""
read -r major minor patch<<<$(echo $version | tr . ' ')
echo "[DEBUG] major.minor.patch=\"$major.$minor.$patch\""

# Tag the image
echo "[INFO] Tagging the container image"
docker tag $image_name:$commit_hash ${ECR_HOSTNAME}/${image_name}:$major.$minor.$patch
docker tag $image_name:$commit_hash ${ECR_HOSTNAME}/${image_name}:$major.$minor
docker tag $image_name:$commit_hash ${ECR_HOSTNAME}/${image_name}:$major

# Push the image to a registry if REPOSITORY_BASE is defined
if [ -z "$ECR_HOSTNAME" ]; then
echo "[ERROR] Unable to push image (ECR_HOSTNAME not specified)"
else
echo "[DEBUG] ECR_HOSTNAME=\"$ECR_HOSTNAME\""
docker push ${ECR_HOSTNAME}/${image_name}:latest
docker push ${ECR_HOSTNAME}/${image_name}:$major.$minor.$patch
docker push ${ECR_HOSTNAME}/${image_name}:$major.$minor
docker push ${ECR_HOSTNAME}/${image_name}:$major
fi

echo "[INFO] $0 finished at `date`"
4 changes: 4 additions & 0 deletions ci/login.sh
@@ -0,0 +1,4 @@
#!/usr/bin/env bash

echo "Logging in to ECR repository"
$(aws ecr get-login --no-include-email)
49 changes: 49 additions & 0 deletions ci/test.sh
@@ -0,0 +1,49 @@
#!/usr/bin/env bash

# Run automated tests
#
# Example:
#
# ci/test.sh
#
# Returns 0 if tests pass, or 1 if tests fail.

echo "[INFO] $0 started at `date`"

# Read the commit hash
read -r commit_hash<<<$(git rev-parse --short HEAD)
echo "[INFO] Commit hash: \"$commit_hash\""

image_name=shib-proxy

# Bring up the proxy and its dependencies
cd tests
export PROXY_IMAGE_TAG="$commit_hash"
docker-compose up -d proxy

# Wait for the idp
echo "[INFO] Waiting for idp"
timeout --foreground 30 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' idp 443

# Wait for the proxy
echo "[INFO] Waiting for proxy"
timeout --foreground 30 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' proxy 8443

# Run the test container and store the exit code
docker-compose run test python3 /tests/test.py
if [ $? -eq 0 ]
then
echo "[INFO] Tests passed!"
set rc=0
else
echo "[ERROR] Tests failed!"
set rc=1
fi

# Shut down all containers
docker-compose down

echo "[INFO] $0 finished at `date`"

# Exit with the return code from the test container
exit $rc
21 changes: 21 additions & 0 deletions container_files/httpd/00-mpm.conf
@@ -0,0 +1,21 @@
# Select the MPM module which should be used by uncommenting exactly
# one of the following LoadModule lines:

# prefork MPM: Implements a non-threaded, pre-forking web server
# See: http://httpd.apache.org/docs/2.4/mod/prefork.html
#
# LoadModule mpm_prefork_module modules/mod_mpm_prefork.so

# worker MPM: Multi-Processing Module implementing a hybrid
# multi-threaded multi-process web server
# See: http://httpd.apache.org/docs/2.4/mod/worker.html
# (Recommended for Shibboleth SP; see:
# https://wiki.shibboleth.net/confluence/display/SP3/Apache)

LoadModule mpm_worker_module modules/mod_mpm_worker.so

# event MPM: A variant of the worker MPM with the goal of consuming
# threads only for connections with active processing
# See: http://httpd.apache.org/docs/2.4/mod/event.html
#
#LoadModule mpm_event_module modules/mod_mpm_event.so