Skip to content

Commit

Permalink
Build docker container and test (#1)
Browse files Browse the repository at this point in the history
* add docker build

* incore auth

* add example for environment variables

* fix bug

* fix bug

* fix bug

* fix docker build

* fix typo

* only manual trigger

* image name

* update gh action

* update ways to extract app name
  • Loading branch information
longshuicy authored Nov 8, 2024
1 parent 04c3863 commit 4a0df91
Show file tree
Hide file tree
Showing 25 changed files with 207 additions and 134 deletions.
87 changes: 87 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: docker

on:
workflow_dispatch:
inputs:
dockerfile:
description: 'Select the Dockerfile to use'
required: true
default: 'Dockerfile.dachub_auth'
options:
- Dockerfile.dachub_auth
- Dockerfile.incore_auth

jobs:
docker:
permissions:
contents: read
packages: write

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# Extract name from Dockerfile input
- name: Extract Dockerfile name
id: extract_name
run: |
NAME=$(echo "${{ github.event.inputs.dockerfile }}" | cut -d'.' -f2)
echo "IMAGE_NAME=$NAME" >> $GITHUB_ENV
# Create metadata for image
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
hub.ncsa.illinois.edu/dachub/${{ env.IMAGE_NAME }}
ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3

- name: Inspect Builder
run: |
echo "Name: ${{ steps.buildx.outputs.name }}"
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
echo "Status: ${{ steps.buildx.outputs.status }}"
echo "Flags: ${{ steps.buildx.outputs.flags }}"
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Login to NCSA Hub
uses: docker/login-action@v3
with:
registry: hub.ncsa.illinois.edu
username: ${{ secrets.NCSA_HUB_USERNAME }}
password: ${{ secrets.NCSA_HUB_PASSWORD }}

# Build and push
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ github.event.inputs.dockerfile }}
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.meta.outputs.version }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

.env
8 changes: 0 additions & 8 deletions .idea/.gitignore

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/forward-auth.iml

This file was deleted.

19 changes: 0 additions & 19 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

7 changes: 0 additions & 7 deletions .idea/inspectionProfiles/profiles_settings.xml

This file was deleted.

4 changes: 0 additions & 4 deletions .idea/misc.xml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

22 changes: 0 additions & 22 deletions Dockerfile.dac

This file was deleted.

21 changes: 21 additions & 0 deletions Dockerfile.dachub_auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM python:3.7-alpine

MAINTAINER NCSA

Check warning on line 3 in Dockerfile.dachub_auth

View workflow job for this annotation

GitHub Actions / docker

The MAINTAINER instruction is deprecated, use a label instead to define an image author

MaintainerDeprecated: Maintainer instruction is deprecated in favor of using label More info: https://docs.docker.com/go/dockerfile/rule/maintainer-deprecated/
LABEL PROJECT_REPO_URL = "" \

Check warning on line 4 in Dockerfile.dachub_auth

View workflow job for this annotation

GitHub Actions / docker

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "LABEL key=value" should be used instead of legacy "LABEL key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/
PROJECT_REPO_BROWSER_URL = "" \
DESCRIPTION = ")"

WORKDIR /srv

COPY requirements.txt .
RUN pip3 install -Ur requirements.txt

COPY dachub_auth dachub_auth
COPY forward_auth dachub_auth/forward_auth

ENV KEYCLOAK_PUBLIC_KEY="" \
KEYCLOAK_AUDIENCE="" \
KEYCLOAK_URL=""

WORKDIR /srv/dachub_auth
CMD ["gunicorn", "app:app", "--config", "gunicorn.config.py"]
16 changes: 8 additions & 8 deletions Dockerfile.incore → Dockerfile.incore_auth
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ LABEL PROJECT_REPO_URL = "" \

WORKDIR /srv

COPY incore_app/requirements.txt incore_app/
RUN pip3 install -Ur incore_app/requirements.txt
COPY requirements.txt incore_auth/
RUN pip3 install -Ur incore_auth/requirements.txt

COPY incore_app incore_app
COPY forward_auth forward_auth
COPY incore_auth incore_auth
COPY forward_auth incore_auth/forward_auth

WORKDIR /srv/incore_app
WORKDIR /srv/incore_auth

ENV FLASK_APP="app.py" \
KEYCLOAK_PUBLIC_KEY="" \
ENV KEYCLOAK_PUBLIC_KEY="" \
KEYCLOAK_AUDIENCE="" \
KEYCLOAK_URL="" \
DATAWOLF_URL="http://incore-datawolf:8888/datawolf" \
MONGODB_URI="" \
INFLUXDB_V2_URL="" \
INFLUXDB_V2_ORG="" \
INFLUXDB_V2_TOKEN="" \
INFLUXDB_V2_FILE_LOCATION="data/IP2LOCATION-LITE-DB5.BIN"

