Commit 719959e0 authored by Vincent Pelletier's avatar Vincent Pelletier

wsgi: Become web-friendly

Self-describe site structure in application/hal+json format.
Add Cross-Origin Resource Sharing support: pre-flight request support,
same-origin-only origin access control minimal html page. Access control
decision is stored client-side in a signed & time-limited cookie
supporting multiple concurrent origins. Origins may be pre-allowed (ex:
when caucase GUI is served from a trusted server).
parent 7ff81404
...@@ -196,6 +196,26 @@ HTTPS. ...@@ -196,6 +196,26 @@ HTTPS.
It handles its own certificate issuance and renewal, so there is no need to use It handles its own certificate issuance and renewal, so there is no need to use
`caucase-updater`_ for this service. `caucase-updater`_ for this service.
CORS
----
caucased implements CORS protection: when receiving a cross-origin request,
it will respond with 401 Unauthorized, with the WWW-Authenticate header set to
a custom scheme ("cors") with an "url" parameter containing an URI template
with one variable field: "return" (more on it later).
Upon receiving this response, the application is expected to render the URL
template and redirect the user to resulting URL. There, the user will be
informed of the cross-origin access attempt, and offered the choice to grant or
deny access to given origin.
Once their decision is made, their browser will receive a cookie remembering
this decision, and they will be redirected to the URL received in the "return"
field received upon above-described redirection.
Then, the application should retry the original request, which will be
accompanied by that cookie.
Backups Backups
------- -------
......
...@@ -25,6 +25,7 @@ import datetime ...@@ -25,6 +25,7 @@ import datetime
from getpass import getpass from getpass import getpass
import glob import glob
import itertools import itertools
import json
import os import os
import socket import socket
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
...@@ -42,7 +43,7 @@ import pem ...@@ -42,7 +43,7 @@ import pem
from . import exceptions from . import exceptions
from . import utils from . import utils
from . import version from . import version
from .wsgi import Application from .wsgi import Application, CORSTokenManager
from .ca import CertificateAuthority, UserCertificateAuthority, Extension from .ca import CertificateAuthority, UserCertificateAuthority, Extension
from .storage import SQLite3Storage from .storage import SQLite3Storage
from .http_wsgirequesthandler import WSGIRequestHandler from .http_wsgirequesthandler import WSGIRequestHandler
...@@ -356,6 +357,19 @@ def main(argv=None, until=utils.until): ...@@ -356,6 +357,19 @@ def main(argv=None, until=utils.until):
help='Path to the ssl key pair to use for https socket. ' help='Path to the ssl key pair to use for https socket. '
'default: %(default)s', 'default: %(default)s',
) )
parser.add_argument(
'--cors-key-store',
default='cors.key',
metavar='PATH',
help='Path to a file containing CORS token keys. default: %(default)s',
)
parser.add_argument(
'--cors-whitelist',
default=[],
nargs='+',
metavar='URL',
help='Origin values to always trust. default: none'
)
parser.add_argument( parser.add_argument(
'--netloc', '--netloc',
required=True, required=True,
...@@ -600,7 +614,34 @@ def main(argv=None, until=utils.until): ...@@ -600,7 +614,34 @@ def main(argv=None, until=utils.until):
ca_life_period=40, # approx. 10 years ca_life_period=40, # approx. 10 years
crt_life_time=args.service_crt_validity, crt_life_time=args.service_crt_validity,
) )
application = Application(cau=cau, cas=cas) if os.path.exists(args.cors_key_store):
with open(args.cors_key_store) as cors_key_file:
cors_secret_list = json.load(cors_key_file)
else:
cors_secret_list = []
def saveCORSKeyList(cors_secret_list):
"""
Update CORS key store when a new key was generated.
"""
with _createKey(args.cors_key_store) as cors_key_file:
json.dump(cors_secret_list, cors_key_file)
application = Application(
cau=cau,
cas=cas,
http_url=urlunsplit((
'http',
'[' + hostname + ']:' + str(http_port),
'/',
None,
None,
)),
https_url=https_base_url,
cors_token_manager=CORSTokenManager(
secret_list=cors_secret_list,
onNewKey=saveCORSKeyList,
),
cors_whitelist=args.cors_whitelist,
)
http_list = [] http_list = []
https_list = [] https_list = []
known_host_set = set() known_host_set = set()
......
This diff is collapsed.
This diff is collapsed.
...@@ -52,6 +52,7 @@ setup( ...@@ -52,6 +52,7 @@ setup(
'cryptography>=2.2.1', # everything x509 except... 'cryptography>=2.2.1', # everything x509 except...
'pyOpenSSL>=18.0.0', # ...certificate chain validation 'pyOpenSSL>=18.0.0', # ...certificate chain validation
'pem>=17.1.0', # Parse PEM files 'pem>=17.1.0', # Parse PEM files
'PyJWT', # CORS token signature
], ],
zip_safe=True, zip_safe=True,
entry_points={ entry_points={
......
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