Skip to content

Commit

Permalink
Merge pull request #76 from esnet-security/topic/soehlert/allow_local…
Browse files Browse the repository at this point in the history
…_auth

feat(auth): Allow django's built in local auth in prod mode
  • Loading branch information
samoehlert authored Nov 20, 2024
2 parents 55e0cc9 + 7fdb6ab commit 3f86e3b
Show file tree
Hide file tree
Showing 29 changed files with 238 additions and 469 deletions.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ To get a basic implementation up and running locally:
- Create ``$scram_home/.envs/.production/.postgres`` a template exists in the docs/templates directory
- Make sure to set the right credentials
- By default this template assumes you have a service defined in docker compose file called postgres. If you use another postgres server, make sure to update that setting as well
- Create a ``.env`` file with the necessary environment variables:
- [comment]: # This chooses if you want to use oidc or local accounts. This can be local or oidc only. Default: `local`
- scram_auth_method: "local"
- ``make build``
- ``make toggle-prod``
- This will turn off debug mode in django and start using nginx to reverse proxy for the app
Expand Down
69 changes: 58 additions & 11 deletions config/settings/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""
Base settings to build other settings files upon.
"""

import logging
import os
from pathlib import Path

Expand Down Expand Up @@ -97,12 +99,6 @@
]
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
AUTH_USER_MODEL = "users.User"
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
LOGIN_REDIRECT_URL = "route_manager:home"
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = "admin:login"
# https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
LOGOUT_URL = "admin:logout"

# PASSWORDS
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -281,6 +277,62 @@
CORS_URLS_REGEX = r"^/api/.*$"
# Your stuff...
# ------------------------------------------------------------------------------
# Are you using local passwords or oidc?
AUTH_METHOD = os.environ.get("SCRAM_AUTH_METHOD", "local").lower()

# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
LOGIN_REDIRECT_URL = "route_manager:home"

# Need to point somewhere otherwise /oidc/logout/ redirects to /oidc/logout/None which 404s
# https://github.com/mozilla/mozilla-django-oidc/issues/118
# Using `/` because named urls don't work for this package
# https://github.com/mozilla/mozilla-django-oidc/issues/434
LOGOUT_REDIRECT_URL = "route_manager:home"

OIDC_OP_JWKS_ENDPOINT = os.environ.get(
"OIDC_OP_JWKS_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/certs",
)
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ.get(
"OIDC_OP_AUTHORIZATION_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/auth",
)
OIDC_OP_TOKEN_ENDPOINT = os.environ.get(
"OIDC_OP_TOKEN_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/token",
)
OIDC_OP_USER_ENDPOINT = os.environ.get(
"OIDC_OP_USER_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/userinfo",
)
OIDC_RP_SIGN_ALGO = "RS256"

logging.info(f"Using AUTH METHOD = {AUTH_METHOD}")
if AUTH_METHOD == "oidc":
# Extend middleware to add OIDC middleware
MIDDLEWARE += ["mozilla_django_oidc.middleware.SessionRefresh"] # noqa F405

# Extend middleware to add OIDC auth backend
AUTHENTICATION_BACKENDS += ["scram.route_manager.authentication_backends.ESnetAuthBackend"] # noqa F405

# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = "oidc_authentication_init"

# https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
LOGOUT_URL = "oidc_logout"

OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID")
OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET")

elif AUTH_METHOD == "local":
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = "local_auth:login"

# https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
LOGOUT_URL = "local_auth:logout"
else:
raise ValueError(f"Invalid authentication method: {AUTH_METHOD}. Please choose 'local' or 'oidc'")


# Should we create an admin user for you
AUTOCREATE_ADMIN = True
Expand All @@ -293,9 +345,6 @@
SIMPLE_HISTORY_HISTORY_CHANGE_REASON_USE_TEXT_FIELD = True
SIMPLE_HISTORY_ENABLED = True

# Are you using local passwords or oidc?
AUTH_METHOD = os.environ.get("SCRAM_AUTH_METHOD", "local")

# Users in these groups have full privileges, including Django is_superuser
SCRAM_ADMIN_GROUPS = ["svc_scram_admin"]

Expand All @@ -311,8 +360,6 @@
# This is the set of all the groups
SCRAM_GROUPS = SCRAM_ADMIN_GROUPS + SCRAM_READWRITE_GROUPS + SCRAM_READONLY_GROUPS + SCRAM_DENIED_GROUPS

# This is the full set of groups

# How many entries to show PER Actiontype on the home page
RECENT_LIMIT = 10
# What is the largest cidr range we'll accept entries for
Expand Down
13 changes: 12 additions & 1 deletion config/settings/local.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .base import * # noqa
from .base import env
from .base import AUTH_METHOD, env

# GENERAL
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -71,3 +71,14 @@

# Behave Django testing framework
INSTALLED_APPS += ["behave_django"] # noqa F405

# AUTHENTICATION
# ------------------------------------------------------------------------------
# We shouldn't be using OIDC in local dev mode as of now, but might be worth pursuing later
if AUTH_METHOD == "oidc":
raise NotImplementedError("oidc is not yet implemented")

# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = "admin:login"
# https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
LOGOUT_URL = "admin:logout"
45 changes: 1 addition & 44 deletions config/settings/production.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import os

from .base import * # noqa
from .base import AUTHENTICATION_BACKENDS, MIDDLEWARE, env
from .base import env

# GENERAL
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -141,44 +139,3 @@

# Your stuff...
# ------------------------------------------------------------------------------
# Extend middleware to add OIDC middleware
MIDDLEWARE += ["mozilla_django_oidc.middleware.SessionRefresh"] # noqa F405

# Extend middleware to add OIDC auth backend
AUTHENTICATION_BACKENDS += ["scram.route_manager.authentication_backends.ESnetAuthBackend"] # noqa F405

# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
LOGIN_URL = "oidc_authentication_init"

# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
LOGIN_REDIRECT_URL = "/"

# https://docs.djangoproject.com/en/dev/ref/settings/#logout-url
LOGOUT_URL = "oidc_logout"

# Need to point somewhere otherwise /oidc/logout/ redirects to /oidc/logout/None which 404s
# https://github.com/mozilla/mozilla-django-oidc/issues/118
# Using `/` because named urls don't work for this package
# https://github.com/mozilla/mozilla-django-oidc/issues/434
LOGOUT_REDIRECT_URL = "/"

OIDC_OP_JWKS_ENDPOINT = os.environ.get(
"OIDC_OP_JWKS_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/certs",
)
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ.get(
"OIDC_OP_AUTHORIZATION_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/auth",
)
OIDC_OP_TOKEN_ENDPOINT = os.environ.get(
"OIDC_OP_TOKEN_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/token",
)
OIDC_OP_USER_ENDPOINT = os.environ.get(
"OIDC_OP_USER_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/userinfo",
)
OIDC_RP_SIGN_ALGO = "RS256"

OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID")
OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET")
31 changes: 7 additions & 24 deletions config/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
With these settings, tests run faster.
"""

import os

from .base import * # noqa
from .base import env

Expand Down Expand Up @@ -41,26 +39,11 @@

# Your stuff...
# ------------------------------------------------------------------------------
# Extend middleware to add OIDC middleware
MIDDLEWARE += ["mozilla_django_oidc.middleware.SessionRefresh"] # noqa F405

OIDC_OP_JWKS_ENDPOINT = os.environ.get(
"OIDC_OP_JWKS_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/certs",
)
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ.get(
"OIDC_OP_AUTHORIZATION_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/auth",
)
OIDC_OP_TOKEN_ENDPOINT = os.environ.get(
"OIDC_OP_TOKEN_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/token",
)
OIDC_OP_USER_ENDPOINT = os.environ.get(
"OIDC_OP_USER_ENDPOINT",
"https://example.com/auth/realms/example/protocol/openid-connect/userinfo",
)
# These variables are required by the ESnetAuthBackend called in our OidcTest case
OIDC_OP_JWKS_ENDPOINT = "https://example.com/auth/realms/example/protocol/openid-connect/certs"
OIDC_OP_AUTHORIZATION_ENDPOINT = "https://example.com/auth/realms/example/protocol/openid-connect/auth"
OIDC_OP_TOKEN_ENDPOINT = "https://example.com/auth/realms/example/protocol/openid-connect/token"
OIDC_OP_USER_ENDPOINT = "https://example.com/auth/realms/example/protocol/openid-connect/userinfo"
OIDC_RP_SIGN_ALGO = "RS256"

OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID", "client_id")
OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET", "client_secret")
OIDC_RP_CLIENT_ID = ""
OIDC_RP_CLIENT_SECRET = ""
3 changes: 2 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
import mozilla_django_oidc # noqa: F401

urlpatterns += [path("oidc/", include("mozilla_django_oidc.urls"))]

