Skip to content

Update documentation #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
06f9c41
Update documentation
marvinbuss Jun 12, 2023
219858b
Merge branch 'main' into marvinbuss/docs
marvinbuss Jun 12, 2023
278aa5e
Update function configuration
marvinbuss Jun 13, 2023
f6f9a80
Merge branch 'marvinbuss/docs' of https://github.com/PerfectThymeTech…
marvinbuss Jun 13, 2023
6024914
Update triggers
marvinbuss Jun 13, 2023
8ebc703
Update entrypoint
marvinbuss Jun 13, 2023
1aa35ac
Update entry point
marvinbuss Jun 13, 2023
fed3c9d
Add function json
marvinbuss Jun 13, 2023
1cba28c
Update function configuration
marvinbuss Jun 13, 2023
a568ae9
Update test
marvinbuss Jun 13, 2023
4516631
Update main to function_app
marvinbuss Jun 13, 2023
b021b25
Update config
marvinbuss Jun 13, 2023
8b20465
Update function folder structure
marvinbuss Jun 13, 2023
2c95920
Update test config
marvinbuss Jun 13, 2023
302d474
Update folder name
marvinbuss Jun 13, 2023
e89c013
Update test reference
marvinbuss Jun 13, 2023
adc5baa
Add app insights logging to function code
marvinbuss Jun 13, 2023
03bc9e3
Update logging configuration
marvinbuss Jun 13, 2023
2a10d6d
Update logging
marvinbuss Jun 13, 2023
a467636
Update logging
marvinbuss Jun 13, 2023
d869bbb
Update logging
marvinbuss Jun 13, 2023
6cdf8c5
Remove opencensus
marvinbuss Jun 13, 2023
91d55fd
Update function app settings
marvinbuss Jun 13, 2023
25f157a
Add health path to variables
marvinbuss Jun 13, 2023
056ad36
Propagate logs
marvinbuss Jun 13, 2023
6ca74bf
Update log categories
marvinbuss Jun 13, 2023
14b19c0
Add excluded type
marvinbuss Jun 13, 2023
2c6ae54
Add mpls to infra
marvinbuss Jun 13, 2023
1e34510
Update docs
marvinbuss Jun 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/functionApp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ on:
- main
paths:
- "**.py"
- "code/function/**"

pull_request:
branches:
- main
paths:
- "**.py"
- "code/function/**"

jobs:
function_test:
Expand All @@ -24,7 +26,7 @@ jobs:
uses: ./.github/workflows/_functionAppDeployTemplate.yml
name: "Function App Deploy"
needs: [function_test]
if: github.event_name == 'push' || github.event_name == 'release'
# if: github.event_name == 'push' || github.event_name == 'release'
with:
environment: "dev"
python_version: "3.10"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ on:
- main
paths:
- "**.tf"
- "code/infra/**"

pull_request:
branches:
- main
paths:
- "**.tf"
- "code/infra/**"

jobs:
terraform_lint:
Expand Down
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,54 @@ This repository provides a scalable baseline for Azure Functions written in Pyth
1. A compliant infrastructure baseline written in Terraform,
2. A Python code baseline that follows best practices and
3. A safe rollout mechanism of code artifacts.

## Infrastructure

The infrastructure as code (IaC) is written in Terraform and uses all the latest and greatest Azure Function features to ensure high security standards and the lowest attack surface possible. The code can be found in the [`/code/infra` folder](/code/infra/) and creates the following resources:

* App Service Plan,
* Azure Function,
* Azure Storage Account,
* Azure Key Vault,
* Azure Application Insights and
* Azure Log Analytics Workspace.

The Azure Function is configured in a way to fulfill highest compliance standards. In addition, the end-to-end setup takes care of wiring up all services to ensure a productive experience on day one. For instance, the Azure Function is automatically being connected to Azure Application Insights and the Application Insights service is being connected to the Azure Log Analytics Workspace.

### Network configuration

The deployed services ensure a compliant network setup using the following features:

* Public network access is denied for all services.
* All deployed services rely on Azure Private Endpoints for all network flows including deployments and usage of the services.

### Authentication & Authorization

The deployed services ensure a compliant authentication & authorization setup using the following features:

