Skip to content

Commit 0dc5574

Browse files
Merge pull request #278622 from JimacoMS4/add-python-to-app-service-mutual-auth
Add python example to App Service - Configure TLS mutual authenticati…
2 parents 06f34f0 + 39ff28d commit 0dc5574

File tree

1 file changed

+147
-4
lines changed

1 file changed

+147
-4
lines changed

articles/app-service/app-service-web-configure-tls-mutual-auth.md

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ author: msangapu-msft
66
ms.author: msangapu
77
ms.assetid: cd1d15d3-2d9e-4502-9f11-a306dac4453a
88
ms.topic: article
9-
ms.date: 12/11/2020
9+
ms.date: 06/21/2024
1010
ms.devlang: csharp
11-
ms.custom: devx-track-csharp, devx-track-extended-java, devx-track-js
11+
ms.custom: devx-track-csharp, devx-track-extended-java, devx-track-js, devx-track-python
1212
---
1313
# Configure TLS mutual authentication for Azure App Service
1414

@@ -36,7 +36,7 @@ az webapp update --set clientCertEnabled=true --name <app-name> --resource-group
3636
```
3737
### [Bicep](#tab/bicep)
3838

39-
For Bicep, modify the properties `clientCertEnabled`, `clientCertMode`, and `clientCertExclusionPaths`. A sampe Bicep snippet is provided for you:
39+
For Bicep, modify the properties `clientCertEnabled`, `clientCertMode`, and `clientCertExclusionPaths`. A sample Bicep snippet is provided for you:
4040

4141
```bicep
4242
resource appService 'Microsoft.Web/sites@2020-06-01' = {
@@ -57,7 +57,7 @@ resource appService 'Microsoft.Web/sites@2020-06-01' = {
5757

5858
### [ARM](#tab/arm)
5959

60-
For ARM templates, modify the properties `clientCertEnabled`, `clientCertMode`, and `clientCertExclusionPaths`. A sampe ARM template snippet is provided for you:
60+
For ARM templates, modify the properties `clientCertEnabled`, `clientCertMode`, and `clientCertExclusionPaths`. A sample ARM template snippet is provided for you:
6161

6262
```ARM
6363
{
@@ -438,4 +438,147 @@ public class ClientCertValidator {
438438
}
439439
```
440440

441+
## Python sample
442+
443+
The following Flask and Django Python code samples implement a decorator named `authorize_certificate` that can be used on a view function to permit access only to callers that present a valid client certificate. It expects a PEM formatted certificate in the `X-ARR-ClientCert` header and uses the Python [cryptography](https://pypi.org/project/cryptography/) package to validate the certificate based on its fingerprint (thumbprint), subject common name, issuer common name, and beginning and expiration dates. If validation fails, the decorator ensures that an HTTP response with status code 403 (Forbidden) is returned to the client.
444+
445+
### [Flask](#tab/flask)
446+
447+
```python
448+
from functools import wraps
449+
from datetime import datetime, timezone
450+
from flask import abort, request
451+
from cryptography import x509
452+
from cryptography.x509.oid import NameOID
453+
from cryptography.hazmat.primitives import hashes
454+
455+
456+
def validate_cert(request):
457+
458+
try:
459+
cert_value = request.headers.get('X-ARR-ClientCert')
460+
if cert_value is None:
461+
return False
462+
463+
cert_data = ''.join(['-----BEGIN CERTIFICATE-----\n', cert_value, '\n-----END CERTIFICATE-----\n',])
464+
cert = x509.load_pem_x509_certificate(cert_data.encode('utf-8'))
465+
466+
fingerprint = cert.fingerprint(hashes.SHA1())
467+
if fingerprint != b'12345678901234567890':
468+
return False
469+
470+
subject = cert.subject
471+
subject_cn = subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
472+
if subject_cn != "contoso.com":
473+
return False
474+
475+
issuer = cert.issuer
476+
issuer_cn = issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
477+
if issuer_cn != "contoso.com":
478+
return False
479+
480+
current_time = datetime.now(timezone.utc)
481+
482+
if current_time < cert.not_valid_before_utc:
483+
return False
484+
485+
if current_time > cert.not_valid_after_utc:
486+
return False
487+
488+
return True
489+
490+
except Exception as e:
491+
# Handle any errors encountered during validation
492+
print(f"Encountered the following error during certificate validation: {e}")
493+
return False
494+
495+
def authorize_certificate(f):
496+
@wraps(f)
497+
def decorated_function(*args, **kwargs):
498+
if not validate_cert(request):
499+
abort(403)
500+
return f(*args, **kwargs)
501+
return decorated_function
502+
```
503+
504+
The following code snippet shows how to use the decorator on a Flask view function.
505+
506+
```python
507+
@app.route('/hellocert')
508+
@authorize_certificate
509+
def hellocert():
510+
print('Request for hellocert page received')
511+
return render_template('index.html')
512+
```
513+
514+
### [Django](#tab/django)
515+
516+
```python
517+
from functools import wraps
518+
from datetime import datetime, timezone
519+
from django.core.exceptions import PermissionDenied
520+
from cryptography import x509
521+
from cryptography.x509.oid import NameOID
522+
from cryptography.hazmat.primitives import hashes
523+
524+
525+
def validate_cert(request):
526+
527+
try:
528+
cert_value = request.headers.get('X-ARR-ClientCert')
529+
if cert_value is None:
530+
return False
531+
532+
cert_data = ''.join(['-----BEGIN CERTIFICATE-----\n', cert_value, '\n-----END CERTIFICATE-----\n',])
533+
cert = x509.load_pem_x509_certificate(cert_data.encode('utf-8'))
534+
535+
fingerprint = cert.fingerprint(hashes.SHA1())
536+
if fingerprint != b'12345678901234567890':
537+
return False
538+
539+
subject = cert.subject
540+
subject_cn = subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
541+
if subject_cn != "contoso.com":
542+
return False
543+
544+
issuer = cert.issuer
545+
issuer_cn = issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
546+
if issuer_cn != "contoso.com":
547+
return False
548+
549+
current_time = datetime.now(timezone.utc)
550+
551+
if current_time < cert.not_valid_before_utc:
552+
return False
553+
554+
if current_time > cert.not_valid_after_utc:
555+
return False
556+
557+
return True
558+
559+
except Exception as e:
560+
# Handle any errors encountered during validation
561+
print(f"Encountered the following error during certificate validation: {e}")
562+
return False
563+
564+
def authorize_certificate(view):
565+
@wraps(view)
566+
def _wrapped_view(request, *args, **kwargs):
567+
if not validate_cert(request):
568+
raise PermissionDenied
569+
return view(request, *args, **kwargs)
570+
return _wrapped_view
571+
```
572+
573+
The following code snippet shows how to use the decorator on a Django view function.
574+
575+
```python
576+
@authorize_certificate
577+
def hellocert(request):
578+
print('Request for hellocert page received')
579+
return render(request, 'hello_azure/index.html')
580+
```
581+
582+
---
583+
441584
[exclusion-paths]: ./media/app-service-web-configure-tls-mutual-auth/exclusion-paths.png

0 commit comments

Comments
 (0)