@@ -13,6 +13,7 @@ Features:
1313* Pluggable APIs and multiple API versions
1414* Database schema migration using Flask-Migrate
1515* API body serialisation using Flask-Marshmallow
16+ * OIDC Authentication using Flask-OIDC
1617* No TLS, because this is intended to run behind a reverse proxy
1718* Healthz
1819
@@ -28,11 +29,12 @@ You have the choice of running this
2829
2930To run this directly:
3031
31- ```
32+ ``` shell
3233$ pip install -r requirements.txt
3334$ python ./setup.py install
3435$ mrmat-python-api-flask -h
3536usage: mrmat-python-api-flask [-h] [-d] [--host HOST] [--port PORT] [--instance-path INSTANCE_PATH] [--db DB]
37+ --oidc-secrets OIDC_SECRETS
3638
3739mrmat-python-api-flask - 0.0.2
3840
@@ -44,9 +46,14 @@ optional arguments:
4446 --instance-path INSTANCE_PATH
4547 Fully qualified path to instance directory
4648 --db DB Database URI
49+ --oidc-secrets OIDC_SECRETS
50+ Path to file containing OIDC registration
51+ ```
4752
53+ ``` shell
4854$ mrmat-python-api-flask
49- [2021-05-09 16:29:49,966] INFO: Creating new instance path at /opt/dyn/python/mrmat-python-api-flask/var/mrmat_python_api_flask-instance
55+ [2021-06-06 15:30:18,005] INFO: Using instance path at /opt/dyn/python/mrmat-python-api-flask/var/mrmat_python_api_flask-instance
56+ [2021-06-06 15:30:18,005] WARNING: Running without any authentication/authorisation
5057 * Serving Flask app " mrmat_python_api_flask" (lazy loading)
5158 * Environment: production
5259 WARNING: This is a development server. Do not use it in a production deployment.
@@ -57,12 +64,10 @@ INFO [werkzeug] * Running on http://localhost:8080/ (Press CTRL+C to quit)
5764< Ctrl-C>
5865```
5966
60- The instance directory defaults to ` var/instance/ ` but can be overridden to be a fully qualified path via the
67+ The instance directory defaults to ` var/instance/ ` but you can override that to be a fully qualified path via the
6168` --instance-path ` option. Any database supported by SQLAlchemy can be provided by the ` --db ` option. The database is
6269a SQLite database within the instance directory by default.
6370
64- When running in the CLI, the database is created and migrated to the latest revision.
65-
6671### To run as a WSGI app
6772
6873To run as a WSGI app, execute the following. When running as a WSGI app, the database is not created and migrated
@@ -81,7 +86,7 @@ $ python ./setup.py sdist
8186$ docker build -t mrmat-python-api-flask:0.0.1 -f var/docker/Dockerfile .
8287...
8388$ docker run --rm mrmat-python-api-flask:0.0.1
84- ...
89+ ```
8590
8691> You may be tempted by Alpine, but most of the Python wheels do not work for it. Go for slim-buster instead
8792
@@ -102,3 +107,133 @@ $ python ./setup.py install
102107$ python -m flake8
103108$ python -m pytest
104109```
110+
111+ Tests for authenticated APIs will be skipped until the testsuite is configured with OIDC secrets.
112+
113+ ## Clients
114+
115+ A client for the (currently) the authenticated Greeting API v3 is installed along with the API server.
116+
117+ ``` shell
118+ $ mrmat-python-api-flask-client -h
119+ usage: mrmat-python-api-flask-client [-h] [-q] [-d] [--config CONFIG] [--client-id CLIENT_ID] [--client-secret CLIENT_SECRET] [--discovery-url DISCOVERY_URL]
120+
121+ mrmat-python-api-flask-client - 0.0.2
122+
123+ optional arguments:
124+ -h, --help show this help message and exit
125+ -q, --quiet Silent Operation
126+ -d, --debug Debug
127+
128+ File Configuration:
129+ Configure the client via a config file
130+
131+ --config CONFIG, -c CONFIG
132+ Path to the configuration file for the flask client
133+
134+ Manual Configuration:
135+ Configure the client manually
136+
137+ --client-id CLIENT_ID
138+ The client_id of this CLI itself (not yours! )
139+ --client-secret CLIENT_SECRET
140+ The client_secret of the CLI itself. Not required for AAD, required for Keycloak
141+ --discovery-url DISCOVERY_URL
142+ Discovery of endpoints in the authentication platform
143+ ```
144+
145+ > The client requires configuration with OIDC secrets and currently implements the Device code flow
146+
147+ ## Configuration
148+
149+ You can provide configuration by pointing to a JSON file via the FLASK_CONFIG environment variable. The file is expected
150+ to be in the following format:
151+
152+ ``` json
153+ {
154+ "web" : {
155+ "client_id" : OIDC client_id of the API server itself
156+ "client_secret" : OIDC client_secret of the API server itself
157+ "auth_uri" : OIDC Authorization endpoint
158+ "token_uri" : OIDC Token endpoint
159+ "userinfo_uri" : OIDC UserInfo endpoint
160+ "redirect_uris" : OIDC redirect URI
161+ "issuer" : OIDC Issuer
162+ "token_introspection_uri" : OIDC Introspection URI
163+ },
164+ "OIDC_CLIENT_SECRETS" : Can be an external file
165+ }
166+ ```
167+
168+ You can externalise ` web ` into a separate file which you may generate via [ Flask-OIDCs oidc-register] ( https://flask-oidc.readthedocs.io/en/latest/ ) .
169+ If you wish to save yourself an external file, ` OIDC_CLIENT_SECRETS ` should point to the same file it is declared in
170+ (i.e. the same file that FLASK_CONFIG points to) but ` web ` must be the first entry due to some unfortunate assumptions
171+ made in Flask-OIDC.
172+
173+ The same or a separate configuration file can be used to configure the testsuite so it includes auth/z/n tests. An
174+ additional dictionary is required to configure the client side:
175+
176+ ``` json
177+ {
178+ "web" : {
179+ "client_id" : OIDC client_id of the API server itself
180+ "client_secret" : OIDC client_secret of the API server itself
181+ "auth_uri" : OIDC Authorization endpoint
182+ "token_uri" : OIDC Token endpoint
183+ "userinfo_uri" : OIDC UserInfo endpoint
184+ "redirect_uris" : OIDC redirect URI
185+ "issuer" : OIDC Issuer
186+ "token_introspection_uri" : OIDC Introspection URI
187+ },
188+ "client" : {
189+ "client_id" : OIDC client_id for the API test client
190+ "client_secret" : OIDC client_secret for the API test client
191+ "preferred_name" : OIDC expected name to test for. The test looks for the preferred_name assertion, which
192+ may not match the client_id.
193+ },
194+ "OIDC_CLIENT_SECRETS" : Can be an external file
195+ }
196+ ```
197+
198+ The client may also be configured via a configuration file, which should have the following format:
199+
200+ ``` json
201+ {
202+ "client_id" : OIDC client_id of the client script (not whoever ultimately authenticates)
203+ "client_secret" : OIDC client_secret of the client script (not whoever ultimately authenticates)
204+ This is required when using Keycloak, which is unfortunate because for instance,
205+ Microsofts AAD doesn't need it and it would make the client app far more distributable
206+ "discovery_url" : OIDC discovery URL
207+ }
208+ ```
209+
210+ ## OIDC
211+
212+ The API has currently been tested with [ Keycloak] ( https://www.keycloak.org ) . If your Keycloak instance is behind a
213+ self-signed CA then you must point the ` HTTPLIB2_CA_CERTS ` environment variable to that CA certificate before executing
214+ either the client or the server, otherwise the validation of the tokens will fail in mysterious ways. Only a stack trace
215+ will tell you that communication with the introspection endpoint failed due to missing CA trust.
216+
217+ Configure the following clients as needed:
218+
219+ ### API Server
220+
221+ * Suggested client_id: mrmat-python-api-flask
222+ * Access Type: confidential
223+ * Flow: Authorization Code Flow (Keycloak: "Standard Flow")
224+
225+ ### API Client
226+
227+ * Suggested client_id: mrmat-python-api-flask-client
228+ * Access Type: confidential (but if it wasn't Keycloak, should be public)
229+ * Flow: Device Authorization Grant
230+
231+ Keycloaks default polling interval during the device authorization flow is set to a rather long 600s. I strongly
232+ suggest to reduce that to 5s in the realm settings.
233+
234+ ### Test Client
235+
236+ * Suggested client_id: mrmat-python-api-flask-test
237+ * Access Type: confidential
238+ * Flow: Client Credentials Grant (Keycloak: "Service Accounts Enabled")
239+
0 commit comments