-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DRIVERS-2883 Add OIDC Support for Azure Functions (#455)
- Loading branch information
Showing
11 changed files
with
455 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# Azure Function Code | ||
|
||
Scripts to handle testing an OIDC on Azure Functions. | ||
|
||
https://learn.microsoft.com/en-us/azure/azure-functions/ | ||
|
||
## Prerequisites | ||
|
||
### Overall | ||
- Admin creates an Application in Azure | ||
- Admin adds an Application URL to the Application | ||
- Admin creates a Resource Group | ||
- Admin creates a managed identity in the Resource Group | ||
- Admin adds a role of "Managed Identity Operator" for the Application in the Resource Group | ||
- Admin creates an Identity Provider in Atlas Cloud-Dev that has the following fields: | ||
- iss: https://sts.windows.net/<subscription> | ||
- aud: <application-url> | ||
- Admin adds the Identity Provider to the Project | ||
- Admin creates a Database User for the IdP: | ||
- sub: <managed-identity-client-id> | ||
- Admin creates a storage account in the resource group | ||
|
||
### Per Driver | ||
|
||
- Admin creates a Function App for the driver in the shared resource group | ||
- Choose "Consumption" plan | ||
- Create a unique name for the driver | ||
- Choose the appropriate Runtime stack and version for the driver | ||
- Admin adds the managed identity as a User-assigned identity on the Function App | ||
- Admin adds the "Contributor" privileged role for the application in the Function App | ||
- Admin adds environment variable for RESOURCE and CLIENT_ID. | ||
- Admin adds the team member as a "Contributor" to the Function App | ||
- Admin gives the function name to the driver team | ||
|
||
- Driver team runs the following to set up a local function instance: | ||
- Follow the quick-start instructions for [Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/) | ||
- Stop at the "Create supporting Azure resources" stage, since they were created by the Admin | ||
- Run the login.sh script | ||
- Run `func azure functionapp publish <func-name>` and verify that it deploys | ||
- Run the invoke script and verify that it invokes | ||
|
||
|
||
## Self-Test | ||
|
||
There is a self-test that runs during setup that invokes the `oidcselftest` function in the `$AZUREOIDC_FUNC_SELF_TEST` Function App | ||
that is run durint setup. | ||
|
||
You can also manually invoke the `gettoken` function by running the following: | ||
|
||
```bash | ||
source ./secrets-export.sh | ||
pushd self-test | ||
export FUNC_NAME=gettoken | ||
export FUNC_APP_NAME=$AZUREOIDC_FUNC_SELF_TEST | ||
export FUNC_RUNTIME=python | ||
bash ../run-driver-test.sh | ||
popd | ||
``` | ||
|
||
## Driver Test | ||
|
||
Drivers should use a task group to ensure resources are properly torn down. An example is as follows: | ||
|
||
```yaml | ||
- name: testoidc_azure_func_task_group | ||
setup_group_can_fail_task: true | ||
setup_group_timeout_secs: 1800 | ||
teardown_group_can_fail_task: true | ||
teardown_group_timeout_secs: 1800 | ||
setup_group: | ||
- func: fetch source | ||
- func: <other setup function> | ||
- command: subprocess.exec | ||
params: | ||
binary: bash | ||
args: | ||
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure_func/setup.sh | ||
teardown_group: | ||
- command: subprocess.exec | ||
params: | ||
binary: bash | ||
args: | ||
- ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure_func/teardown.sh | ||
- func: <other teardown function> | ||
tasks: | ||
- oidc-auth-test-azure-func | ||
``` | ||
Where the test func does something like the following: | ||
```bash | ||
pushd <driver-oidc-func-dir> | ||
export FUNC_NAME=<driver-oidc-func-test> | ||
export FUNC_APP_NAME=${DRIVER_FUNC_FROM_ENVIRONMENT} | ||
export FUNC_RUNTIME=<runtime> | ||
bash ${DRIVERS_TOOLS/.evergreen/auth_oidc/azure_func/run-driver-test.sh | ||
popd | ||
``` | ||
|
||
The <driver-oidc-func-dir> should contain an Azure function that runs a write operation | ||
on a driver similar to the following: | ||
|
||
```python | ||
@app.route(route='pythonoidctest') | ||
def oidcselftest(req: func.HttpRequest) -> func.HttpResponse: | ||
resource=os.environ['APPSETTING_RESOURCE'] | ||
client_id= os.environ['APPSETTING_CLIENT_ID'] | ||
req_body = req.get_json() | ||
uri = req_body.get('MONGODB_URI') | ||
props = dict(ENVIRONMENT='azure', TOKEN_RESOURCE=resource) | ||
client = MongoClient(uri, username=client_id, authMechanism="MONGODB-OIDC", authMechanismProperties=props) | ||
c.test.test.insert_one({}) | ||
c.close() | ||
return func.HttpResponse('Success!') | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -o errexit | ||
|
||
SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]}) | ||
. $SCRIPT_DIR/../../handle-paths.sh | ||
pushd $SCRIPT_DIR | ||
|
||
# Handle secrets from vault. | ||
source ./secrets-export.sh | ||
|
||
if [ -z "$FUNC_APP_NAME" ]; then | ||
echo "Missing FUNC_APP_NAME!" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "$FUNC_NAME" ]; then | ||
echo "Missing FUNC_NAME!" | ||
exit 1 | ||
fi | ||
|
||
if [ -z "$MONGODB_URI" ]; then | ||
echo "Missing MONGODB_URI!" | ||
fi | ||
|
||
CODE=$(az functionapp function keys list -g $AZUREOIDC_FUNC_RESOURCE_GROUP -n $FUNC_APP_NAME --function-name $FUNC_NAME | jq -r '.default') | ||
URL=https://$FUNC_APP_NAME.azurewebsites.net/api/$FUNC_NAME?code=$CODE | ||
DATA="{\"MONGODB_URI\": \"$MONGODB_URI\" }" | ||
curl -i \ | ||
-X POST \ | ||
-d "$DATA" \ | ||
-H "x-functions-key: $CODE" \ | ||
$URL |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -o errexit | ||
|
||
SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]}) | ||
. $SCRIPT_DIR/../../handle-paths.sh | ||
pushd $SCRIPT_DIR | ||
|
||
# Handle secrets from vault. | ||
if [ ! -f secrets-export.sh ]; then | ||
. $DRIVERS_TOOLS/.evergreen/secrets_handling/setup-secrets.sh drivers/azureoidc | ||
fi | ||
source ./secrets-export.sh | ||
|
||
export AZUREKMS_TENANTID=$AZUREOIDC_TENANTID | ||
export AZUREKMS_SECRET=$AZUREOIDC_SECRET | ||
export AZUREKMS_CLIENTID=$AZUREOIDC_APPID | ||
"$DRIVERS_TOOLS"/.evergreen/csfle/azurekms/login.sh | ||
|
||
popd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -o errexit | ||
|
||
SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]}) | ||
. $SCRIPT_DIR/../../handle-paths.sh | ||
|
||
# Handle secrets from vault. | ||
source $SCRIPT_DIR/secrets-export.sh | ||
|
||
VARLIST=( | ||
FUNC_APP_NAME | ||
FUNC_NAME | ||
FUNC_RUNTIME | ||
MONGODB_URI | ||
) | ||
|
||
# Ensure that all variables required to run the test are set, otherwise throw | ||
# an error. | ||
for VARNAME in ${VARLIST[*]}; do | ||
[[ -z "${!VARNAME:-}" ]] && echo "ERROR: $VARNAME not set" && exit 1; | ||
done | ||
|
||
func init --$FUNC_RUNTIME | ||
func azure functionapp publish $FUNC_APP_NAME | ||
bash $SCRIPT_DIR/invoke.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import azure.functions as func | ||
import logging | ||
import os | ||
from urllib.request import urlopen, Request | ||
import json | ||
from pymongo import MongoClient | ||
from pymongo.auth_oidc import OIDCCallback, OIDCCallbackContext, OIDCCallbackResult | ||
|
||
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION) | ||
|
||
def _get_token(): | ||
resource=os.environ['APPSETTING_RESOURCE'] | ||
client_id= os.environ['APPSETTING_CLIENT_ID'] | ||
url = os.environ['IDENTITY_ENDPOINT'] | ||
url += '?api-version=2019-08-01' | ||
url += f'&resource={resource}' | ||
url += f'&client_id={client_id}' | ||
|
||
headers = { "X-IDENTITY-HEADER": os.environ['IDENTITY_HEADER'] } | ||
|
||
request = Request(url, headers=headers) | ||
logging.info('Making a token request.') | ||
with urlopen(request, timeout=30) as response: | ||
body = response.read().decode('utf8') | ||
return json.loads(body)['access_token'] | ||
|
||
|
||
class MyCallback(OIDCCallback): | ||
def fetch(self, context: OIDCCallbackContext) -> OIDCCallbackResult: | ||
return OIDCCallbackResult(access_token=_get_token()) | ||
|
||
|
||
@app.route(route="gettoken") | ||
def gettoken(req: func.HttpRequest) -> func.HttpResponse: | ||
logging.info('Handling a gettoken request.') | ||
try: | ||
token = _get_token() | ||
except Exception as e: | ||
return func.HttpResponse(str(e), status_code=500) | ||
logging.info('Returning the token.') | ||
return func.HttpResponse(token) | ||
|
||
|
||
@app.route(route='oidcselftest') | ||
def oidcselftest(req: func.HttpRequest) -> func.HttpResponse: | ||
logging.info('Handling an oidcselftest request.') | ||
try: | ||
req_body = req.get_json() | ||
uri = req_body.get('MONGODB_URI') | ||
props = dict(OIDC_CALLBACK=MyCallback()) | ||
logging.info('Testing MONGODB-OIDC on azure functions...') | ||
c = MongoClient(f'{uri}/?authMechanism=MONGODB-OIDC', authMechanismProperties=props) | ||
c.test.test.insert_one({}) | ||
c.close() | ||
except Exception as e: | ||
return func.HttpResponse(str(e), status_code=500) | ||
logging.info('Testing MONGODB-OIDC on azure functions... done.') | ||
logging.info('Self test complete!') | ||
return func.HttpResponse('Success!') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"version": "2.0", | ||
"logging": { | ||
"applicationInsights": { | ||
"samplingSettings": { | ||
"isEnabled": true, | ||
"excludedTypes": "Request" | ||
} | ||
} | ||
}, | ||
"extensionBundle": { | ||
"id": "Microsoft.Azure.Functions.ExtensionBundle", | ||
"version": "[4.*, 5.0.0)" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Do not include azure-functions-worker in this file | ||
# The Python Worker is managed by the Azure Functions platform | ||
# Manually managing azure-functions-worker may cause unexpected issues | ||
|
||
azure-functions | ||
pymongo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -o errexit | ||
|
||
SCRIPT_DIR=$(dirname ${BASH_SOURCE[0]}) | ||
. $SCRIPT_DIR/../../handle-paths.sh | ||
pushd $SCRIPT_DIR | ||
|
||
# Ensure Azure Functions Core Tools is/can be installed. | ||
DOCS_URL=https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local | ||
OS_NAME=$(uname -s | tr '[:upper:]' '[:lower:]') | ||
if ! command -v func &> /dev/null; then | ||
if [ "$OS_NAME" != "linux" ]; then | ||
echo "See $DOCS_URL for Azure Functions Core Tools installation" | ||
exit 1 | ||
fi | ||
fi | ||
|
||
######################## | ||
# Log in to azure and set up secrets. | ||
# Ensure clean secrets. | ||
rm -f secrets-export.sh | ||
bash ./login.sh | ||
source ./secrets-export.sh | ||
|
||
######################## | ||
# Start an Atlas Cluster | ||
|
||
# Get the utility functions | ||
. ../../atlas/atlas-utils.sh | ||
|
||
# Generate a random cluster name. | ||
# See: https://docs.atlas.mongodb.com/reference/atlas-limits/#label-limits | ||
DEPLOYMENT_NAME="$RANDOM-DRIVERGCP" | ||
echo "export CLUSTER_NAME=$DEPLOYMENT_NAME" >> "secrets-export.sh" | ||
|
||
# Set the create cluster configuration. | ||
export DEPLOYMENT_DATA=$(cat <<EOF | ||
{ | ||
"autoScaling" : { | ||
"autoIndexingEnabled" : false, | ||
"compute" : { | ||
"enabled" : true, | ||
"scaleDownEnabled" : true | ||
}, | ||
"diskGBEnabled" : true | ||
}, | ||
"backupEnabled" : false, | ||
"biConnector" : { | ||
"enabled" : false, | ||
"readPreference" : "secondary" | ||
}, | ||
"clusterType" : "REPLICASET", | ||
"diskSizeGB" : 10.0, | ||
"encryptionAtRestProvider" : "NONE", | ||
"mongoDBMajorVersion" : "7.0", | ||
"name" : "${DEPLOYMENT_NAME}", | ||
"numShards" : 1, | ||
"paused" : false, | ||
"pitEnabled" : false, | ||
"providerBackupEnabled" : false, | ||
"providerSettings" : { | ||
"providerName" : "AWS", | ||
"autoScaling" : { | ||
"compute" : { | ||
"maxInstanceSize" : "M20", | ||
"minInstanceSize" : "M10" | ||
} | ||
}, | ||
"diskIOPS" : 3000, | ||
"encryptEBSVolume" : true, | ||
"instanceSizeName" : "M10", | ||
"regionName" : "US_EAST_1", | ||
"volumeType" : "STANDARD" | ||
}, | ||
"replicationFactor" : 3, | ||
"rootCertType" : "ISRGROOTX1", | ||
"terminationProtectionEnabled" : false, | ||
"versionReleaseSystem" : "LTS" | ||
} | ||
EOF | ||
) | ||
|
||
export ATLAS_PUBLIC_API_KEY=$OIDC_ATLAS_PUBLIC_API_KEY | ||
export ATLAS_PRIVATE_API_KEY=$OIDC_ATLAS_PRIVATE_API_KEY | ||
export ATLAS_GROUP_ID=$OIDC_ATLAS_GROUP_ID | ||
|
||
create_deployment | ||
|
||
# Ensure Azure Functions Core Tools is installed. | ||
URL=https://github.com/Azure/azure-functions-core-tools/releases/download/4.0.5907/Azure.Functions.Cli.linux-x64.4.0.5907.zip | ||
if ! command -v func &> /dev/null; then | ||
curl -L -o /tmp/azure-functions-cli.zip $URL | ||
unzip -q -d /tmp/azure-functions-cli /tmp/azure-functions-cli.zip | ||
pushd /tmp/azure-functions-cli | ||
mkdir -p $DRIVERS_TOOLS/.bin | ||
chmod +x func | ||
chmod +x gozip | ||
mv * $DRIVERS_TOOLS/.bin | ||
popd | ||
fi | ||
|
||
######################## | ||
# Wait for the Atlas Cluster | ||
export MONGODB_URI=$(check_deployment) | ||
echo "export MONGODB_URI=$MONGODB_URI" >> ./secrets-export.sh | ||
|
||
popd |
Oops, something went wrong.