├── .gitignore ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README.rst ├── demo ├── demo │ ├── __init__.py │ ├── settings.py │ ├── templates │ │ └── home.html │ ├── urls.py │ └── wsgi.py └── manage.py ├── requirements.txt ├── setup.py ├── sslserver ├── __init__.py ├── certs │ ├── development.crt │ └── development.key └── management │ ├── __init__.py │ └── commands │ ├── __init__.py │ └── runsslserver.py └── test.exp /.gitignore: -------------------------------------------------------------------------------- 1 | lint 2 | *.py[co] 3 | __pycache__ 4 | dist 5 | MANIFEST 6 | .idea 7 | venv/ 8 | *.sw[po] 9 | django_sslserver.egg-info/ 10 | build/ 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ted Dziuba 2 | Adam Stokes 3 | titusz 4 | Allan Daemon 5 | Ben Dean-Kawamura 6 | Collin Anderson 7 | Joseph Schilz 8 | Kevin Turner 9 | Nguyễn Hà Dương 10 | Rob Dennis 11 | Steve Gregory 12 | Yurii Zolot'ko 13 | myrs 14 | Anthony Monthe (ZuluPro) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Ted Dziuba 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include AUTHORS 3 | include LICENSE 4 | include README.rst 5 | recursive-include sslserver development.* 6 | 7 | recursive-exclude demo * 8 | global-exclude *.pyc *.pyo 9 | global-exclude .git 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Django SSL Server 3 | ================= 4 | 5 | .. image:: https://img.shields.io/pypi/v/django-sslserver.svg 6 | :target: https://pypi.python.org/pypi/django-sslserver 7 | 8 | .. image:: https://img.shields.io/pypi/pyversions/django-sslserver.svg 9 | :target: https://pypi.python.org/pypi/django-sslserver/ 10 | 11 | Django SSL Server is a SSL-enabled development server for the Django Framework. 12 | 13 | Please note that this `should not be used for production setups 14 | `_. This 15 | app is intended for special use-cases. Most people should instead do a proper 16 | `production deployment 17 | `_ where a real 18 | webserver such as Apache or NGINX handles SSL. 19 | 20 | Getting Started 21 | =============== 22 | 23 | Install the module in your Python distribution or virtualenv:: 24 | 25 | $ pip install django-sslserver 26 | 27 | Add the application to your `INSTALLED_APPS`:: 28 | 29 | INSTALLED_APPS = (... 30 | "sslserver", 31 | ... 32 | ) 33 | 34 | Start a SSL-enabled debug server:: 35 | 36 | $ python manage.py runsslserver 37 | 38 | and access app on https://localhost:8000 or start server on specified port:: 39 | 40 | $ python manage.py runsslserver 127.0.0.1:9000 41 | 42 | IPv6 support:: 43 | 44 | $ python manage.py runsslserver -6 [::]:7443 45 | 46 | You'll now be able to access your Django app on https://localhost:9000/ 47 | 48 | 49 | Browser Certificate Errors 50 | ========================== 51 | 52 | Using the default settings, your local browser will make all sorts of noise that it *doesn't trust the certificate*. **This is expected.** 53 | 54 | Django SSL Server ships "batteries included" with a self-signed server certificate. With self-signed certificates, 55 | the server is effectively telling the user, "I'm such-and-such server, because I said so". Whereas, with a commercial 56 | SSL certificate, the server tells the user, "I'm Bank of America, because VeriSign said so (or any other commercial certificate authority)." 57 | 58 | There are two options for making the certificate warning go away in development: 59 | 60 | **Option 1**: Tell your browser to explicitly trust the certificate. You can do this in your browser's "advanced settings" 61 | tab, by installing ``sslserver/certs/development.crt`` as a trusted certificate. The mechanism for this varies from browser to browser. 62 | 63 | **Option 2**: Use a certificate from a CA that your browser trusts, for example `Letsencrypt `_. 64 | If you have a certificate/key pair from a certificate authority, 65 | you can tell Django SSL Server to use it with the following arguments:: 66 | 67 | $ python manage.py runsslserver --certificate /path/to/certificate.crt --key /path/to/key.key 68 | 69 | 70 | Third-Party Static File Handlers 71 | ================================ 72 | 73 | If you're using a wrapper around your WSGI application such as dj_static or WhiteNoise, you probably want to let it handle serving 74 | static files. Otherwise, you may see 404s when requesting static files. You can disable the default behavior by using the ``--nostatic`` 75 | option. 76 | 77 | Getting Involved 78 | ================ 79 | 80 | Feel free to open pull requests or issues. GitHub is the canonical location of this project. 81 | -------------------------------------------------------------------------------- /demo/demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddziuba/django-sslserver/27c6f467524583fc522dc7b6792f166960c60210/demo/demo/__init__.py -------------------------------------------------------------------------------- /demo/demo/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for demo project. 2 | from os import path 3 | 4 | DEBUG = True 5 | TEMPLATE_DEBUG = DEBUG 6 | 7 | PROJECT_ROOT = path.dirname(__file__) 8 | 9 | ADMINS = ( 10 | # ('Your Name', 'your_email@example.com'), 11 | ) 12 | 13 | MANAGERS = ADMINS 14 | 15 | DATABASES = { 16 | 'default': { 17 | 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 18 | 'NAME': '', # Or path to database file if using sqlite3. 19 | # The following settings are not used with sqlite3: 20 | 'USER': '', 21 | 'PASSWORD': '', 22 | 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 23 | 'PORT': '', # Set to empty string for default. 24 | } 25 | } 26 | 27 | # Hosts/domain names that are valid for this site; required if DEBUG is False 28 | # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts 29 | ALLOWED_HOSTS = [] 30 | 31 | # Local time zone for this installation. Choices can be found here: 32 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 33 | # although not all choices may be available on all operating systems. 34 | # In a Windows environment this must be set to your system time zone. 35 | TIME_ZONE = 'America/Chicago' 36 | 37 | # Language code for this installation. All choices can be found here: 38 | # http://www.i18nguy.com/unicode/language-identifiers.html 39 | LANGUAGE_CODE = 'en-us' 40 | 41 | SITE_ID = 1 42 | 43 | # If you set this to False, Django will make some optimizations so as not 44 | # to load the internationalization machinery. 45 | USE_I18N = True 46 | 47 | # If you set this to False, Django will not format dates, numbers and 48 | # calendars according to the current locale. 49 | USE_L10N = True 50 | 51 | # If you set this to False, Django will not use timezone-aware datetimes. 52 | USE_TZ = True 53 | 54 | # Absolute filesystem path to the directory that will hold user-uploaded files. 55 | # Example: "/var/www/example.com/media/" 56 | MEDIA_ROOT = '' 57 | 58 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 59 | # trailing slash. 60 | # Examples: "http://example.com/media/", "http://media.example.com/" 61 | MEDIA_URL = '' 62 | 63 | # Absolute path to the directory static files should be collected to. 64 | # Don't put anything in this directory yourself; store your static files 65 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 66 | # Example: "/var/www/example.com/static/" 67 | STATIC_ROOT = '' 68 | 69 | # URL prefix for static files. 70 | # Example: "http://example.com/static/", "http://static.example.com/" 71 | STATIC_URL = '/static/' 72 | 73 | # Additional locations of static files 74 | STATICFILES_DIRS = ( 75 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 76 | # Always use forward slashes, even on Windows. 77 | # Don't forget to use absolute paths, not relative paths. 78 | ) 79 | 80 | # List of finder classes that know how to find static files in 81 | # various locations. 82 | STATICFILES_FINDERS = ( 83 | 'django.contrib.staticfiles.finders.FileSystemFinder', 84 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 85 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 86 | ) 87 | 88 | # Make this unique, and don't share it with anybody. 89 | SECRET_KEY = '3a=fu+&h1njr4pjuwr^4n0rmd6)5vw!o!44e*y61qil=zr5$^g' 90 | 91 | # List of callables that know how to import templates from various sources. 92 | TEMPLATE_LOADERS = ( 93 | 'django.template.loaders.filesystem.Loader', 94 | 'django.template.loaders.app_directories.Loader', 95 | # 'django.template.loaders.eggs.Loader', 96 | ) 97 | 98 | MIDDLEWARE_CLASSES = ( 99 | 'django.middleware.common.CommonMiddleware', 100 | 'django.contrib.sessions.middleware.SessionMiddleware', 101 | 'django.middleware.csrf.CsrfViewMiddleware', 102 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 103 | 'django.contrib.messages.middleware.MessageMiddleware', 104 | # Uncomment the next line for simple clickjacking protection: 105 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 106 | ) 107 | 108 | ROOT_URLCONF = 'demo.urls' 109 | 110 | # Python dotted path to the WSGI application used by Django's runserver. 111 | WSGI_APPLICATION = 'demo.wsgi.application' 112 | 113 | TEMPLATE_DIRS = ( 114 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 115 | # Always use forward slashes, even on Windows. 116 | # Don't forget to use absolute paths, not relative paths. 117 | path.join(PROJECT_ROOT, 'templates'), 118 | ) 119 | 120 | TEMPLATES = [ 121 | { 122 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 123 | 'DIRS': TEMPLATE_DIRS 124 | } 125 | ] 126 | 127 | INSTALLED_APPS = ( 128 | 'django.contrib.auth', 129 | 'django.contrib.contenttypes', 130 | 'django.contrib.sessions', 131 | 'django.contrib.sites', 132 | 'django.contrib.messages', 133 | 'django.contrib.staticfiles', 134 | 'sslserver', 135 | # Uncomment the next line to enable the admin: 136 | # 'django.contrib.admin', 137 | # Uncomment the next line to enable admin documentation: 138 | # 'django.contrib.admindocs', 139 | ) 140 | 141 | # A sample logging configuration. The only tangible logging 142 | # performed by this configuration is to send an email to 143 | # the site admins on every HTTP 500 error when DEBUG=False. 144 | # See http://docs.djangoproject.com/en/dev/topics/logging for 145 | # more details on how to customize your logging configuration. 146 | LOGGING = { 147 | 'version': 1, 148 | 'disable_existing_loggers': False, 149 | 'filters': { 150 | 'require_debug_false': { 151 | '()': 'django.utils.log.RequireDebugFalse' 152 | } 153 | }, 154 | 'handlers': { 155 | 'mail_admins': { 156 | 'level': 'ERROR', 157 | 'filters': ['require_debug_false'], 158 | 'class': 'django.utils.log.AdminEmailHandler' 159 | } 160 | }, 161 | 'loggers': { 162 | 'django.request': { 163 | 'handlers': ['mail_admins'], 164 | 'level': 'ERROR', 165 | 'propagate': True, 166 | }, 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /demo/demo/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