CMD ["gunicorn", "app:app", "--config", "/srv/incore_app/gunicorn.config.py"]
CMD ["gunicorn", "app:app", "--config", "gunicorn.config.py"]
3 changes: 0 additions & 3 deletions dac_app/config.json

This file was deleted.

48 changes: 26 additions & 22 deletions dac_app/app.py → dachub_auth/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
load_dotenv()

# Load configuration from JSON file
with open("../incore_forward_auth/config.json") as config_file:
with open("./config.json") as config_file:
config = json.load(config_file)

app = Flask(__name__)
Expand All @@ -29,40 +29,44 @@
@app.before_request
def handle_request():
"""Handle incoming requests: authorization, resource checks, and monitoring."""
if request.url_rule is None:
if request.url_rule is not None:
return healthz()

# Handle CORS preflight
if request.headers.get('X-Forwarded-Method') == 'OPTIONS':
return Response(status=200)

# Retrieve user and request information
request_info = Util.get_request_info(request)['resource']
request_info = Util.get_request_info(request)

# Allow non-protected resources
if request_info['resource'] not in app.config["PROTECTED_RESOURCES"]:
return Response(status=200)

# Authentication check
if jwt_authenticator.verify_token(request.headers.get('Authorization', '')):
user_info = jwt_authenticator.get_userinfo(request.headers.get('Authorization', ''))

# Build response
response = Response(status=200)
response.headers['X-Auth-UserInfo'] = json.dumps({"preferred_username": user_info['username']})
response.headers['X-Auth-UserGroup'] = json.dumps({"groups": user_info['groups']})

# Handle Authorization headers and cookies
auth_header = request.headers.get('Authorization') or request.cookies.get('Authorization')
if auth_header:
# Handle Authorization headers and cookies
auth_header = request.headers.get('Authorization') or request.cookies.get('Authorization')
if auth_header:
token = auth_header.split(" ")[1] if "bearer " in auth_header.lower() else auth_header
if token.count('.') != 2:
make_response("Invalid JWT format: Not enough segments.", 400)

verified, message = jwt_authenticator.verify_token(token)
if verified:
user_info = jwt_authenticator.get_userinfo(token)

# Build response
response = Response(status=200)
response.headers['X-Auth-UserInfo'] = json.dumps({"preferred_username": user_info['username']})
response.headers['X-Auth-UserGroup'] = json.dumps({"groups": user_info['groups']})
response.headers['Authorization'] = unquote_plus(auth_header)

group_header = request.headers.get('X-Auth-UserGroup') or request.cookies.get('X-Auth-UserGroup')
if group_header:
response.headers['X-Auth-UserGroup'] = group_header

return response
group_header = request.headers.get('X-Auth-UserGroup') or request.cookies.get('X-Auth-UserGroup')
if group_header:
response.headers['X-Auth-UserGroup'] = group_header

return response
else:
return make_response(message, 401)
else:
return make_response("Unauthenticated", 401)

Expand All @@ -73,5 +77,5 @@ def healthz():
return Response("OK", 200)

# Uncomment to run locally
# if __name__ == "__main__":
# app.run(host="0.0.0.0", port=5000, debug=True)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
3 changes: 3 additions & 0 deletions dachub_auth/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"PROTECTED_RESOURCES": ["geoserver", "geoserver/web"]
}
3 changes: 3 additions & 0 deletions dachub_auth/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
KEYCLOAK_AUDIENCE=
KEYCLOAK_PUBLIC_KEY=
KEYCLOAK_URL=
File renamed without changes.
41 changes: 41 additions & 0 deletions dachub_auth/test_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import json
import os
import pytest
from unittest.mock import patch, MagicMock
from flask import Flask

from app import app # Replace `your_app_module` with the module name of your app


@pytest.fixture
def client():
app.config["TESTING"] = True
with app.test_client() as client:
yield client


def test_authorized_request(client):
response = client.get(
"https://localhost:5000/geoserver", # Replace with an actual protected endpoint in your app
headers={"Authorization": "bearer valid_token"}
)

assert response.status_code == 200
assert response.headers["X-Auth-UserInfo"] == json.dumps({"preferred_username": "cwang138"})
assert response.headers["X-Auth-UserGroup"] == json.dumps({"groups": []})


def test_unauthorized_request(client):
response = client.get(
"https://localhost:5000/geoserver",
headers={"Authorization": "Bearer invalid_token"}
)

assert response.status_code == 401
assert response.data == b"JWT Error: token is invalid"


def test_non_protected_resource(client):
response = client.get("https://localhost:5000/test")

assert response.status_code == 200
Loading

0 comments on commit 4a0df91

Please sign in to comment.