Commit 1d0fa9e7 authored by Chris Hines's avatar Chris Hines
Browse files

add setup, add comments, refactor function names

parent d5dc07c5
Pipeline #9338 canceled with stages
"""A setuptools based setup module.
See:
https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject
"""
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path
here = path.abspath(path.dirname(__file__))
long_description = 'Generate a SSH Certificate from an OAuth2 compliat cert server'
setup(
name='ssossh',
version='0.0.1',
description=long_description,
long_description=long_description,
# The project's main homepage.
url='https://gitlab.erc.monash.edu.au/hpc-team/ssossh',
# Author details
author='Chris Hines',
author_email='chris.hines@monash.edu',
# Choose your license
license='MIT',
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
'Development Status :: 4 - Beta',
# Pick your license as you wish (should match "license" above)
'License :: OSI Approved :: MIT License',
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
# What does your project relate to?
keywords='',
# You can just specify the packages manually here if your project is
# simple. Or you can use find_packages().
# Alternatively, if you want to distribute just a my_module.py, uncomment
# this:
# py_modules=["my_module"],
# List run-time dependencies here. These will be installed by pip when
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=[
'jinja2',
'requests'
],
data_files = [('',[])],
# To provide executable scripts, use entry points in preference to the
# "scripts" keyword. Entry points provide cross-platform support and allow
# pip to create the appropriate form of executable for the target platform.
entry_points={
'console_scripts': [ 'ssossh=ssossh.__main__:main'],
'gui_scripts': [ ]
},
)
import json
import webbrowser
import os
from functools import partial
from http.server import HTTPServer, BaseHTTPRequestHandler
from jinja2 import Template
import queue
import subprocess
import requests
import pathlib
from functools import partial
from http.server import HTTPServer, BaseHTTPRequestHandler
from jinja2 import Template
q = queue.Queue()
q=queue.Queue()
class MyRequestHandler(BaseHTTPRequestHandler):
def __init__(self, port, logout, *args,**kwargs):
"""
The request handler runs twice. Once serves a snippet of
javascript which takes the token in the URL framgment and puts in into
a request parameter.
The second run receives the token as a request parameter and puts it in the queue
"""
def __init__(self, port, logout, *args, **kwargs):
self.port = port
self.logout = logout
super(MyRequestHandler, self).__init__(*args, **kwargs)
......@@ -21,27 +28,33 @@ class MyRequestHandler(BaseHTTPRequestHandler):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
def do_GET(self, *args, **kwargs):
if "callback" in self.path:
self._set_headers()
currpath = pathlib.Path(__file__).parent.absolute()
with open(os.path.join(currpath,'templates','call_back.html'),'rb') as f:
with open(os.path.join(currpath, 'templates', 'call_back.html'),
'rb') as f:
tsrc = f.read()
t = Template(tsrc.decode())
self.wfile.write(t.render(port=self.port,logout=self.logout).encode())
self.wfile.write(t.render(port=self.port,
logout=self.logout).encode())
return
else:
q.put(self.path)
def make_key():
"""
Generate a keyfile (using ssh-keygen)
"""
keypath = os.path.expanduser('~/.ssh/ssossh-key')
try:
mode = os.stat(keypath).st_mode
import stat
if stat.S_ISREG(mode):
try:
rm_cert(keypath)
rm_key_agent(keypath)
except subprocess.CalledProcessError:
pass
try:
......@@ -58,42 +71,68 @@ def make_key():
pass
except FileNotFoundError:
pass
subprocess.call(['ssh-keygen','-N','','-f',keypath],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
subprocess.call(['ssh-keygen', '-N', '', '-f', keypath],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
return keypath
def sign_cert(keypath,token,url):
with open(keypath+'.pub','r') as f:
def sign_cert(keypath, token, url):
"""
give a public key and a OAuth2 token,
use the token to access the signing endpoint and save
the resulting certificate
"""
with open(keypath + '.pub', 'r') as f:
pub_key = f.read()
sess = requests.Session()
headers = {"Authorization":"Bearer %s"%token}
data = {"public_key":pub_key}
headers = {"Authorization": "Bearer {}".format(token)}
data = {"public_key": pub_key}
resp = sess.post(url, json=data, headers=headers, verify=False)
data = resp.json()
cert = data['certificate']
with open(keypath+"-cert.pub",'w') as f:
with open(keypath + "-cert.pub", 'w') as f:
f.write(cert)
def start_agent():
pass
def rm_cert(keypath):
subprocess.call(['ssh-add','-d',keypath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def rm_key_agent(keypath):
"""
Remove the key/cert from the agent
"""
subprocess.call(['ssh-add', '-d', keypath],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
def add_key_agent(keypath):
"""
Add the key and cert to the agent
"""
subprocess.call(['ssh-add', keypath],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
def add_cert(keypath):
subprocess.call(['ssh-add',keypath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def do_request(authservice):
"""
Open a web browser window
and request an OAuth2 token to sign certificates
We must service two http requests
The first is the OAuth2 callback with the token in the URL fragment
(as specified by OAuth2 Impliciy flow standards)
The second has the token in the query parameters
so that the backend can read it
"""
nonce = os.urandom(8).hex()
redirect_uri = "http://localhost:4200/sshauthz_callback"
requrl = (authservice['authorise'] + "?response_type=token&redirect_uri=" +
redirect_uri + "&state="+nonce +"&client_id=" +
authservice['client_id'] + "&scope=" + authservice['scope'])
redirect_uri + "&state=" + nonce + "&client_id=" +
authservice['client_id'] + "&scope=" + authservice['scope'])
webbrowser.open(requrl)
port=4200
port = 4200
server_address = ('', port)
handler = partial(MyRequestHandler, port, authservice['logout'])
httpd = HTTPServer(server_address, handler)
httpd = HTTPServer(server_address, handler)
httpd.handle_request()
httpd.handle_request()
path = q.get()
......@@ -101,20 +140,28 @@ def do_request(authservice):
token = params[0].split('=')[1]
state = params[1].split('=')[1]
if not state == nonce:
raise Exception('It looks like someone is playing silly buggers and intercepting messages between you and the authentication server')
raise Exception('OAuth2 error: A securit check failed')
return token
def main():
"""
Read the authservers.json config file
Get an OAuth2 Implicit token
Generate an SSH key pair
Use the OAuth2 token to create a certificate from the pub key
Add the certificate to the users agent
"""
currpath = pathlib.Path(__file__).parent.absolute()
with open(os.path.join(currpath,'authservers.json'),'r') as f:
with open(os.path.join(currpath, 'authservers.json'), 'r') as f:
config = json.loads(f.read())
authservice = config[0]
token = do_request(authservice)
path = make_key()
sign_cert(path,token,authservice['sign'])
add_cert(path)
sign_cert(path, token, authservice['sign'])
add_key_agent(path)
if __name__ == '__main__':
main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment