Skip to content

Commit 164d8b7

Browse files
authored
Merge pull request #144 from microsoft/beejones/akv-signing
Sign proposals with AKV
2 parents df08ca3 + ac8a2d8 commit 164d8b7

10 files changed

+550
-24
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
scripts/set_python_env.sh && pip install -r requirements.txt && make demo && npm run test
2828
env:
2929
KMS_WORKSPACE: ./workspace
30-
30+
3131
test-mccf-kms:
3232
runs-on: ubuntu-20.04
3333
steps:

docs/Create_new_akv_member_key.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Create new AKV member key
2+
## Create AKV certificate
3+
Use the script:
4+
```
5+
scripts/create_member_cert_akv.sh \
6+
-v name of the vault \
7+
-c name of the certificate \
8+
-rg resource group name where vault is resident
9+
-l location where vault is resident e.g. westeurope
10+
-mi name of the managed identity to access the ceritificate
11+
```
12+
The user needs to be owner of the subscription with role activated in order to create the key and the roles.
13+
## Test the certificate
14+
Doing a signature proves that the managed identity has access to the AKV certificate
15+
```
16+
export AKV_VAULT_NAME="<vault name>"
17+
export AKV_CERTIFICATE_NAME="<certifcate name>>"
18+
export AKV_CERTIFICATE_VERSION="<certificate version>"
19+
export AKV_KID="https://$AKV_VAULT_NAME.vault.azure.net/keys/$AKV_CERTIFICATE_NAME/$AKV_CERTIFICATE_VERSION" # use keys instead of certifcates for signing
20+
echo $AKV_KID
21+
22+
curl "$AKV_KID/sign?api-version=7.4" -H "Authorization: $AKV_AUTHORIZATION" -H "Content-type: application/json" -d '{"alg": "ES384", "value": "AQIDBAUGBwgJCgECAwQFBgcICQoBAgMEBQYHCAkKAQIDBAUGBwgJCgECAwQFBgcI"}'
23+
```
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Deploy to mCCF using AKV member key
2+
## Get service cert
3+
```
4+
export KEYS_DIR=vol
5+
cd $KEYS_DIR
6+
export CCF_NAME=<name of mCCF instance>
7+
export KMS_URL=https://${CCF_NAME}.confidential-ledger.azure.com
8+
export identityurl=https://identity.confidential-ledger.core.azure.com/ledgerIdentity/${CCF_NAME}
9+
curl $identityurl | jq ' .ledgerTlsCertificate' | xargs echo -e > service_cert.pem
10+
```
11+
## Set AKV env variables
12+
```
13+
export AKV_VAULT_NAME="<akv NAME>"
14+
export AKV_CERTIFICATE_NAME="name of the cert"
15+
export AKV_CERTIFICATE_VERSION="<version of certificate>"
16+
export AKV_KID="https://$AKV_VAULT_NAME.vault.azure.net/certificates/$AKV_CERTIFICATE_NAME/$AKV_CERTIFICATE_VERSION"
17+
echo $AKV_KID
18+
export AKV_AUTHORIZATION="Bearer ey..."
19+
```
20+
## Retrieve all members of KMS
21+
```
22+
curl $KMS_URL/gov/members --cacert service_cert.pem | jq
23+
```
24+
## Set custom constitution
25+
```
26+
./scripts/submit_constitution.sh --network-url $KMS_URL --certificate-dir $KEYS_DIR --custom-constitution ./governance/constitution/kms_actions.js --member-count 1
27+
```
28+
## Deploy
29+
30+
```
31+
make deploy
32+
```
33+
## Propose and vote new key release policy, settings, jwt policy and create first key
34+
```
35+
make setup
36+
```
37+
# List public keys
38+
```
39+
curl ${KMS_URL}/app/listpubkeys --cacert $KEYS_DIR/service_cert.pem -H "Content-Type: application/json" -i -w '\n'
40+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Signing a proposal using AKV certificate
2+
3+
## Env variables
4+
5+
For production environments we need to sign proposals with an Azure Key Vault (AKV) key.
6+
The script submit_proposal will test if the env variable AKV_KID and AKV_AUTHORIZATION are defined. If these are defined, the script will sign the proposal with the AKV key.
7+
8+
Setting AKV_KID:
9+
10+
```
11+
export AKV_VAULT_NAME="<akv NAME>"
12+
export AKV_CERTIFICATE_NAME="name of the cert"
13+
export AKV_CERTIFICATE_VERSION="<version of certificate>"
14+
export AKV_KID="https://$AKV_VAULT_NAME.vault.azure.net/certificates/$AKV_CERTIFICATE_NAME/$AKV_CERTIFICATE_VERSION"
15+
echo $AKV_KID
16+
```
17+
18+
AKV_AUTHORIZATION is defined as "Bearer ey...". The token is a managed identity which has access to do signatures with the certificate.
19+
20+
If these two env variables are not set, submit_proposal will use a local key stored in the workspace directory. When set, signing will be done on AKV.
21+
22+
## Submit proposal using AKV
23+
24+
export AKV_AUTHORIZATION as managed identity. Format "Bearer ey.."
25+
export AKV_KID points to the AKV certificate
26+
27+
```
28+
scripts/submit_proposal.sh --network_url $KMS_URL --certificate_dir $KEYS_DIR --proposal_file governance/policies/settings-policy.json
29+
```

docs/Using_mCCF_with_AKV_member_key

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Using mCCF with AKV member key
2+
## Set env variables
3+
```
4+
# AKV_KID
5+
export CCF_NAME="<your mCCF instance>"
6+
export KMS_URL=https://${CCF_NAME}.confidential-ledger.azure.com
7+
export KEYS_DIR=${PWD}/vol
8+
export AKV_VAULT_NAME="AKV name"
9+
export AKV_CERTIFICATE_NAME="member0"
10+
export AKV_CERTIFICATE_VERSION="version of certificate"
11+
export AKV_KID="https://$AKV_VAULT_NAME.vault.azure.net/certificates/$AKV_KEY_NAME/$KEY_VERSION"
12+
echo $AKV_KID
13+
# AKV_AUTHORIZATION
14+
export AKV_AUTHORIZATION="Bearer ey..."
15+
```
16+
## Test signing and access to signing certificate
17+
```
18+
curl "$AKV_KID/sign?api-version=7.4" -H "Authorization: $AKV_AUTHORIZATION" -H "Content-type: application/json" -d '{"alg": "ES384", "value": "AQIDBAUGBwgJCgECAwQFBgcICQoBAgMEBQYHCAkKAQIDBAUGBwgJCgECAwQFBgcI"}'
19+
```
20+
## Test AKV key by signing a proposal
21+
```
22+
scripts/submit_proposal.sh --network_url $KMS_URL --certificate_dir $KEYS_DIR --proposal_file governance/policies/settings-policy.json
23+
```

scripts/add_member.sh renamed to scripts/add_member_proposal.sh

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@ function create_member_proposal {
1212
local setUserFile=$3
1313

1414
cert=$(< $certFile sed '$!G' | paste -sd '\\n' -)
15-
key=$(< $keyFile sed '$!G' | paste -sd '\\n' -)
15+
16+
if [ -n "$keyFile" ]; then
17+
key=$(< $keyFile sed '$!G' | paste -sd '\\n' -)
18+
key_entry="\"encryption_pub_key\": \"${key}\n\""
19+
else
20+
key_entry=""
21+
fi
1622

1723
cat <<JSON > $setUserFile
1824
{
1925
"actions": [
2026
{
2127
"name": "set_member",
2228
"args": {
23-
"cert": "${cert}\n",
24-
"encryption_pub_key": "${key}\n"
29+
"cert": "${cert}\n"
30+
${key_entry:+, $key_entry}
2531
}
2632
}
2733
]
@@ -33,13 +39,14 @@ function usage {
3339
echo ""
3440
echo "Generate set_member.json proposal for adding members to CCF."
3541
echo ""
36-
echo "usage: ./add_member.sh --cert-file string --pubk-file string"
42+
echo "usage: ./add_member.sh --cert-file string [--pubk-file string]"
3743
echo ""
3844
echo " --cert-file string the certificate .pem file for the member"
39-
echo " --pubk-file string the encryption public key .pem file for the member"
45+
echo " --pubk-file string the encryption public key .pem file for the member (optional)"
4046
echo ""
4147
exit 0
4248
}
49+
4350
function failed {
4451
printf "Script failed: %s\n\n" "$1"
4552
exit 1
@@ -51,11 +58,14 @@ if [ $# -eq 0 ]; then
5158
exit 1
5259
fi
5360

61+
cert_file=""
62+
pubk_file=""
63+
5464
while [ $# -gt 0 ]
5565
do
5666
name="${1/--/}"
5767
name="${name/-/_}"
58-
case "--$name" in
68+
case "--$name" in
5969
--cert_file) cert_file="$2"; shift;;
6070
--pubk_file) pubk_file="$2"; shift;;
6171
--help) usage; exit 0; shift;;
@@ -66,9 +76,7 @@ done
6676

6777
# validate parameters
6878
if [ -z "$cert_file" ]; then
69-
failed "Missing parameter --cert-file"
70-
elif [ -z "$pubk_file" ]; then
71-
failed "Missing parameter --pubk-file"
79+
failed "Missing parameter --cert-file"
7280
fi
7381

7482
echo "Looking for certificate file..."
@@ -78,26 +86,32 @@ if [ -z "$check_existence" ]; then
7886
exit 0
7987
fi
8088

81-
echo "Looking for public key file..."
82-
check_existence=$(ls $pubk_file 2>/dev/null || true)
83-
if [ -z "$check_existence" ]; then
84-
echo "Public key file \"$pubk_file\" does not exist."
85-
exit 0
86-
fi
87-
88-
if [ ${cert_file##*.} != "pem" -o ${pubk_file##*.} != "pem" ]
89-
then
90-
echo "Wrong file extensions. Only \".pem\" files are supported."
91-
exit 0
89+
if [ -n "$pubk_file" ]; then
90+
echo "Looking for public key file..."
91+
check_existence=$(ls $pubk_file 2>/dev/null || true)
92+
if [ -z "$check_existence" ]; then
93+
echo "Public key file \"$pubk_file\" does not exist."
94+
exit 0
95+
fi
96+
97+
if [ ${cert_file##*.} != "pem" -o ${pubk_file##*.} != "pem" ]; then
98+
echo "Wrong file extensions. Only \".pem\" files are supported."
99+
exit 0
100+
fi
101+
else
102+
if [ ${cert_file##*.} != "pem" ]; then
103+
echo "Wrong file extension. Only \".pem\" files are supported."
104+
exit 0
105+
fi
92106
fi
93107

94-
certs_folder=`dirname $cert_file`
108+
certs_folder=$(dirname $cert_file)
95109
proposal_json_file="${certs_folder}/set_member.json"
96110

97111
echo "Creating member json proposal file..."
98-
create_member_proposal $cert_file $pubk_file $proposal_json_file
112+
create_member_proposal $cert_file "${pubk_file:-}" $proposal_json_file
99113

100114
member_id=$(openssl x509 -in "$cert_file" -noout -fingerprint -sha256 | cut -d "=" -f 2 | sed 's/://g' | awk '{print tolower($0)}')
101115

102116
echo "proposal json file created: $proposal_json_file"
103-
echo "member id: $member_id"
117+
echo "member id: $member_id"

scripts/create_member_cert_akv.sh

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Function to display usage
5+
display_usage() {
6+
echo "Create Azure Key Vault certificate with managed identity access policy."
7+
echo ""
8+
echo "Usage:"
9+
echo -e "$0 [-v | --vault] vault_name name of the vault"
10+
echo -e "$0 [-c | --create] identity_cert_name name of the certificate"
11+
echo -e "$0 [-rg | --resource-group] resource_group_name name of the resource group"
12+
echo -e "$0 [-l | --location] location location of the vault"
13+
echo -e "$0 [-mi | --managed-identity] managed_identity_name name of the managed identity to access the ceritificate"
14+
echo -e "$0 [-h | --help"
15+
echo ""
16+
}
17+
18+
# Check if the correct number of arguments is provided
19+
if [ $# -le 1 ]; then
20+
display_usage
21+
exit 1
22+
fi
23+
24+
# Parse command-line arguments
25+
while [[ "$#" -gt 0 ]]; do
26+
case $1 in
27+
-v|--vault) vault_name="$2"; shift ;;
28+
-c|--create) cert_name="$2"; shift ;;
29+
-rg|--resource_group) resource_group="$2"; shift ;;
30+
-l|--location) location="$2"; shift ;;
31+
-mi|--managed_identity) managed_identity_name="$2"; shift ;;
32+
-h|--help) display_usage; exit 0 ;;
33+
*) echo "Unknown parameter passed: $1"; display_usage; exit 1 ;;
34+
esac
35+
shift
36+
done
37+
38+
echo "Vault Name: $vault_name"
39+
echo "Certificate Name: $cert_name"
40+
echo "Resource Group: $resource_group"
41+
echo "Location: $location"
42+
echo "Managed Identity Name: $managed_identity_name"
43+
44+
# Check if required arguments are provided
45+
if [ -z "$vault_name" ] || [ -z "$cert_name" ] || [ -z "$resource_group" ] || [ -z "$location" ] || [ -z "$managed_identity_name" ]; then
46+
echo "Error: Vault name, certificate name, resource group, location, and managed identity name are required."
47+
display_usage
48+
exit 1
49+
fi
50+
51+
# Check if the Key Vault exists and if it has '--enable-rbac-authorization' specified
52+
vault_properties=$(az keyvault show --name "$vault_name" --resource-group "$resource_group" --query "properties" -o json)
53+
if echo "$vault_properties" | grep -q '"enableRbacAuthorization": true'; then
54+
echo "The Key Vault is configured with '--enable-rbac-authorization'."
55+
echo "You cannot set access policies directly on this Key Vault."
56+
echo "Ensuring that the necessary RBAC roles are assigned to the managed identity."
57+
58+
# Get the managed identity principal ID
59+
managed_identity_principal_id=$(az identity show --name "$managed_identity_name" --resource-group "$resource_group" --query "principalId" -o tsv)
60+
if [ -z "$managed_identity_principal_id" ]; then
61+
echo "Error: Failed to retrieve the managed identity principal ID."
62+
exit 1
63+
fi
64+
65+
# Array of roles to assign
66+
roles=("Key Vault Certificate User" "Key Vault Crypto User" "Reader")
67+
68+
# Loop through each role and assign it to the managed identity
69+
for role in "${roles[@]}"; do
70+
az role assignment create --role "$role" --assignee "$managed_identity_principal_id" --scope "/subscriptions/$(az account show --query 'id' -o tsv)/resourceGroups/$resource_group/providers/Microsoft.KeyVault/vaults/$vault_name"
71+
if [ $? -ne 0 ]; then
72+
echo "Error: Failed to assign the '$role' role to the managed identity."
73+
exit 1
74+
fi
75+
done
76+
77+
echo "Successfully assigned the 'Key Vault Certificate User' role to the managed identity."
78+
fi
79+
80+
# Your script logic to create the certificate goes here
81+
82+
# Create the JSON file dynamically
83+
JSON_FILE="/tmp/identity_cert_policy.json"
84+
cat <<EOF > $JSON_FILE
85+
{
86+
"issuerParameters": {
87+
"certificateTransparency": null,
88+
"name": "Self"
89+
},
90+
"keyProperties": {
91+
"curve": "P-384",
92+
"exportable": false,
93+
"keyType": "EC",
94+
"reuseKey": true
95+
},
96+
"lifetimeActions": [
97+
{
98+
"action": {
99+
"actionType": "AutoRenew"
100+
},
101+
"trigger": {
102+
"daysBeforeExpiry": 90
103+
}
104+
}
105+
],
106+
"secretProperties": {
107+
"contentType": "application/x-pkcs12"
108+
},
109+
"x509CertificateProperties": {
110+
"keyUsage": ["digitalSignature"],
111+
"subject": "CN=Member",
112+
"validityInMonths": 12
113+
}
114+
}
115+
EOF
116+
117+
# Create the certificate in Azure Key Vault
118+
az keyvault certificate create --vault-name $vault_name -n $cert_name -p @$JSON_FILE
119+
az keyvault key show --vault-name $vault_name --name $cert_name
120+
rm $JSON_FILE

0 commit comments

Comments
 (0)