It works !

7 | 8 | 9 | -------------------------------------------------------------------------------- /demo/demo/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.shortcuts import render 3 | 4 | 5 | def home(request): 6 | return render(request, 'home.html') 7 | 8 | # Uncomment the next two lines to enable the admin: 9 | # from django.contrib import admin 10 | # admin.autodiscover() 11 | 12 | urlpatterns = [ 13 | url('', home), 14 | # Examples: 15 | # url(r'^$', 'demo.views.home', name='home'), 16 | # url(r'^demo/', include('demo.foo.urls')), 17 | 18 | # Uncomment the admin/doc line below to enable admin documentation: 19 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 20 | 21 | # Uncomment the next line to enable the admin: 22 | # url(r'^admin/', include(admin.site.urls)), 23 | ] 24 | -------------------------------------------------------------------------------- /demo/demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 19 | # if running multiple sites in the same mod_wsgi process. To fix this, use 20 | # mod_wsgi daemon mode with each site in its own daemon process, or use 21 | # os.environ["DJANGO_SETTINGS_MODULE"] = "demo.settings" 22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 23 | 24 | # This application object is used by any WSGI server configured to use this 25 | # file. This includes Django's development server, if the WSGI_APPLICATION 26 | # setting points here. 27 | from django.core.wsgi import get_wsgi_application 28 | application = get_wsgi_application() 29 | 30 | # Apply WSGI middleware here. 31 | # from helloworld.wsgi import HelloWorldApplication 32 | # application = HelloWorldApplication(application) 33 | -------------------------------------------------------------------------------- /demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django>=1.8 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | import sslserver 4 | 5 | setup(name="django-sslserver", 6 | version=sslserver.__version__, 7 | author="Ted Dziuba", 8 | author_email="tjdziuba@gmail.com", 9 | description="An SSL-enabled development server for Django", 10 | url="https://github.com/teddziuba/django-sslserver", 11 | packages=find_packages(exclude=['demo']), 12 | include_package_data=True, 13 | install_requires=["Django >= 1.8"], 14 | license="MIT", 15 | classifiers=[ 16 | "Environment :: Web Environment", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Programming Language :: Python", 20 | "Programming Language :: Python :: 2", 21 | "Programming Language :: Python :: 2.7", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3.4", 24 | "Programming Language :: Python :: 3.5", 25 | "Programming Language :: Python :: 3.6", 26 | "Programming Language :: Python :: 3.7", 27 | "Framework :: Django", 28 | "Framework :: Django :: 1.8", 29 | "Framework :: Django :: 1.9", 30 | "Framework :: Django :: 1.10", 31 | "Framework :: Django :: 1.11", 32 | "Framework :: Django :: 2.0", 33 | "Framework :: Django :: 2.1", 34 | "Framework :: Django :: 2.2"], 35 | ) 36 | -------------------------------------------------------------------------------- /sslserver/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.20" 2 | -------------------------------------------------------------------------------- /sslserver/certs/development.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGSTCCBDGgAwIBAgIJAPc4JnQC+MORMA0GCSqGSIb3DQEBCwUAMIG6MQswCQYD 3 | VQQGEwJVUzEaMBgGA1UECAwRREVWRUxPUE1FTlQgU1RBVEUxGTAXBgNVBAcMEERF 4 | VkVMT1BNRU5UIENJVFkxHDAaBgNVBAoME0RFVkVMT1BNRU5UIENPTVBBTlkxGjAY 5 | BgNVBAsMEURKQU5HTyBERVZFTE9QRVJTMRIwEAYDVQQDDAlsb2NhbGhvc3QxJjAk 6 | BgkqhkiG9w0BCQEWF2RldmVsb3BtZW50QGV4YW1wbGUuY29tMB4XDTE3MDcyMzIy 7 | NTMxOVoXDTI3MDcyMTIyNTMxOVowgboxCzAJBgNVBAYTAlVTMRowGAYDVQQIDBFE 8 | RVZFTE9QTUVOVCBTVEFURTEZMBcGA1UEBwwQREVWRUxPUE1FTlQgQ0lUWTEcMBoG 9 | A1UECgwTREVWRUxPUE1FTlQgQ09NUEFOWTEaMBgGA1UECwwRREpBTkdPIERFVkVM 10 | T1BFUlMxEjAQBgNVBAMMCWxvY2FsaG9zdDEmMCQGCSqGSIb3DQEJARYXZGV2ZWxv 11 | cG1lbnRAZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC 12 | AQC44Me+Wb3x9kfW9BfHqNpDcQS3sEZuboirglbz6cLej2gIb6zaz93MFGjw4nyp 13 | 6lfm/Rss56gCfcVwmrrxZFjqnKLbIM7b2Fes3VeMFOGtYGFS4X60EPq11T5MG2VX 14 | 8ARJnDRX4oKWaMawUg3UrjTOWadoBwdp+k3EWLyOc9miMHOrPhmKiaYcZJdM78nl 15 | TLG7DqMStltVpDn4IuJWkw0Y9QYeRAESsQ+DLp+jvIRv7DJ7UjOAXa0IpiJyr1bJ 16 | 7qcCBd6/Yxz8lj/2yQrOSvHHjN69eYB8gcZaVoP++N8uYyea205UmzR/b3KUhFII 17 | wUXJKhWnsf0Yv4a/9RU8/ZVxPsw+43bvnW/VvFMXc7+Rm8gJHg5qIXJB+2AmbM0k 18 | g7DwgkM6f+DLn7m/SIZrgk6Pv2Zrv+k6AkPuCcTD8VY5yYRAddJZ9cWKIwCYUwVO 19 | 32+UKN8A41xShB7XPyd9VtpPMhAZQrwvuWJTbiWYoQGXyktGSKr3GGe6keU8NNxW 20 | QU4Hn0+nhe/LOzHYw7sSoTjgBfvP8By+ei4zhiWUetyogd18m9F1HnkOJHdXn8uf 21 | Fy0kblCWu83jIGzDwW8uke2Osdm4N5hJL2Qj0AXGOWON4OM6JeJETuXjZXsqAqWV 22 | ZqM4A7gGo12rIRfg2cZ9tx3JhOhvSMWBE5MA9x7SGT2fAQIDAQABo1AwTjAdBgNV 23 | HQ4EFgQUyjHyDG2Yos8dxhoiUT04oVR+JdwwHwYDVR0jBBgwFoAUyjHyDG2Yos8d 24 | xhoiUT04oVR+JdwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEASpqo 25 | aUHGEMGHpszdYowLRq9me8XqJkxFWuKPIZgZL/fb6nf5hjICtjpZGic79x13A2eV 26 | paXPmQa7MK1JbGGnVEJN07W7CKnwB2lgV/WZuARDPzJ/9anLtRTs8OhuOIcDYIsg 27 | f5pfTwWosNwZej1coosK04Dp8/8gt0qq6Ri1nH/HV5r7bzYmtMquv6N1sKtS4QGG 28 | lPBnGptToj4zN9x0w57JvUzGnUAVwuaRJerizmxMggS1j2Q3iK70xG9860fMJvCC 29 | pX2tsOjHsyxw/qRYZPeANGQlwVX+93cV4LV7XE4tK2QvyUOJMjwqB1EMmJEcSDXw 30 | 65zWZrCTpVsSkPmlQcL7gVYWPGyPpAWJICuvdvG4klbAcQS9EhK8w4rnrV2B3wzm 31 | KNp1c48gmIOsxRYYOQYB9TKfqmv6icYUPZ7wUhahYyf5z5EMnvfXEyflmSAsHOy6 32 | kkSLd8TfC8VQxAhL87DeCEkWkDl1xeEcPuRu3gOm90dxVZfphrqlBZ9hW9j7dysg 33 | Xp2xSqDYOcyKAMR5mMajOogE8vDXgoSRkQK+/HYCNCtKthVIXT2x5h8VP8MGFWNH 34 | YBQwmKnIT6PT2DV2TgpF3X9T9lLdPbg+TwnaoBuYxCIIEbTK+LNKnqaj2dLCxdOq 35 | VjVM6fboLD/7AqtVyld7pX/5FbcD3PaZURQw3p0= 36 | -----END CERTIFICATE----- 37 | -------------------------------------------------------------------------------- /sslserver/certs/development.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC44Me+Wb3x9kfW 3 | 9BfHqNpDcQS3sEZuboirglbz6cLej2gIb6zaz93MFGjw4nyp6lfm/Rss56gCfcVw 4 | mrrxZFjqnKLbIM7b2Fes3VeMFOGtYGFS4X60EPq11T5MG2VX8ARJnDRX4oKWaMaw 5 | Ug3UrjTOWadoBwdp+k3EWLyOc9miMHOrPhmKiaYcZJdM78nlTLG7DqMStltVpDn4 6 | IuJWkw0Y9QYeRAESsQ+DLp+jvIRv7DJ7UjOAXa0IpiJyr1bJ7qcCBd6/Yxz8lj/2 7 | yQrOSvHHjN69eYB8gcZaVoP++N8uYyea205UmzR/b3KUhFIIwUXJKhWnsf0Yv4a/ 8 | 9RU8/ZVxPsw+43bvnW/VvFMXc7+Rm8gJHg5qIXJB+2AmbM0kg7DwgkM6f+DLn7m/ 9 | SIZrgk6Pv2Zrv+k6AkPuCcTD8VY5yYRAddJZ9cWKIwCYUwVO32+UKN8A41xShB7X 10 | Pyd9VtpPMhAZQrwvuWJTbiWYoQGXyktGSKr3GGe6keU8NNxWQU4Hn0+nhe/LOzHY 11 | w7sSoTjgBfvP8By+ei4zhiWUetyogd18m9F1HnkOJHdXn8ufFy0kblCWu83jIGzD 12 | wW8uke2Osdm4N5hJL2Qj0AXGOWON4OM6JeJETuXjZXsqAqWVZqM4A7gGo12rIRfg 13 | 2cZ9tx3JhOhvSMWBE5MA9x7SGT2fAQIDAQABAoICAGE2qTF5hPyfUQVBkuOE8Ug3 14 | PJDOWyqkaq8suUZGrBZVeBG/jzf7x5gRP2Ey5vR96jzm6IxDP8AXnGNqqVRXPL4Z 15 | ITVyWfOWHfwi428gZ86/+E0Zj4Nlzj8g40InwbFs3wRJ4+g2jg5DB/8dFnK4K8I5 16 | uGJmF8rtOEk/k96gIlc7fQcVf16upK8XYcut5w7wtiAzQSKMyxgMZAMLbNKzPlba 17 | OJ5fieTnP83uDTmO3mDv5VedUMF2AI+ktEZz1oIWQLE6nvcqScqzwuykqE7zb5FL 18 | jLA7HInbv44vIfdHrCt7Lg8qtFSezAsacjiQH4bFAkoWHA7y87A4vcVgPQu2GAbv 19 | k3RCGbvVzaAq+INudeQWl3kwwvnugrxjZ67O+UJRieXPy0mqtG0xxbiRsP8ifnIi 20 | dPXFoP8BUcz8H/ntXfIve/5PCH2mWd48rpKZWZes3HQXYAupAeBRX5HXfFTjYVVT 21 | Lf39ROJlIZyXFxCTuAohP4RaQscwdh3xifSgQS0Bc+NzxtOBeziu1y4Tsg7OCr2p 22 | fvcQzJrnjXdO0/dsMndtsjYWXD1IkoQuCswA6iLNyBgH5J5TwFm3g/NzfOhRd77V 23 | XvN8fLLyGNxQu7H9kYKMuQEM7BPZbRwHLhLqQIE+yhfy7VUE/DwLey4ca+zAO3s1 24 | FGqe8zjU08UMdO5ZZZe1AoIBAQDuCXNCjp3QI5YyJh9BhtFbA75bVNrgEQL0H87D 25 | hNNiZXzlavzoLE5kUWxIxPAlLiqtz6dYHLayfSAxoeN4B8DAES+M2ZtaJkdkxEg3 26 | a52IFvlQSsqzqIlzWqNrgfdhphDLfBFUONHWDQMiyyetm5ziQHU0a24ZSmGli8Q/ 27 | ogPMUsu8QoH2w4JdLPEv7zeSZQQrznfeiBqiE8r7tzHbVZAl00JBSqRUhgfgwWao 28 | h+IFIvESQIZEhEc7gzPa2g1kmHE6qbRKOrF1KXVV+MJkU3+/X1HlrAPP+ESfRPPy 29 | BSqHDwNYSsnuEzPnehY9FujzHtiIytJEYh7M8UwJI/K3DtYXAoIBAQDG1F94XJ26 30 | AIrr8IbXj0EEresfOcI8H3GScxYRiUOFrzV3cMU5wttliUZ8b0b+zNjcQfeP/WOU 31 | 0+I8kKC9l4txJxJ2Yrl9nwiSiLcCtJWbReg/LPAS8R0+2tYIHxz+HAN4+CVqCc5j 32 | UIvng6h1pBXGGmuAOSK9Jy4P8Op/0aKV5hAY6ELc993ZYJNBVj2x/qDBWmyvXk8N 33 | TBerfJ/KLTfe7b2Tv1QIayukOXGNGaZTDnVqZTIIkLpNAW5EjYJbO97EPzcqwyeM 34 | YXxnKsLw09KQ47Orzt5b7pUjGWygUQ2YuLokBZ1h+9NORh0aaKDhcQNUWz50ZEbm 35 | nIW/krdmX3qnAoIBAHiSONvBuZYwMGf+f16Fl43ue4Fvnd5ZP5HAes72fMwedAER 36 | DEHeD2cpBxxlBO8QT3Xy9OTgxnnqf2AkjD/ETZH8IoXJw7MKKKeV9K59yDGi0W4m 37 | ND6EY/sWMijGkDPaFi+8DFsI3OD7flclaFe7tt+znqqKsB4HWyargSBqRFo+pgwZ 38 | 9BK2PVZM6W5KUW/J0y8NShJiBnuKYwrVZF+hG37NX4YK9OoLafoifIlpKsdHR6zj 39 | Gh+ikSJH9wAn6xjFTrVUOIpQ6IpcEZ8WfgCres3IzobzdorlSn0SRC6XB0+Z/yu9 40 | 1kLNX0Ekyhbvf9eGDFBdy9vvh4Jvxv/qQYsP7MkCggEAcMY8cFR30exE35ZJpTJH 41 | upO67LciCF/9VweX2NJ2TfkW5mw8MYroTisSo7mqFxOxXRy9ghpCxUN46slNoru2 42 | B7QVsLhGZY4ymoqY4xvyT2PTWyHt+ZBw+hm9lFWxaCATiCWXcxGk0of5EpOGlVNA 43 | VmDd4JiHr6ntYnVPTLhm84bOUpJKenLMjmNMop57192I6KKVStK/G9exBT9mkpIb 44 | QoKuKIM48p1232sFkPafdDSbPIGxY1JTmSHanV92NLE8pSsF62dNoz4YBZwnfdln 45 | cW71ldl8HcCxLntZNYVDpQAB9DSLE+x16cuy7iuyNKyZKtumDPZjkKHu6r4Y8CPv 46 | uQKCAQEAlkgFgNAsiOtPwFBZ6FW3q/YrlFE+hdhIieIZkS41qxAjgteiRMCANv7B 47 | SvFO3i4bzwuB3WtxnGSsk+uybb4u+Sju6cwWnYk5plpvG5w2I1i9Udmd+UDCblLy 48 | zmy12NHMkmQp9VGaFujjbOB/pboIQvocvqPj6FngCwLYPpmx8w0EG9LNCosPFCo6 49 | 6T4413Iadde0g55MpxC7SvOWK08uxBXVksEacGBD1qZ8ghB6CbFoTGymI3Gvj8Wl 50 | Mm98jE/Xih/zgp8YXe4i7N8wDiV/Jn16Ic5U1uQtfiNhEwBCIyKYeuW/ybaLT8IV 51 | ywoqq7w2QdhJK/vom01mFY8wglE8lQ== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /sslserver/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddziuba/django-sslserver/27c6f467524583fc522dc7b6792f166960c60210/sslserver/management/__init__.py -------------------------------------------------------------------------------- /sslserver/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teddziuba/django-sslserver/27c6f467524583fc522dc7b6792f166960c60210/sslserver/management/commands/__init__.py -------------------------------------------------------------------------------- /sslserver/management/commands/runsslserver.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import os 4 | import ssl 5 | import sys 6 | 7 | try: 8 | from pathlib import Path 9 | except ImportError: 10 | # removed in django version 3 11 | from django.utils._os import upath 12 | 13 | from django.core.servers.basehttp import WSGIRequestHandler 14 | from django.core.management.base import CommandError 15 | from django.core.management.commands import runserver 16 | from django.contrib.staticfiles.handlers import StaticFilesHandler 17 | 18 | 19 | if (sys.version_info[0] > 3) or \ 20 | (sys.version_info[0] == 3 and sys.version_info[1] >= 6) or \ 21 | (sys.version_info[0] == 3 and sys.version_info[1] == 5 and sys.version_info[2] >= 3) or \ 22 | (sys.version_info[0] == 2 and sys.version_info[1] == 7 and sys.version_info[2] >= 13): 23 | _ssl_version = ssl.PROTOCOL_TLS 24 | else: 25 | _ssl_version = ssl.PROTOCOL_SSLv23 26 | 27 | try: 28 | from django.core.servers.basehttp import WSGIServerException 29 | except ImportError: 30 | from socket import error as WSGIServerException 31 | 32 | try: 33 | # introduced in Django 2.0 34 | from django.core.servers.basehttp import ThreadedWSGIServer 35 | except ImportError: 36 | try: 37 | import socketserver 38 | except ImportError: 39 | # Python 2 compatibility 40 | import SocketServer as socketserver 41 | from django.core.servers.basehttp import WSGIServer 42 | 43 | class ThreadedWSGIServer(socketserver.ThreadingMixIn, WSGIServer): 44 | """A threaded version of the WSGIServer""" 45 | pass 46 | 47 | 48 | class SecureHTTPServer(ThreadedWSGIServer): 49 | def __init__(self, address, handler_cls, certificate, key, ipv6=False): 50 | super(SecureHTTPServer, self).__init__(address, handler_cls, ipv6=ipv6) 51 | self.socket = ssl.wrap_socket(self.socket, certfile=certificate, 52 | keyfile=key, server_side=True, 53 | ssl_version=_ssl_version, 54 | cert_reqs=ssl.CERT_NONE) 55 | 56 | 57 | class WSGIRequestHandler(WSGIRequestHandler): 58 | def get_environ(self): 59 | env = super(WSGIRequestHandler, self).get_environ() 60 | env['HTTPS'] = 'on' 61 | return env 62 | 63 | 64 | def default_ssl_files_dir(): 65 | import sslserver as app_module 66 | 67 | try: 68 | ssl_dir = str(Path(app_module.__file__).parent / 'certs') 69 | except NameError: 70 | # Django < 3 backwards compatibility 71 | mod_path = os.path.dirname(upath(app_module.__file__)) 72 | ssl_dir = os.path.join(mod_path, "certs") 73 | 74 | return ssl_dir 75 | 76 | 77 | class Command(runserver.Command): 78 | def add_arguments(self, parser): 79 | super(Command, self).add_arguments(parser) 80 | parser.add_argument("--certificate", 81 | default=os.path.join(default_ssl_files_dir(), 82 | "development.crt"), 83 | help="Path to the certificate"), 84 | parser.add_argument("--key", 85 | default=os.path.join(default_ssl_files_dir(), 86 | "development.key"), 87 | help="Path to the key file"), 88 | parser.add_argument("--nostatic", dest='use_static_handler', 89 | action='store_false', default=None, 90 | help="Do not use internal static file handler"), 91 | parser.add_argument("--static", dest='use_static_handler', 92 | action='store_true', 93 | help="Use internal static file handler"), 94 | 95 | help = "Run a Django development server over HTTPS" 96 | 97 | def get_handler(self, *args, **options): 98 | """ 99 | Returns the static files serving handler wrapping the default handler, 100 | if static files should be served. Otherwise just returns the default 101 | handler. 102 | 103 | """ 104 | handler = super(Command, self).get_handler(*args, **options) 105 | insecure_serving = options.get('insecure_serving', False) 106 | if self.should_use_static_handler(options): 107 | return StaticFilesHandler(handler) 108 | return handler 109 | 110 | def should_use_static_handler(self, options): 111 | # it's a bit weird to import settings in the middle of the method, but 112 | # this is what inner_run does 113 | from django.conf import settings 114 | use_static_handler = options.get('use_static_handler') 115 | if use_static_handler: 116 | return True 117 | if (use_static_handler is None and 118 | 'django.contrib.staticfiles' in settings.INSTALLED_APPS): 119 | return True 120 | return False 121 | 122 | def check_certs(self, key_file, cert_file): 123 | # TODO: maybe validate these? wrap_socket doesn't... 124 | 125 | if not os.path.exists(key_file): 126 | raise CommandError("Can't find key at %s" % key_file) 127 | if not os.path.exists(cert_file): 128 | raise CommandError("Can't find certificate at %s" % 129 | cert_file) 130 | 131 | 132 | def inner_run(self, *args, **options): 133 | # Django did a shitty job abstracting this. 134 | 135 | key_file = options.get("key") 136 | cert_file = options.get("certificate") 137 | self.check_certs(key_file, cert_file) 138 | 139 | from django.conf import settings 140 | from django.utils import translation 141 | 142 | threading = options.get('use_threading') 143 | shutdown_message = options.get('shutdown_message', '') 144 | quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C' 145 | 146 | self.stdout.write("Validating models...\n\n") 147 | self.check(display_num_errors=True) 148 | self.stdout.write(( 149 | "%(started_at)s\n" 150 | "Django version %(version)s, using settings %(settings)r\n" 151 | "Starting development server at https://%(addr)s:%(port)s/\n" 152 | "Using SSL certificate: %(cert)s\n" 153 | "Using SSL key: %(key)s\n" 154 | "Quit the server with %(quit_command)s.\n" 155 | ) % { 156 | "started_at": datetime.now().strftime('%B %d, %Y - %X'), 157 | "version": self.get_version(), 158 | "settings": settings.SETTINGS_MODULE, 159 | "addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr, 160 | "port": self.port, 161 | "quit_command": quit_command, 162 | "cert": cert_file, 163 | "key": key_file 164 | }) 165 | # django.core.management.base forces the locale to en-us. We should 166 | # set it up correctly for the first request (particularly important 167 | # in the "--noreload" case). 168 | translation.activate(settings.LANGUAGE_CODE) 169 | 170 | try: 171 | handler = self.get_handler(*args, **options) 172 | server = SecureHTTPServer((self.addr, int(self.port)), 173 | WSGIRequestHandler, 174 | cert_file, key_file, ipv6=self.use_ipv6) 175 | server.set_app(handler) 176 | server.serve_forever() 177 | 178 | except WSGIServerException: 179 | e = sys.exc_info()[1] 180 | # Use helpful error messages instead of ugly tracebacks. 181 | ERRORS = { 182 | 13: "You don't have permission to access that port.", 183 | 98: "That port is already in use.", 184 | 99: "That IP address can't be assigned-to.", 185 | } 186 | try: 187 | error_text = ERRORS[e.args[0].args[0]] 188 | except (AttributeError, KeyError): 189 | error_text = str(e) 190 | self.stderr.write("Error: %s" % error_text) 191 | # Need to use an OS exit because sys.exit doesn't work in a thread 192 | os._exit(1) 193 | except KeyboardInterrupt: 194 | if shutdown_message: 195 | self.stdout.write(shutdown_message) 196 | sys.exit(0) 197 | 198 | -------------------------------------------------------------------------------- /test.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect 2 | 3 | spawn python demo/manage.py runsslserver 4 | 5 | expect "Starting development server at https://127.0.0.1:8000/" 6 | expect "Quit the server with CONTROL-C." 7 | 8 | send "\x03" 9 | --------------------------------------------------------------------------------