* No key-based or local/basic authentication flows.
* Azure AD-only authentication.
* All authorization is controlled by Azure RBAC.
* This includes the interaction of the Azure Function with the Azure Storage Account and the Azure Key Vault.

### Encryption

The deployed services ensure a compliant encryption setup using the following features:

* Encryption at rest using 256-bit AES (FIPS 140-2).
* HTTPS traffic only.
* All traffic is encrypted using TLS 1.2.
* Note: Customer-manaed keys are not used at this point in time but can be added easily.
* Note: Cypher suites are set to default and can further be limited.

## Azure Function Code

The Azure Function code is written in Python and leverages the new [Web Framework integration](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=asgi%2Capplication-level&pivots=python-mode-decorators#web-frameworks) supported by the v2 Python programming model. This allows to rely on proven frameworks such as FastAPI and Flask. The Azure Function application code can be found in the [`/code/function` folder](/code/function/).

## FastAPI

This sample uses FastAPI as a baseline which is a scalable, modern, fast and proven web framework for APIs built in Python. More details about FastAPI can be found [here](https://fastapi.tiangolo.com/).

## Testing

Testing of the Azure Functon application code. The testing is done using `pytest`. Tests are stored in the [`/tests` folder](/tests/) and should be extended for new functionality that is being implemented over time. The `pytest.ini` is used to reference the Azure Functon project for imports. This file makes sure that the respective python objects from the Azrue Function application code can be imported into the tests and validated accordingly.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from fastapi import APIRouter
from function.api.v1.endpoints import heartbeat, sample
from fastapp.api.v1.endpoints import heartbeat, sample

api_v1_router = APIRouter()
api_v1_router.include_router(sample.router, prefix="/sample", tags=["sample"])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Any

from fastapi import APIRouter
from function.models.heartbeat import HearbeatResult
from function.utils import setup_logging
from fastapp.models.heartbeat import HearbeatResult
from fastapp.utils import setup_logging

logger = setup_logging(__name__)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Any

from fastapi import APIRouter
from function.models.sample import SampleRequest, SampleResponse
from function.utils import setup_logging
from fastapp.models.sample import SampleRequest, SampleResponse
from fastapp.utils import setup_logging

logger = setup_logging(__name__)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from pydantic import BaseSettings
from pydantic import BaseSettings, Field


class Settings(BaseSettings):
Expand All @@ -10,6 +10,9 @@ class Settings(BaseSettings):
API_V1_STR: str = "/v1"
LOGGING_LEVEL: int = logging.INFO
DEBUG: bool = False
APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field(
default="", env="APPLICATIONINSIGHTS_CONNECTION_STRING"
)


settings = Settings()
File renamed without changes.
33 changes: 33 additions & 0 deletions code/function/fastapp/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from fastapi import FastAPI
from fastapp.api.v1.api_v1 import api_v1_router
from fastapp.core.config import settings


def get_app() -> FastAPI:
"""Setup the Fast API server.

RETURNS (FastAPI): The FastAPI object to start the server.
"""
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.APP_VERSION,
openapi_url="/openapi.json",
debug=settings.DEBUG,
)
app.include_router(api_v1_router, prefix=settings.API_V1_STR)
return app


app = get_app()


@app.on_event("startup")
async def startup_event():
"""Gracefully start the application before the server reports readiness."""
pass


@app.on_event("shutdown")
async def shutdown_event():
"""Gracefully close connections before shutdown of the server."""
pass
Empty file.
File renamed without changes.
4 changes: 2 additions & 2 deletions code/function/utils.py → code/function/fastapp/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import logging
from logging import Logger

from function.core.config import settings
from fastapp.core.config import settings


def setup_logging(module) -> Logger:
"""Setup logging and event handler.

RETURNS (Logger): The logger object to log activities.
"""
logger = logging.getLogger(module)
Expand All @@ -17,6 +18,5 @@ def setup_logging(module) -> Logger:
logger_stream_handler.setFormatter(
logging.Formatter("[%(asctime)s] [%(levelname)s] [%(module)-8.8s] %(message)s")
)

logger.addHandler(logger_stream_handler)
return logger
34 changes: 0 additions & 34 deletions code/function/function_app.py

This file was deleted.

14 changes: 13 additions & 1 deletion code/function/host.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
{
"version": "2.0",
"logging": {
"fileLoggingMode": "debugOnly",
"logLevel": {
"default": "Information",
"Host": "Information",
"Function": "Information",
"Host.Aggregator": "Information"
},
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
"excludedTypes": "Request;Exception"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"extensions": {
"http": {
"routePrefix": ""
}
}
}
6 changes: 6 additions & 0 deletions code/function/wrapper/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import azure.functions as func
from fastapp.main import app


async def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
return await func.AsgiMiddleware(app).handle_async(req, context)
21 changes: 21 additions & 0 deletions code/function/wrapper/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
],
"route": "{*route}"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
15 changes: 14 additions & 1 deletion code/infra/function.tf
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ resource "azapi_resource" "function" {
},
{
name = "WEBSITE_RUN_FROM_PACKAGE"
value = "0"
},
{
name = "PYTHON_ENABLE_WORKER_EXTENSIONS"
value = "1"
},
{
name = "ENABLE_ORYX_BUILD"
value = "1"
},
{
name = "SCM_DO_BUILD_DURING_DEPLOYMENT"
value = "1"
},
{
Expand All @@ -82,9 +94,10 @@ resource "azapi_resource" "function" {
functionAppScaleLimit = 0
functionsRuntimeScaleMonitoringEnabled = false
ftpsState = "Disabled"
healthCheckPath = var.function_health_path
http20Enabled = false
ipSecurityRestrictionsDefaultAction = "Deny"
linuxFxVersion = "Python|${var.python_version}"
linuxFxVersion = "Python|${var.function_python_version}"
localMySqlEnabled = false
loadBalancing = "LeastRequests"
minTlsVersion = "1.2"
Expand Down
20 changes: 20 additions & 0 deletions code/infra/logging.tf
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,23 @@ resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting_log_analytics_
}
}
}

