Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions all-of-auth/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SHELL := /bin/bash

.PHONY: help
.DEFAULT_GOAL := help

check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))))

help: ## 💬 This help message :)
@grep -E '[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

build: ## 🔨 Build the app
@echo -e "\e[34m$@\e[0m" || true
@npm run build

clean: ## 🧹 Clean the working folders created during build
@rm -rf dist
85 changes: 85 additions & 0 deletions all-of-auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Banking application

This is a sample application to demostrate the use of "all of" authentication scheme.
Refer to https://microsoft.github.io/CCF/main/build_apps/js_app_bundle.html for more details
about the authentication schemes supported in CCF.

## Use case

Use a certificate and a token (JWT) for authentication and authorization.

## What the application does

This application provides a REST API with the following endpoints:

- PUT `/app/logs/{key_op}`
- write a log entry for the {key_op}. The log entry is supplied in the body.
- can be invoked by an user (identified by a certificate) with the 'log_writer' role.
- status code for success: 204

### Scenario in the demo

The app defines a role called the 'log_writer' with permission to write a log entry. The caller
authenticates with a certificate. The app expects a Microsoft Entra ID token to be supplied in the 'Authorization' header.
Up on receiving a request, the app validates the certificate followed by the token using the 'all of' authentication scheme.

### Setup

1. Deploy an Azure confidential ledger instance. (https://learn.microsoft.com/en-us/azure/confidential-ledger/quickstart-portal)

2. Create a certificate with the name log_writer_cert.pem and log_writer_privk.pem

openssl ecparam -out "log_writer_privk.pem" -name "secp384r1" -genkey
openssl req -new -key "log_writer_privk.pem" -x509 -nodes -days 365 -out "log_writer_cert.pem" -"sha384" -subj=/CN="log_writer"

3. Obtain a Microsoft Entra ID token (for an Administrator user on the ledger) and copy the raw token.

az login
az account get-access-token --resource https://confidential-ledger.azure.com

4. Replace the Tenant ID in the 'expectedIssuer' variable in the app code with an appropriate value. The app checks the 'iss' claim in the
token against this value.

expectedIssuer = "https://login.microsoftonline.com/<tenant id>/v2.0"

5. Build and deploy the app.

# Declare variables
#
apiVersion="2024-08-22-preview"
content_type_application_json="Content-Type: application/json"
bundle="/home/settiy/azureconfidentialledger-app-samples/all-of-auth/dist/bundle.json"
content_type_merge_patch_json="Content-Type: application/merge-patch+json"
authorization="Authorization: Bearer <token>"

# Build the app
#
make build

# Deploy the application
#
curl -k -X PUT "https://myledger.confidential-ledger.azure.com/app/userDefinedEndpoints?api-version=$apiVersion" -H "$content_type_application_json" -H "$authorization" -d @$bundle

# View the application
#
curl -k "https://myledger.confidential-ledger.azure.com/app/userDefinedEndpoints?api-version=$apiVersion" -H "$authorization"

# Create the role
# These actions must match (case-sensitive) the values defined in the application.
#
role_actions='{"roles":[{"role_name":"log_writer","role_actions":["/logs/write"]}]}'
curl -k -X PUT "https://myledger.confidential-ledger.azure.com/app/roles?api-version=$apiVersion" -H "$content_type_application_json" -H "$authorization" -d $role_actions

# View the roles
#
curl -k "https://myledger.confidential-ledger.azure.com/app/roles?api-version=$apiVersion" -H "$authorization"

# Grant the log writer cert an appropriate role and create the user.
#
log_writer_cert_fingerprint=$(openssl x509 -in "log_writer_cert.pem" -noout -fingerprint -sha256 | cut -d "=" -f 2)
log_writer_user="{\"user_id\":\"$log_writer_cert_fingerprint\",\"assignedRoles\":[\"log_writer\"]}"
curl -k -X PATCH "https://myledger.confidential-ledger.azure.com/app/ledgerUsers/$log_writer_cert_fingerprint?api-version=$apiVersion" -H "$content_type_merge_patch_json" -H "$authorization" -d $log_writer_user

# View the users
#
curl -k "https://myledger.confidential-ledger.azure.com/app/ledgerUsers?api-version=$apiVersion" -H "$authorization"
29 changes: 29 additions & 0 deletions all-of-auth/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"endpoints": {
"/logs/{key_op}": {
"put": {
"js_module": "endpoints/all-of.js",
"js_function": "writeLogMessage",
"forwarding_required": "always",
"authn_policies": [
{
"all_of": [
"any_cert",
"jwt"
]
}
],
"mode": "readwrite",
"openapi": {
"responses": {
"204": {
"description": "Ok"
}
},
"security": [],
"parameters": []
}
}
}
}
}
67 changes: 67 additions & 0 deletions all-of-auth/build_bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { readdirSync, statSync, readFileSync, writeFileSync } from "fs";
import { join, posix, sep } from "path";

const args = process.argv.slice(2);

const getAllFiles = function (dirPath, arrayOfFiles) {
arrayOfFiles = arrayOfFiles || [];

const files = readdirSync(dirPath);
for (const file of files) {
const filePath = join(dirPath, file);
if (statSync(filePath).isDirectory()) {
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
} else {
arrayOfFiles.push(filePath);
}
}

return arrayOfFiles;
};

const removePrefix = function (s, prefix) {
return s.substr(prefix.length).split(sep).join(posix.sep);
};

const rootDir = args[0];

const metadataPath = join(rootDir, "app.json");
const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));

const srcDir = join(rootDir, "src");
const allFiles = getAllFiles(srcDir);

// The trailing / is included so that it is trimmed in removePrefix.
// This produces "foo/bar.js" rather than "/foo/bar.js"
const toTrim = srcDir + "/";

const modules = allFiles.map(function (filePath) {
return {
name: removePrefix(filePath, toTrim),
module: readFileSync(filePath, "utf-8"),
};
});

const bundlePath = join(args[0], "bundle.json");
const appRegPath = join(args[0], "set_js_app.json");
const bundle = {
metadata: metadata,
modules: modules,
};
const app_reg = {
actions: [
{
name: "set_js_app",
args: {
bundle: bundle,
disable_bytecode_cache: false,
},
},
],
};

console.log(
`Writing bundle containing ${modules.length} modules to ${bundlePath}`,
);
writeFileSync(bundlePath, JSON.stringify(bundle));
writeFileSync(appRegPath, JSON.stringify(app_reg));
Loading