Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docker image #8

Merged
merged 8 commits into from
Oct 2, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/publish_docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
name: Build and publish a Docker image

# Run workflow on pushes to matching branches
on: # yamllint disable-line rule:truthy
push:
branches: ["main"]
tags: ["*"]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-publish-image:
name: Build Docker image and publish to GitHub container repository
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v3

- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/${{ github.repository }}

- name: Build and publish Docker images
uses: docker/build-push-action@v4
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM python:3.11-alpine

WORKDIR /app

RUN apk add --update --no-cache \
gcc libc-dev libffi-dev

# Upload and install Python package and dependencies
COPY ./apricot apricot
COPY ./pyproject.toml .
COPY ./README.md .
RUN pip install --upgrade hatch pip
# Initialise environment with hatch
RUN hatch run true

# Install executable files and set permissions
COPY ./docker/entrypoint.sh .
COPY ./run.py .
RUN chmod ugo+x ./entrypoint.sh

# Open appropriate ports
EXPOSE 1389

# Run the server
ENTRYPOINT ["./entrypoint.sh"]
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ The name is a slightly tortured acronym for: LD**A**P **pr**oxy for Open**I**D *

## Usage

Start the `Apricot` server on port 8080 by running:
Start the `Apricot` server on port 1389 by running:

```bash
python run.py --client-id "<your client ID>" --client-secret "<your client secret>" --backend "<your backend>" --port 8080 --domain "<your domain name>"
python run.py --client-id "<your client ID>" --client-secret "<your client secret>" --backend "<your backend>" --port 1389 --domain "<your domain name>"
```

Alternatively, you can run in Docker by editing `docker/docker-compose.yaml` and running:

```bash
docker-compose up
```

from the `docker` directory.

## Outputs

This will create an LDAP tree that looks like this:

```ldif
Expand Down
16 changes: 16 additions & 0 deletions docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
version: "3"
services:
apricot:
container_name: apricot
image: apricot
build: .
environment:
BACKEND: "MicrosoftEntra"
CLIENT_ID: "<your OpenID client ID here"
CLIENT_SECRET: "<your OpenID client secret here>"
DOMAIN: "<your domain here>"
ENTRA_TENANT_ID: "<your Entra tenant ID here>"
ports:
- "1389:1389"
restart: always
38 changes: 38 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#! /bin/sh
# shellcheck disable=SC2086
# shellcheck disable=SC2089

if [ -z "${BACKEND}" ]; then
echo "BACKEND environment variable is not set"
exit 1
fi

if [ -z "${CLIENT_ID}" ]; then
echo "CLIENT_ID environment variable is not set"
exit 1
fi

if [ -z "${CLIENT_SECRET}" ]; then
echo "CLIENT_SECRET environment variable is not set"
exit 1
fi

if [ -z "${DOMAIN}" ]; then
echo "DOMAIN environment variable is not set"
exit 1
fi

# Optional arguments
EXTRA_OPTS=""
if [ -n "${ENTRA_TENANT_ID}" ]; then
EXTRA_OPTS="${EXTRA_OPTS} --entra-tenant-id $ENTRA_TENANT_ID"
fi

# Run the server
hatch run python run.py \
--backend "$BACKEND" \
--client-id "$CLIENT_ID" \
--client-secret "$CLIENT_SECRET" \
--domain "$DOMAIN" \
--port 1389 \
$EXTRA_OPTS
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ classifiers = [
]
dependencies = [
"ldaptor~=21.2.0",
"oauthlib~=3.2.2",
"requests-oauthlib~=1.3.1",
"Twisted~=23.8.0",
"zope.interface~=6.0",
]
Expand Down
39 changes: 23 additions & 16 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,28 @@
from apricot.oauth import OAuthBackend

if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="Apricot",
description="Apricot is a proxy for delegating LDAP requests to an OpenID Connect backend.",
)
# Common options needed for all backends
parser.add_argument("-b", "--backend", type=OAuthBackend, help="Which OAuth backend to use.")
parser.add_argument("-d", "--domain", type=str, help="Which domain users belong to.")
parser.add_argument("-p", "--port", type=int, default=8080, help="Port to run on.")
parser.add_argument("-i", "--client-id", type=str, help="OAuth client ID.")
parser.add_argument("-s", "--client-secret", type=str, help="OAuth client secret.")
# Options for Microsoft Entra backend
parser.add_argument("-t", "--entra-tenant-id", type=str, help="Microsoft Entra tenant ID.", required=False)
# Parse arguments
args = parser.parse_args()
try:
parser = argparse.ArgumentParser(
prog="Apricot",
description="Apricot is a proxy for delegating LDAP requests to an OpenID Connect backend.",
)
# Common options needed for all backends
parser.add_argument("-b", "--backend", type=OAuthBackend, help="Which OAuth backend to use.")
parser.add_argument("-d", "--domain", type=str, help="Which domain users belong to.")
parser.add_argument("-p", "--port", type=int, default=1389, help="Port to run on.")
parser.add_argument("-i", "--client-id", type=str, help="OAuth client ID.")
parser.add_argument("-s", "--client-secret", type=str, help="OAuth client secret.")
# Options for Microsoft Entra backend
group = parser.add_argument_group("Microsoft Entra")
group.add_argument("-t", "--entra-tenant-id", type=str, help="Microsoft Entra tenant ID.", required=False)
# Parse arguments
args = parser.parse_args()

# Create the Apricot server
reactor = ApricotServer(**vars(args))
# Create the Apricot server
reactor = ApricotServer(**vars(args))
except Exception:
msg = "Unable to initialise Apricot server from provided command line arguments."
raise ValueError(msg)

# Run the Apricot server
reactor.run()