resource "azurerm_monitor_private_link_scope" "mpls" {
name = "${local.prefix}-ampls001"
resource_group_name = azurerm_resource_group.logging_rg.name
tags = var.tags
}

resource "azurerm_monitor_private_link_scoped_service" "mpls_application_insights" {
name = "ampls-${azurerm_application_insights.application_insights.name}"
resource_group_name = azurerm_monitor_private_link_scope.mpls.resource_group_name
scope_name = azurerm_monitor_private_link_scope.mpls.name
linked_resource_id = azurerm_application_insights.application_insights.id
}

resource "azurerm_monitor_private_link_scoped_service" "mpls_log_analytics_workspace" {
name = "ampls-${azurerm_log_analytics_workspace.log_analytics_workspace.name}"
resource_group_name = azurerm_monitor_private_link_scope.mpls.resource_group_name
scope_name = azurerm_monitor_private_link_scope.mpls.name
linked_resource_id = azurerm_log_analytics_workspace.log_analytics_workspace.id
}
14 changes: 12 additions & 2 deletions code/infra/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,27 @@ variable "route_table_id" {
}
}

variable "python_version" {
variable "function_python_version" {
description = "Specifies the python version of the Azure Function."
type = string
sensitive = false
default = "3.10"
validation {
condition = contains(["3.9", "3.10"], var.python_version)
condition = contains(["3.9", "3.10"], var.function_python_version)
error_message = "Please specify a valid Python version."
}
}

variable "function_health_path" {
description = "Specifies the health endpoint of the Azure Function."
type = string
sensitive = false
validation {
condition = startswith(var.function_health_path, "/")
error_message = "Please specify a valid path."
}
}

variable "private_dns_zone_id_blob" {
description = "Specifies the resource ID of the private DNS zone for Azure Storage blob endpoints. Not required if DNS A-records get created via Azue Policy."
type = string
Expand Down
2 changes: 2 additions & 0 deletions code/infra/vars.dev.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ location = "northeurope"
environment = "dev"
prefix = "myfunc"
tags = {}
function_python_version = "3.10"
function_health_path = "/v1/health/heartbeat"
vnet_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/virtualNetworks/mycrp-prd-function-vnet001"
nsg_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/networkSecurityGroups/mycrp-prd-function-nsg001"
route_table_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-function-network-rg/providers/Microsoft.Network/routeTables/mycrp-prd-function-rt001"
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[pytest]
pythonpath = code
pythonpath = code/function
Loading