Skip to content

Commit

Permalink
feat: create kms key as part of cluster bootstrap (#4170)
Browse files Browse the repository at this point in the history
  • Loading branch information
aramase authored Jan 25, 2021
1 parent ae4bb40 commit 5d932f1
Show file tree
Hide file tree
Showing 19 changed files with 568 additions and 36 deletions.
8 changes: 4 additions & 4 deletions .pipelines/pr-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
k8sRelease: '1.18'
apimodel: 'examples/e2e-tests/kubernetes/release/default/definition.json'
createVNET: true
enableKMSEncryption: true
enableKMSEncryption: false
containerRuntime: 'docker'
runSSHTests: true

Expand All @@ -74,7 +74,7 @@ jobs:
k8sRelease: '1.19'
apimodel: 'examples/e2e-tests/kubernetes/release/default/definition.json'
createVNET: true
enableKMSEncryption: true
enableKMSEncryption: false
containerRuntime: 'docker'
runSSHTests: true

Expand Down Expand Up @@ -104,7 +104,7 @@ jobs:
k8sRelease: '1.18'
apimodel: 'examples/e2e-tests/kubernetes/release/default/definition-no-vnet.json'
createVNET: false
enableKMSEncryption: true
enableKMSEncryption: false
containerRuntime: 'containerd'
runSSHTests: true

Expand All @@ -114,7 +114,7 @@ jobs:
k8sRelease: '1.19'
apimodel: 'examples/e2e-tests/kubernetes/release/default/definition-no-vnet.json'
createVNET: false
enableKMSEncryption: true
enableKMSEncryption: false
containerRuntime: 'containerd'
runSSHTests: true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"properties": {
"orchestratorProfile": {
"kubernetesConfig": {
"enableEncryptionWithExternalKms": true,
"useManagedIdentity": true,
"addons": [
{
Expand Down Expand Up @@ -121,4 +120,4 @@
"enableAutomaticUpdates": false
}
}
}
}
8 changes: 5 additions & 3 deletions examples/e2e-tests/kubernetes/release/default/definition.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"properties": {
"orchestratorProfile": {
"kubernetesConfig": {
"enableEncryptionWithExternalKms": true,
"useManagedIdentity": true,
"clusterSubnet": "10.239.0.0/16",
"addons": [
Expand Down Expand Up @@ -56,7 +55,10 @@
"vnetSubnetId": "/subscriptions/SUB_ID/resourceGroups/RG_NAME/providers/Microsoft.Network/virtualNetworks/VNET_NAME/subnets/SUBNET_NAME",
"firstConsecutiveStaticIP": "10.239.255.239",
"vnetCidr": "10.239.0.0/16",
"availabilityZones": ["1", "2"]
"availabilityZones": [
"1",
"2"
]
},
"agentPoolProfiles": [
{
Expand Down Expand Up @@ -132,4 +134,4 @@
"enableAutomaticUpdates": false
}
}
}
}
8 changes: 8 additions & 0 deletions parts/k8s/cloud-init/artifacts/cse_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ ensureDHCPv6() {
fi
}
{{end}}
{{- if EnableEncryptionWithExternalKms}}
ensureKMSKeyvaultKey() {
wait_for_file 3600 1 {{GetKMSKeyvaultKeyServiceCSEScriptFilepath}} || exit {{GetCSEErrorCode "ERR_FILE_WATCH_TIMEOUT"}}
wait_for_file 3600 1 {{GetKMSKeyvaultKeyCSEScriptFilepath}} || exit {{GetCSEErrorCode "ERR_FILE_WATCH_TIMEOUT"}}
systemctlEnableAndStart kms-keyvault-key || exit {{GetCSEErrorCode "ERR_SYSTEMCTL_START_FAIL"}}
}
{{end}}
ensureKubelet() {
wait_for_file 1200 1 /etc/sysctl.d/11-aks-engine.conf || exit {{GetCSEErrorCode "ERR_FILE_WATCH_TIMEOUT"}}
sysctl_reload 10 5 120 || exit {{GetCSEErrorCode "ERR_SYSCTL_RELOAD"}}
Expand Down Expand Up @@ -564,6 +571,7 @@ configAzurePolicyAddon() {
sed -i "s|<resourceId>|/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP|g" $ADDONS_DIR/azure-policy-deployment.yaml
}
{{end}}

configAddons() {
{{if IsClusterAutoscalerAddonEnabled}}
if [[ ${CLUSTER_AUTOSCALER_ADDON} == true ]]; then
Expand Down
7 changes: 7 additions & 0 deletions parts/k8s/cloud-init/artifacts/cse_main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ time_metric "EnsureContainerd" ensureContainerd
time_metric "EnsureDHCPv6" ensureDHCPv6
{{end}}

{{/* configure and enable kms plugin */}}
{{- if EnableEncryptionWithExternalKms}}
if [[ -n ${MASTER_NODE} ]]; then
time_metric "EnsureKMSKeyvaultKey" ensureKMSKeyvaultKey
fi
{{end}}

time_metric "EnsureKubelet" ensureKubelet
{{if IsAzurePolicyAddonEnabled}}
if [[ -n ${MASTER_NODE} ]]; then
Expand Down
11 changes: 11 additions & 0 deletions parts/k8s/cloud-init/artifacts/kms-keyvault-key.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=setupkmskey
After=network-online.target

[Service]
Type=oneshot
ExecStart={{GetKMSKeyvaultKeyCSEScriptFilepath}}

[Install]
WantedBy=multi-user.target
#EOF
118 changes: 118 additions & 0 deletions parts/k8s/cloud-init/artifacts/kms-keyvault-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env bash

set +x
set -euo pipefail

AZURE_JSON_PATH="/etc/kubernetes/azure.json"
SERVICE_PRINCIPAL_CLIENT_ID=$(jq -r '.aadClientId' ${AZURE_JSON_PATH})
SERVICE_PRINCIPAL_CLIENT_SECRET=$(jq -r '.aadClientSecret' ${AZURE_JSON_PATH})
TENANT_ID=$(jq -r '.tenantId' ${AZURE_JSON_PATH})
KMS_KEYVAULT_NAME=$(jq -r '.providerVaultName' ${AZURE_JSON_PATH})
KMS_KEY_NAME=$(jq -r '.providerKeyName' ${AZURE_JSON_PATH})
USER_ASSIGNED_IDENTITY_ID=$(jq -r '.userAssignedIdentityID' ${AZURE_JSON_PATH})
PROVIDER_KEY_VERSION=$(jq -r '.providerKeyVersion' ${AZURE_JSON_PATH})
AZURE_CLOUD=$(jq -r '.cloud' ${AZURE_JSON_PATH})

# get the required parameters specific for cloud
if [[ $AZURE_CLOUD == "AzurePublicCloud" ]]; then
ACTIVE_DIRECTORY_ENDPOINT="https://login.microsoftonline.com/"
KEYVAULT_DNS_SUFFIX="vault.azure.net"
elif [[ $AZURE_CLOUD == "AzureChinaCloud" ]]; then
ACTIVE_DIRECTORY_ENDPOINT="https://login.chinacloudapi.cn/"
KEYVAULT_DNS_SUFFIX="vault.azure.cn"
elif [[ $AZURE_CLOUD == "AzureGermanCloud" ]]; then
ACTIVE_DIRECTORY_ENDPOINT="https://login.microsoftonline.de/"
KEYVAULT_DNS_SUFFIX="vault.microsoftazure.de"
elif [[ $AZURE_CLOUD == "AzureUSGovernmentCloud" ]]; then
ACTIVE_DIRECTORY_ENDPOINT="https://login.microsoftonline.us/"
KEYVAULT_DNS_SUFFIX="vault.usgovcloudapi.net"
elif [[ $AZURE_CLOUD == "AzureStackCloud" ]]; then
AZURESTACK_ENVIRONMENT_JSON_PATH="/etc/kubernetes/azurestackcloud.json"
ACTIVE_DIRECTORY_ENDPOINT=$(jq -r '.activeDirectoryEndpoint' ${AZURESTACK_ENVIRONMENT_JSON_PATH})
KEYVAULT_DNS_SUFFIX=$(jq -r '.keyVaultDNSSuffix' ${AZURESTACK_ENVIRONMENT_JSON_PATH})
else
echo "Invalid cloud name"
exit 120
fi

TOKEN_URL="${ACTIVE_DIRECTORY_ENDPOINT}${TENANT_ID}/oauth2/token"
KEYVAULT_URL="https://${KMS_KEYVAULT_NAME}.${KEYVAULT_DNS_SUFFIX}/keys/${KMS_KEY_NAME}/versions?maxresults=1&api-version=7.1"
KEYVAULT_ENDPOINT="https://${KEYVAULT_DNS_SUFFIX}"
KMS_KUBERNETES_FILE=/etc/kubernetes/manifests/kube-azure-kms.yaml

# provider key version already exists
# this will be the case for BYOK
if [[ -n $PROVIDER_KEY_VERSION ]]; then
echo "KMS provider key version already exists"
exit 0
fi

echo "Generating token for Azure Key Vault"
echo "------------------------------------------------------------------------"
echo "Parameters"
echo "------------------------------------------------------------------------"
echo "SERVICE_PRINCIPAL_CLIENT_ID: ..."
echo "SERVICE_PRINCIPAL_CLIENT_SECRET: ..."
echo "ACTIVE_DIRECTORY_ENDPOINT: $ACTIVE_DIRECTORY_ENDPOINT"
echo "TENANT_ID: $TENANT_ID"
echo "TOKEN_URL: $TOKEN_URL"
echo "SCOPE: $KEYVAULT_ENDPOINT"
echo "------------------------------------------------------------------------"

if [[ $SERVICE_PRINCIPAL_CLIENT_ID == "msi" ]] && [[ $SERVICE_PRINCIPAL_CLIENT_SECRET == "msi" ]]; then
if [[ -z $USER_ASSIGNED_IDENTITY_ID ]]; then
# using system-assigned identity to access keyvault
TOKEN=$(curl -s --retry 5 --retry-delay 10 --max-time 60 \
-H Metadata:true \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$KEYVAULT_ENDPOINT" | jq '.access_token' | xargs)
else
# using user-assigned managed identity to access keyvault
TOKEN=$(curl -s --retry 5 --retry-delay 10 --max-time 60 \
-H Metadata:true \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&client_id=$USER_ASSIGNED_IDENTITY_ID&resource=$KEYVAULT_ENDPOINT" | jq '.access_token' | xargs)
fi
else
# use service principal token to access keyvault
TOKEN=$(curl -s --retry 5 --retry-delay 10 --max-time 60 -f -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$SERVICE_PRINCIPAL_CLIENT_ID" \
--data-urlencode "client_secret=$SERVICE_PRINCIPAL_CLIENT_SECRET" \
--data-urlencode "resource=$KEYVAULT_ENDPOINT" \
${TOKEN_URL} | jq '.access_token' | xargs)
fi


if [[ -z $TOKEN ]]; then
echo "Error generating token for Azure Keyvault"
exit 120
fi

# Get the keyID for the kms key created as part of cluster bootstrap
KEY_ID=$(curl -s --retry 5 --retry-delay 10 --max-time 60 -f \
${KEYVAULT_URL} -H "Authorization: Bearer ${TOKEN}" | jq '.value[0].kid' | xargs)

if [[ -z "$KEY_ID" || "$KEY_ID" == "null" ]]; then
echo "Error getting the kms key version"
exit 120
fi

# KID format: https://<keyvault name>.vault.azure.net/keys/<key name>/<key version>
# Example KID: "https://akv0112master.vault.azure.net/keys/k8s/128a3d9956bc44feb6a0e2c2f35b732c"
KEY_VERSION=${KEY_ID##*/}

# Set the version in azure.json
if [ -f $AZURE_JSON_PATH ]; then
# once the version is set in azure.json, kms plugin will just default to using the key
# this will be changed in upcoming kms release to set the version as container args
tmpDir=$(mktemp -d "$(pwd)/XXX")
jq --arg KEY_VERSION ${KEY_VERSION} '.providerKeyVersion=($KEY_VERSION)' "$AZURE_JSON_PATH" > $tmpDir/tmp
mv $tmpDir/tmp "$AZURE_JSON_PATH"
# set the permissions for azure json
chmod 0600 "$AZURE_JSON_PATH"
chown root:root "$AZURE_JSON_PATH"
rm -Rf $tmpDir
fi

set -x
#EOF
14 changes: 14 additions & 0 deletions parts/k8s/cloud-init/masternodecustomdata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,20 @@ write_files:
endpoint: unix:///opt/azurekms.socket
cachesize: 1000
- identity: {}
- path: {{GetKMSKeyvaultKeyServiceCSEScriptFilepath}}
permissions: "0644"
encoding: gzip
owner: root
content: !!binary |
{{CloudInitData "kmsKeyvaultKeySystemdService"}}

- path: {{GetKMSKeyvaultKeyCSEScriptFilepath}}
permissions: "0544"
encoding: gzip
owner: root
content: !!binary |
{{CloudInitData "kmsKeyvaultKeyScript"}}
{{end}}

MASTER_MANIFESTS_CONFIG_PLACEHOLDER
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ const (
APIVersionAuthorizationSystem = "2018-09-01-preview"
APIVersionCompute = "2019-07-01"
APIVersionDeployments = "2018-06-01"
APIVersionKeyVault = "2018-02-14"
APIVersionKeyVault = "2019-09-01"
APIVersionManagedIdentity = "2018-11-30"
APIVersionNetwork = "2018-08-01"
APIVersionStorage = "2018-07-01"
Expand Down
14 changes: 8 additions & 6 deletions pkg/engine/armvariables.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ func getK8sMasterVars(cs *api.ContainerService) (map[string]interface{}, error)
"dockerMonitorSystemdService": getBase64EncodedGzippedCustomScript(kubernetesDockerMonitorSystemdService, cs),
}

if enableEncryptionWithExternalKms {
cloudInitFiles["kmsKeyvaultKeySystemdService"] = getBase64EncodedGzippedCustomScript(kmsKeyvaultKeySystemdService, cs)
cloudInitFiles["kmsKeyvaultKeyScript"] = getBase64EncodedGzippedCustomScript(kmsKeyvaultKeyScript, cs)
masterVars["clusterKeyVaultName"] = "[take(concat('kv', tolower(uniqueString(concat(variables('masterFqdnPrefix'),variables('location'),parameters('nameSuffix'))))), 22)]"
} else {
masterVars["clusterKeyVaultName"] = ""
}

if cs.Properties.OrchestratorProfile.KubernetesConfig.IsAddonEnabled(common.AADPodIdentityAddonName) {
cloudInitFiles["untaintNodesScript"] = getBase64EncodedGzippedCustomScript(untaintNodesScript, cs)
cloudInitFiles["untaintNodesSystemdService"] = getBase64EncodedGzippedCustomScript(untaintNodesSystemdService, cs)
Expand Down Expand Up @@ -520,12 +528,6 @@ func getK8sMasterVars(cs *api.ContainerService) (map[string]interface{}, error)
masterVars["windowsCustomScriptSuffix"] = " $inputFile = '%SYSTEMDRIVE%\\AzureData\\CustomData.bin' ; $outputFile = '%SYSTEMDRIVE%\\AzureData\\CustomDataSetupScript.ps1' ; Copy-Item $inputFile $outputFile ; Invoke-Expression('{0} {1}' -f $outputFile, $arguments) ; "
}

if enableEncryptionWithExternalKms {
masterVars["clusterKeyVaultName"] = "[take(concat('kv', tolower(uniqueString(concat(variables('masterFqdnPrefix'),variables('location'),parameters('nameSuffix'))))), 22)]"
} else {
masterVars["clusterKeyVaultName"] = ""
}

if cs.Properties.OrchestratorProfile.KubernetesConfig.IsAddonEnabled(common.AppGwIngressAddonName) {
masterVars["appGwName"] = "[concat(parameters('orchestratorName'), '-appgw-', parameters('nameSuffix'))]"
masterVars["appGwSubnetName"] = "appgw-subnet"
Expand Down
36 changes: 33 additions & 3 deletions pkg/engine/armvariables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestK8sVars(t *testing.T) {
"apiVersionAuthorizationUser": "2018-09-01-preview",
"apiVersionCompute": "2019-07-01",
"apiVersionDeployments": "2018-06-01",
"apiVersionKeyVault": "2018-02-14",
"apiVersionKeyVault": "2019-09-01",
"apiVersionManagedIdentity": "2018-11-30",
"apiVersionNetwork": "2018-08-01",
"apiVersionStorage": "2018-07-01",
Expand Down Expand Up @@ -201,7 +201,6 @@ func TestK8sVars(t *testing.T) {
}

diff := cmp.Diff(varMap, expectedMap)

if diff != "" {
t.Errorf("unexpected diff while expecting equal structs: %s", diff)
}
Expand Down Expand Up @@ -810,7 +809,7 @@ func TestK8sVarsMastersOnly(t *testing.T) {
"apiVersionAuthorizationUser": "2018-09-01-preview",
"apiVersionCompute": "2019-07-01",
"apiVersionDeployments": "2018-06-01",
"apiVersionKeyVault": "2018-02-14",
"apiVersionKeyVault": "2019-09-01",
"apiVersionManagedIdentity": "2018-11-30",
"apiVersionNetwork": "2018-08-01",
"apiVersionStorage": "2018-07-01",
Expand Down Expand Up @@ -929,6 +928,37 @@ func TestK8sVarsMastersOnly(t *testing.T) {
if diff != "" {
t.Errorf("unexpected diff while expecting equal structs: %s", diff)
}

// enable external kms encryption
cs.Properties.OrchestratorProfile.KubernetesConfig.EnableEncryptionWithExternalKms = to.BoolPtr(true)
expectedMap["clusterKeyVaultName"] = string("[take(concat('kv', tolower(uniqueString(concat(variables('masterFqdnPrefix'),variables('location'),parameters('nameSuffix'))))), 22)]")
expectedMap["cloudInitFiles"] = map[string]interface{}{
"provisionScript": getBase64EncodedGzippedCustomScript(kubernetesCSEMainScript, cs),
"provisionSource": getBase64EncodedGzippedCustomScript(kubernetesCSEHelpersScript, cs),
"provisionInstalls": getBase64EncodedGzippedCustomScript(kubernetesCSEInstall, cs),
"provisionConfigs": getBase64EncodedGzippedCustomScript(kubernetesCSEConfig, cs),
"customSearchDomainsScript": getBase64EncodedGzippedCustomScript(kubernetesCustomSearchDomainsScript, cs),
"etcdSystemdService": getBase64EncodedGzippedCustomScript(etcdSystemdService, cs),
"dhcpv6ConfigurationScript": getBase64EncodedGzippedCustomScript(dhcpv6ConfigurationScript, cs),
"dhcpv6SystemdService": getBase64EncodedGzippedCustomScript(dhcpv6SystemdService, cs),
"kubeletSystemdService": getBase64EncodedGzippedCustomScript(kubeletSystemdService, cs),
"etcdMonitorSystemdService": getBase64EncodedGzippedCustomScript(etcdMonitorSystemdService, cs),
"healthMonitorScript": getBase64EncodedGzippedCustomScript(kubernetesHealthMonitorScript, cs),
"kubeletMonitorSystemdService": getBase64EncodedGzippedCustomScript(kubernetesKubeletMonitorSystemdService, cs),
"dockerMonitorSystemdService": getBase64EncodedGzippedCustomScript(kubernetesDockerMonitorSystemdService, cs),
"kmsKeyvaultKeySystemdService": getBase64EncodedGzippedCustomScript(kmsKeyvaultKeySystemdService, cs),
"kmsKeyvaultKeyScript": getBase64EncodedGzippedCustomScript(kmsKeyvaultKeyScript, cs),
}

varMap, err = GetKubernetesVariables(cs)
if err != nil {
t.Fatal(err)
}
diff = cmp.Diff(varMap, expectedMap)

if diff != "" {
t.Errorf("unexpected diff while expecting equal structs: %s", diff)
}
}

func TestK8sVarsWindowsProfile(t *testing.T) {
Expand Down
19 changes: 12 additions & 7 deletions pkg/engine/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,22 @@ const (
// scripts and service for enabling ipv6 dual stack
dhcpv6SystemdService = "k8s/cloud-init/artifacts/dhcpv6.service"
dhcpv6ConfigurationScript = "k8s/cloud-init/artifacts/enable-dhcpv6.sh"
// script for getting key version from keyvault for kms
kmsKeyvaultKeySystemdService = "k8s/cloud-init/artifacts/kms-keyvault-key.service"
kmsKeyvaultKeyScript = "k8s/cloud-init/artifacts/kms-keyvault-key.sh"
)

// cloud-init destination file references
const (
customCloudConfigCSEScriptFilepath = "/opt/azure/containers/provision_configs_custom_cloud.sh"
cseHelpersScriptFilepath = "/opt/azure/containers/provision_source.sh"
cseInstallScriptFilepath = "/opt/azure/containers/provision_installs.sh"
cseConfigScriptFilepath = "/opt/azure/containers/provision_configs.sh"
customSearchDomainsCSEScriptFilepath = "/opt/azure/containers/setup-custom-search-domains.sh"
dhcpV6ServiceCSEScriptFilepath = "/etc/systemd/system/dhcpv6.service"
dhcpV6ConfigCSEScriptFilepath = "/opt/azure/containers/enable-dhcpv6.sh"
customCloudConfigCSEScriptFilepath = "/opt/azure/containers/provision_configs_custom_cloud.sh"
cseHelpersScriptFilepath = "/opt/azure/containers/provision_source.sh"
cseInstallScriptFilepath = "/opt/azure/containers/provision_installs.sh"
cseConfigScriptFilepath = "/opt/azure/containers/provision_configs.sh"
customSearchDomainsCSEScriptFilepath = "/opt/azure/containers/setup-custom-search-domains.sh"
dhcpV6ServiceCSEScriptFilepath = "/etc/systemd/system/dhcpv6.service"
dhcpV6ConfigCSEScriptFilepath = "/opt/azure/containers/enable-dhcpv6.sh"
kmsKeyvaultKeyServiceCSEScriptFilepath = "/etc/systemd/system/kms-keyvault-key.service"
kmsKeyvaultKeyCSEScriptFilepath = "/opt/azure/containers/kms-keyvault-key.sh"
)

const (
Expand Down
Loading

0 comments on commit 5d932f1

Please sign in to comment.