├── django_conch ├── __init__.py ├── models.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── conch.py └── checker.py ├── .gitignore ├── setup.py ├── UNLICENSE └── README.md /django_conch/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_conch/models.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /django_conch/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_conch/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | .DS_Store 4 | dist 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-conch', 5 | version='0.0.1', 6 | description='Expose the Django shell as an SSH server.', 7 | author='Zachary Voase', 8 | author_email='z@zacharyvoase.com', 9 | url='https://github.com/zacharyvoase/django-conch', 10 | packages=find_packages(), 11 | install_requires=[ 12 | 'Twisted>=12.3.0', 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /django_conch/checker.py: -------------------------------------------------------------------------------- 1 | from twisted.cred import checkers, error, credentials 2 | from twisted.internet import defer 3 | from twisted.python import failure 4 | from zope.interface import implements 5 | 6 | from django.contrib.auth.models import User, check_password 7 | 8 | 9 | class DjangoSuperuserCredChecker: 10 | implements(checkers.ICredentialsChecker) 11 | 12 | credentialInterfaces = (credentials.IUsernamePassword, 13 | credentials.IUsernameHashedPassword) 14 | 15 | user_queryset = User.objects.filter(is_superuser=True) 16 | 17 | def passwordMatched(self, matched, user): 18 | if matched: 19 | return user.username 20 | return failure.Failure(error.UnauthorizedLogin()) 21 | 22 | def requestAvatarId(self, credentials): 23 | try: 24 | user = self.user_queryset.get(username=credentials.username) 25 | return defer.maybeDeferred( 26 | check_password, 27 | credentials.password, 28 | user.password).addCallback(self.passwordMatched, user) 29 | except User.DoesNotExist: 30 | return defer.fail(error.UnauthorizedLogin()) 31 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-conch 2 | 3 | Run an SSH server which provides direct access to the Django shell for 4 | superusers. 5 | 6 | 7 | ## Installation 8 | 9 | pip install django_conch 10 | 11 | Then just add `'django_conch'` to your `INSTALLED_APPS` setting. Done. 12 | 13 | 14 | ## Usage 15 | 16 | django-admin.py conch -p 1234 17 | 18 | This runs the SSH server on port 1234—it's best to execute this under your 19 | daemonization tool of choice. 20 | 21 | ssh -p 1234 username@localhost 22 | 23 | Run this to access the shell. All the models in your project will be imported 24 | into the top-level namespace for you. 25 | 26 | 27 | ## (Un)license 28 | 29 | This software is released under the [Unlicense](http://unlicense.org/). 30 | 31 | > This is free and unencumbered software released into the public domain. 32 | > 33 | > Anyone is free to copy, modify, publish, use, compile, sell, or 34 | > distribute this software, either in source code form or as a compiled 35 | > binary, for any purpose, commercial or non-commercial, and by any 36 | > means. 37 | > 38 | > In jurisdictions that recognize copyright laws, the author or authors 39 | > of this software dedicate any and all copyright interest in the 40 | > software to the public domain. We make this dedication for the benefit 41 | > of the public at large and to the detriment of our heirs and 42 | > successors. We intend this dedication to be an overt act of 43 | > relinquishment in perpetuity of all present and future rights to this 44 | > software under copyright law. 45 | > 46 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 47 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 48 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 49 | > IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 50 | > OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 51 | > ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 52 | > OTHER DEALINGS IN THE SOFTWARE. 53 | > 54 | > For more information, please refer to 55 | -------------------------------------------------------------------------------- /django_conch/management/commands/conch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from optparse import make_option 3 | import json 4 | import os 5 | 6 | from django.core.management.base import BaseCommand 7 | from django.db.models.loading import get_models, get_apps 8 | from twisted.application import internet, service 9 | from twisted.conch.insults import insults 10 | from twisted.conch.manhole import ColoredManhole 11 | from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm 12 | from twisted.cred import portal 13 | 14 | from django_conch.checker import DjangoSuperuserCredChecker 15 | 16 | 17 | def make_namespace(): 18 | ns = {} 19 | for app in get_apps(): 20 | for model in get_models(app): 21 | ns[model.__name__] = model 22 | ns[model._meta.app_label + '_' + model._meta.object_name] = model 23 | return ns 24 | 25 | 26 | def make_service(args): 27 | checker = DjangoSuperuserCredChecker() 28 | 29 | def chainProtocolFactory(): 30 | return insults.ServerProtocol( 31 | args['protocol_factory'], 32 | *args.get('protocol_args', ()), 33 | **args.get('protocol_kwargs', {})) 34 | 35 | realm = TerminalRealm() 36 | realm.chainedProtocolFactory = chainProtocolFactory 37 | ptl = portal.Portal(realm, [checker]) 38 | f = ConchFactory(ptl) 39 | return internet.TCPServer(args['ssh_port'], f) 40 | 41 | 42 | application = service.Application("Django Secure Shell") 43 | 44 | if '_DJANGO_CONCH_CONFIG' in os.environ: 45 | config = json.loads(os.environ['_DJANGO_CONCH_CONFIG']) 46 | namespace = make_namespace() 47 | make_service({'protocol_factory': ColoredManhole, 48 | 'protocol_kwargs': {'namespace': namespace}, 49 | 'ssh_port': config['ssh_port']}).setServiceParent(application) 50 | 51 | 52 | class Command(BaseCommand): 53 | 54 | help = 'Run a Django secure shell server on ' 55 | option_list = BaseCommand.option_list + ( 56 | make_option('-p', '--port', type='int', 57 | help='The port number on which to listen for SSH connections'), 58 | ) 59 | 60 | def handle(self, *args, **kwargs): 61 | cmd = ['twistd', '-noy', __file__] 62 | os.environ['_DJANGO_CONCH_CONFIG'] = json.dumps({ 63 | 'ssh_port': kwargs['port']}) 64 | os.execvp('twistd', cmd) 65 | --------------------------------------------------------------------------------