@@ -6,9 +6,9 @@ author: msangapu-msft
6
6
ms.author : msangapu
7
7
ms.assetid : cd1d15d3-2d9e-4502-9f11-a306dac4453a
8
8
ms.topic : article
9
- ms.date : 12/11/2020
9
+ ms.date : 06/21/2024
10
10
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
12
12
---
13
13
# Configure TLS mutual authentication for Azure App Service
14
14
@@ -36,7 +36,7 @@ az webapp update --set clientCertEnabled=true --name <app-name> --resource-group
36
36
```
37
37
### [ Bicep] ( #tab/bicep )
38
38
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:
40
40
41
41
``` bicep
42
42
resource appService 'Microsoft.Web/sites@2020-06-01' = {
@@ -57,7 +57,7 @@ resource appService 'Microsoft.Web/sites@2020-06-01' = {
57
57
58
58
### [ ARM] ( #tab/arm )
59
59
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:
61
61
62
62
``` ARM
63
63
{
@@ -438,4 +438,147 @@ public class ClientCertValidator {
438
438
}
439
439
```
440
440
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
+
441
584
[ exclusion-paths ] : ./media/app-service-web-configure-tls-mutual-auth/exclusion-paths.png
0 commit comments