elif settings.AUTH_METHOD == "local":
urlpatterns += [path("auth/", include("scram.local_auth.urls", namespace="local_auth"))]
# API URLS
api_version_urls = (
[
Expand Down
76 changes: 76 additions & 0 deletions docs/environment_variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## Environment Variables to Set for Deployment
[comment]: # Which branch of SCRAM to use (you probably want to set it to a release tag)
scram_code_branch:
#### Systems
[comment]: # Email of the main admin
scram_manager_email:
[comment]: # Set to true for production mode; set to false to set up the compose.override.local.yml stack
scram_prod: true
[comment]: # Set to true if you want ansible to install a scram user
scram_install_user: true
[comment]: # What group to put `scram` user in
scram_group: 'scram'
[comment]: # What username to use for `scram` user
scram_user: ''
[comment]: # WHat uid to use for `scram` user
scram_uid: ''
[comment]: # What directory to use for base of the repo
scram_home: '/usr/local/scram'
[comment]: # IP or DNS record for your postgres host
scram_postgres_host:
[comment]: # What postgres user to use
scram_postgres_user: ''

#### Authentication
[comment]: # This chooses if you want to use oidc or local accounts. This can be local or oidc only. Default: `local`
scram_auth_method: "local"
[comment]: # This client id (username) for your oidc connection. Only need to set this if you are trying to do oidc.
scram_oidc_client_id:

#### Networking
[comment]: # What is the peering interface docker uses for gobgp to talk to the router
scram_peering_iface: 'ens192'
[comment]: # The v6 network of your peering connection
scram_v4_subnet: '10.0.0.0/24'
[comment]: # The v4 IP of the peering connection for the router side
scram_v4_gateway: '10.0.0.1'
[comment]: # The v4 IP of the peering connection for gobgp side
scram_v4_address: '10.0.0.2'
[comment]: # The v6 network of your peering connection
scram_v6_subnet: '2001:db8::/64'
[comment]: # The v6 IP of the peering connection for the router side
scram_v6_gateway: '2001:db8::2'
[comment]: # The v6 IP of the peering connection for the gobgp side
scram_v6_address: '2001:db8::3'
[comment]: # The AS you want to use for gobgp
scram_as:
[comment]: # A string representing your gobgp instance. Often seen as the local IP of the gobgp instance
scram_router_id:
[comment]: #
scram_peer_as:
[comment]: # The AS you want to use for gobgp side (can this be the same as `scram_as`?)
scram_local_as:
[comment]: # The fqdn of the server hosting this - to be used for nginx
scram_nginx_host:
[comment]: # List of allowed hosts per the django setting "ALLOWED_HOSTS". This should be a list of strings in shell
[comment]: # `django` is required for the websockets to work
[comment]: # Our Ansible assumes `django` + `scram_nginx_host`
scram_django_allowed_hosts: "django"
[comment]: # The fqdn of the server hosting this - to be used for nginx
scram_server_alias:
[comment]: # Do you want to set an md5 for authentication of bgp
scram_bgp_md5_enabled: false
[comment]: # The neighbor config of your gobgp config
scram_neighbors:
[comment]: # The v6 address of your neighbor
- neighbor_address: 2001:db8::2
[comment]: # This is a v6 address so don't use v4
ipv4: false
[comment]: # This is a v6 address so use v6
ipv6: true
[comment]: # The v4 address of your neighbor
- neighbor_address: 10.0.0.200
[comment]: # This is a v4 address so use v4
ipv4: true
[comment]: # This is a v4 address so don't use v6
ipv6: false
2 changes: 1 addition & 1 deletion requirements/local.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ watchgod==0.8.2 # https://github.com/samuelcolvin/watchgod
# Testing
# ------------------------------------------------------------------------------
django-stubs==1.11.0 # https://github.com/typeddjango/django-stubs
pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar
pytest-sugar==0.9.6 # https://github.com/Frozenball/pytest-sugar
behave-django==1.4.0 # https://github.com/behave/behave-django

# Documentation
Expand Down
1 change: 1 addition & 0 deletions scram/local_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Local_auth is the app that holds urls we want to use with local Django auth."""
15 changes: 15 additions & 0 deletions scram/local_auth/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Register URLs for local auth known to Django, and the View that will handle each."""

from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path

app_name = "local_auth"

urlpatterns = [
path(
"login/",
LoginView.as_view(template_name="local_auth/login.html", success_url="route_manager:home"),
name="login",
),
path("logout/", LogoutView.as_view(), name="logout"),
]
4 changes: 2 additions & 2 deletions scram/route_manager/tests/test_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ def test_unauthorized_after_group_removal(self):
self.assertEqual(response.status_code, 302)


class OidcTest(TestCase):
"""Define tests using OIDC authentication."""
class ESnetAuthBackendTest(TestCase):
"""Define tests using OIDC authentication with our ESnetAuthBackend."""

def setUp(self):
"""Create a sample OIDC user."""
Expand Down
Loading

0 comments on commit 3f86e3b

Please sign in to comment.