Skip to content

Commit cff9751

Browse files
committed
Update GCE Windows startup scripts for TPM-based authentication
"Shielded" nodes have a virtual TPM attached which is used for generating the client certificate, instead of using a bootstrap kubeconfig. Determining which to use happens during node startup based on the instance metadata.
1 parent b141a99 commit cff9751

File tree

5 files changed

+147
-60
lines changed

5 files changed

+147
-60
lines changed

cluster/gce/config-common.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ export WINDOWS_CNI_CONFIG_DIR="${WINDOWS_K8S_DIR}\cni\config"
144144
export WINDOWS_MANIFESTS_DIR="${WINDOWS_K8S_DIR}\manifests"
145145
# Directory where cert/key files will be stores on Windows nodes.
146146
export WINDOWS_PKI_DIR="${WINDOWS_K8S_DIR}\pki"
147+
# Location of the certificates file on Windows nodes.
148+
export WINDOWS_CA_FILE="${WINDOWS_PKI_DIR}\ca-certificates.crt"
147149
# Path for kubelet config file on Windows nodes.
148150
export WINDOWS_KUBELET_CONFIG_FILE="${WINDOWS_K8S_DIR}\kubelet-config.yaml"
149151
# Path for kubeconfig file on Windows nodes.

cluster/gce/util.sh

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -839,17 +839,6 @@ function construct-windows-kubelet-flags {
839839
# Many of these flags were adapted from
840840
# https://github.com/Microsoft/SDN/blob/master/Kubernetes/windows/start-kubelet.ps1.
841841
flags+=" --config=${WINDOWS_KUBELET_CONFIG_FILE}"
842-
843-
# Path to a kubeconfig file that will be used to get client certificate for
844-
# kubelet. If the file specified by --kubeconfig does not exist, the bootstrap
845-
# kubeconfig is used to request a client certificate from the API server. On
846-
# success, a kubeconfig file referencing the generated client certificate and
847-
# key is written to the path specified by --kubeconfig. The client certificate
848-
# and key file will be stored in the directory pointed by --cert-dir.
849-
#
850-
# See also:
851-
# https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-tls-bootstrapping/
852-
flags+=" --bootstrap-kubeconfig=${WINDOWS_BOOTSTRAP_KUBECONFIG_FILE}"
853842
flags+=" --kubeconfig=${WINDOWS_KUBECONFIG_FILE}"
854843

855844
# The directory where the TLS certs are located.
@@ -1066,7 +1055,7 @@ function print-windows-node-kubelet-config {
10661055
cat <<EOF
10671056
authentication:
10681057
x509:
1069-
clientCAFile: '${WINDOWS_PKI_DIR}\ca-certificates.crt'
1058+
clientCAFile: '${WINDOWS_CA_FILE}'
10701059
EOF
10711060
}
10721061

@@ -1534,6 +1523,7 @@ CNI_DIR: $(yaml-quote ${WINDOWS_CNI_DIR})
15341523
CNI_CONFIG_DIR: $(yaml-quote ${WINDOWS_CNI_CONFIG_DIR})
15351524
MANIFESTS_DIR: $(yaml-quote ${WINDOWS_MANIFESTS_DIR})
15361525
PKI_DIR: $(yaml-quote ${WINDOWS_PKI_DIR})
1526+
CA_FILE_PATH: $(yaml-quote ${WINDOWS_CA_FILE})
15371527
KUBELET_CONFIG_FILE: $(yaml-quote ${WINDOWS_KUBELET_CONFIG_FILE})
15381528
KUBEPROXY_ARGS: $(yaml-quote ${KUBEPROXY_ARGS})
15391529
KUBECONFIG_FILE: $(yaml-quote ${WINDOWS_KUBECONFIG_FILE})

cluster/gce/windows/common.psm1

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
#>
2020

2121
# IMPORTANT PLEASE NOTE:
22-
# Any time the file structure in the `windows` directory changes, `windows/BUILD`
23-
# and `k8s.io/release/lib/releaselib.sh` must be manually updated with the changes.
22+
# Any time the file structure in the `windows` directory changes,
23+
# `windows/BUILD` and `k8s.io/release/lib/releaselib.sh` must be manually
24+
# updated with the changes.
2425
# We HIGHLY recommend not changing the file structure, because consumers of
2526
# Kubernetes releases depend on the release structure remaining stable.
2627

@@ -544,5 +545,16 @@ function Test-IsTestCluster {
544545
return $false
545546
}
546547

548+
# Returns true if this node uses a plugin to support authentication to the
549+
# master, e.g. for TPM-based authentication. $KubeEnv is a hash table
550+
# containing the kube-env metadata keys+values.
551+
function Test-NodeUsesAuthPlugin {
552+
param (
553+
[parameter(Mandatory=$true)] [hashtable]$KubeEnv
554+
)
555+
556+
return $KubeEnv.Contains('EXEC_AUTH_PLUGIN_URL')
557+
}
558+
547559
# Export all public functions:
548560
Export-ModuleMember -Function *-*

cluster/gce/windows/configure.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ try {
133133

134134
DownloadAndInstall-Crictl
135135
Setup-ContainerRuntime
136+
DownloadAndInstall-AuthPlugin
136137
DownloadAndInstall-KubernetesBinaries
137138
Create-NodePki
138139
Create-KubeletKubeconfig

cluster/gce/windows/k8s-node-setup.psm1

Lines changed: 128 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -237,24 +237,23 @@ function Set-EnvironmentVars {
237237
"CNI_DIR" = ${kube_env}['CNI_DIR']
238238
"CNI_CONFIG_DIR" = ${kube_env}['CNI_CONFIG_DIR']
239239
"PKI_DIR" = ${kube_env}['PKI_DIR']
240+
"CA_FILE_PATH" = ${kube_env}['CA_FILE_PATH']
240241
"KUBELET_CONFIG" = ${kube_env}['KUBELET_CONFIG_FILE']
241242
"BOOTSTRAP_KUBECONFIG" = ${kube_env}['BOOTSTRAP_KUBECONFIG_FILE']
243+
"KUBECONFIG" = ${kube_env}['KUBECONFIG_FILE']
242244
"KUBEPROXY_KUBECONFIG" = ${kube_env}['KUBEPROXY_KUBECONFIG_FILE']
245+
"LOGS_DIR" = ${kube_env}['LOGS_DIR']
246+
"MANIFESTS_DIR" = ${kube_env}['MANIFESTS_DIR']
243247

244248
"Path" = ${env:Path} + ";" + ${kube_env}['NODE_DIR']
245249
"KUBE_NETWORK" = "l2bridge".ToLower()
246-
"CA_CERT_BUNDLE_PATH" = ${kube_env}['PKI_DIR'] + '\ca-certificates.crt'
247250
"KUBELET_CERT_PATH" = ${kube_env}['PKI_DIR'] + '\kubelet.crt'
248251
"KUBELET_KEY_PATH" = ${kube_env}['PKI_DIR'] + '\kubelet.key'
249252

250253
"CONTAINER_RUNTIME" = ${kube_env}['CONTAINER_RUNTIME']
251254
"CONTAINER_RUNTIME_ENDPOINT" = ${kube_env}['CONTAINER_RUNTIME_ENDPOINT']
252255

253-
# TODO(pjh): these are only in flags, can be removed from env once flags are
254-
# moved to util.sh:
255-
"LOGS_DIR" = ${kube_env}['LOGS_DIR']
256-
"MANIFESTS_DIR" = ${kube_env}['MANIFESTS_DIR']
257-
"KUBECONFIG" = ${kube_env}['KUBECONFIG_FILE']
256+
'LICENSE_DIR' = 'C:\Program Files\Google\Compute Engine\THIRD_PARTY_NOTICES'
258257
}
259258

260259
# Set the environment variables in two ways: permanently on the machine (only
@@ -289,7 +288,7 @@ function Create-Directories {
289288
Log-Output "Creating ${env:K8S_DIR} and its subdirectories."
290289
ForEach ($dir in ("${env:K8S_DIR}", "${env:NODE_DIR}", "${env:LOGS_DIR}",
291290
"${env:CNI_DIR}", "${env:CNI_CONFIG_DIR}", "${env:MANIFESTS_DIR}",
292-
"${env:PKI_DIR}"), "C:\tmp", "C:\var\log") {
291+
"${env:PKI_DIR}", "${env:LICENSE_DIR}"), "C:\tmp", "C:\var\log") {
293292
mkdir -Force $dir
294293
}
295294
}
@@ -322,6 +321,39 @@ function Get_ContainerVersionLabel {
322321
"version label")
323322
}
324323

324+
# Downloads the gke-exec-auth-plugin for TPM-based authentication to the
325+
# master, if auth plugin support has been requested for this node (see
326+
# Test-NodeUsesAuthPlugin).
327+
# https://github.com/kubernetes/cloud-provider-gcp/tree/master/cmd/gke-exec-auth-plugin
328+
#
329+
# Required ${kube_env} keys:
330+
# EXEC_AUTH_PLUGIN_LICENSE_URL
331+
# EXEC_AUTH_PLUGIN_SHA1
332+
# EXEC_AUTH_PLUGIN_URL
333+
function DownloadAndInstall-AuthPlugin {
334+
if (-not (Test-NodeUsesAuthPlugin ${kube_env})) {
335+
Log-Output 'Skipping download of auth plugin'
336+
return
337+
}
338+
if (-not (ShouldWrite-File "${env:NODE_DIR}\gke-exec-auth-plugin.exe")) {
339+
return
340+
}
341+
342+
if (-not ($kube_env.ContainsKey('EXEC_AUTH_PLUGIN_LICENSE_URL') -and
343+
$kube_env.ContainsKey('EXEC_AUTH_PLUGIN_SHA1') -and
344+
$kube_env.ContainsKey('EXEC_AUTH_PLUGIN_URL'))) {
345+
Log-Output -Fatal ("Missing one or more kube-env keys needed for " +
346+
"downloading auth plugin: $(Out-String $kube_env)")
347+
}
348+
MustDownload-File `
349+
-URLs ${kube_env}['EXEC_AUTH_PLUGIN_URL'] `
350+
-Hash ${kube_env}['EXEC_AUTH_PLUGIN_SHA1'] `
351+
-OutFile "${env:NODE_DIR}\gke-exec-auth-plugin.exe"
352+
MustDownload-File `
353+
-URLs ${kube_env}['EXEC_AUTH_PLUGIN_LICENSE_URL'] `
354+
-OutFile "${env:LICENSE_DIR}\LICENSE_gke-exec-auth-plugin.txt"
355+
}
356+
325357
# Downloads the Kubernetes binaries from kube-env's NODE_BINARY_TAR_URL and
326358
# puts them in a subdirectory of $env:K8S_DIR.
327359
#
@@ -477,44 +509,66 @@ function Write_PkiData {
477509
#
478510
# Required ${kube_env} keys:
479511
# CA_CERT
512+
# ${kube_env} keys that can be omitted for nodes that do not use an
513+
# authentication plugin:
480514
# KUBELET_CERT
481515
# KUBELET_KEY
482516
function Create-NodePki {
483-
Log-Output "Creating node pki files"
517+
Log-Output 'Creating node pki files'
518+
519+
if ($kube_env.ContainsKey('CA_CERT')) {
520+
$CA_CERT_BUNDLE = ${kube_env}['CA_CERT']
521+
Write_PkiData "${CA_CERT_BUNDLE}" ${env:CA_FILE_PATH}
522+
}
523+
else {
524+
Log-Output -Fatal 'CA_CERT not present in kube-env'
525+
}
484526

485-
$CA_CERT_BUNDLE = ${kube_env}['CA_CERT']
486-
$KUBELET_CERT = ${kube_env}['KUBELET_CERT']
487-
$KUBELET_KEY = ${kube_env}['KUBELET_KEY']
527+
# On nodes that use a plugin to support authentication, KUBELET_CERT and
528+
# KUBELET_KEY will not be present - TPM_BOOTSTRAP_CERT and TPM_BOOTSTRAP_KEY
529+
# should be set instead.
530+
if (Test-NodeUsesAuthPlugin ${kube_env}) {
531+
Log-Output ('Skipping KUBELET_CERT and KUBELET_KEY, plugin will be used ' +
532+
'for authentication')
533+
return
534+
}
535+
536+
if ($kube_env.ContainsKey('KUBELET_CERT')) {
537+
$KUBELET_CERT = ${kube_env}['KUBELET_CERT']
538+
Write_PkiData "${KUBELET_CERT}" ${env:KUBELET_CERT_PATH}
539+
}
540+
else {
541+
Log-Output -Fatal 'KUBELET_CERT not present in kube-env'
542+
}
543+
if ($kube_env.ContainsKey('KUBELET_KEY')) {
544+
$KUBELET_KEY = ${kube_env}['KUBELET_KEY']
545+
Write_PkiData "${KUBELET_KEY}" ${env:KUBELET_KEY_PATH}
546+
}
547+
else {
548+
Log-Output -Fatal 'KUBELET_KEY not present in kube-env'
549+
}
488550

489-
Write_PkiData "${CA_CERT_BUNDLE}" ${env:CA_CERT_BUNDLE_PATH}
490-
Write_PkiData "${KUBELET_CERT}" ${env:KUBELET_CERT_PATH}
491-
Write_PkiData "${KUBELET_KEY}" ${env:KUBELET_KEY_PATH}
492551
Get-ChildItem ${env:PKI_DIR}
493552
}
494553

495-
# Creates the kubelet kubeconfig at $env:BOOTSTRAP_KUBECONFIG.
554+
# Creates the bootstrap kubelet kubeconfig at $env:BOOTSTRAP_KUBECONFIG.
555+
# https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-tls-bootstrapping/
496556
#
497557
# Create-NodePki() must be called first.
498558
#
499559
# Required ${kube_env} keys:
500560
# KUBERNETES_MASTER_NAME: the apiserver IP address.
501-
function Create-KubeletKubeconfig {
502-
# The API server IP address comes from KUBERNETES_MASTER_NAME in kube-env, I
503-
# think. cluster/gce/gci/configure-helper.sh?l=2801
504-
$apiserverAddress = ${kube_env}['KUBERNETES_MASTER_NAME']
561+
function Write_BootstrapKubeconfig {
562+
if (-not (ShouldWrite-File ${env:BOOTSTRAP_KUBECONFIG})) {
563+
return
564+
}
505565

506-
# TODO(pjh): set these using kube-env values.
507-
$createBootstrapConfig = $true
508-
$fetchBootstrapConfig = $false
566+
# TODO(mtaufen): is user "kubelet" correct? Other examples use e.g.
567+
# "system:node:$(hostname)".
509568

510-
if (${createBootstrapConfig}) {
511-
if (-not (ShouldWrite-File ${env:BOOTSTRAP_KUBECONFIG})) {
512-
return
513-
}
514-
New-Item -Force -ItemType file ${env:BOOTSTRAP_KUBECONFIG} | Out-Null
515-
# TODO(mtaufen): is user "kubelet" correct? Other examples use e.g.
516-
# "system:node:$(hostname)".
517-
Set-Content ${env:BOOTSTRAP_KUBECONFIG} `
569+
$apiserverAddress = ${kube_env}['KUBERNETES_MASTER_NAME']
570+
New-Item -Force -ItemType file ${env:BOOTSTRAP_KUBECONFIG} | Out-Null
571+
Set-Content ${env:BOOTSTRAP_KUBECONFIG} `
518572
'apiVersion: v1
519573
kind: Config
520574
users:
@@ -526,30 +580,53 @@ clusters:
526580
- name: local
527581
cluster:
528582
server: https://APISERVER_ADDRESS
529-
certificate-authority: CA_CERT_BUNDLE_PATH
583+
certificate-authority: CA_FILE_PATH
530584
contexts:
531585
- context:
532586
cluster: local
533587
user: kubelet
534588
name: service-account-context
535589
current-context: service-account-context'.`
536-
replace('KUBELET_CERT_PATH', ${env:KUBELET_CERT_PATH}).`
537-
replace('KUBELET_KEY_PATH', ${env:KUBELET_KEY_PATH}).`
538-
replace('APISERVER_ADDRESS', ${apiserverAddress}).`
539-
replace('CA_CERT_BUNDLE_PATH', ${env:CA_CERT_BUNDLE_PATH})
540-
Log-Output ("kubelet bootstrap kubeconfig:`n" +
541-
"$(Get-Content -Raw ${env:BOOTSTRAP_KUBECONFIG})")
590+
replace('KUBELET_CERT_PATH', ${env:KUBELET_CERT_PATH}).`
591+
replace('KUBELET_KEY_PATH', ${env:KUBELET_KEY_PATH}).`
592+
replace('APISERVER_ADDRESS', ${apiserverAddress}).`
593+
replace('CA_FILE_PATH', ${env:CA_FILE_PATH})
594+
Log-Output ("kubelet bootstrap kubeconfig:`n" +
595+
"$(Get-Content -Raw ${env:BOOTSTRAP_KUBECONFIG})")
596+
}
597+
598+
# Fetches the kubelet kubeconfig from the metadata server and writes it to
599+
# $env:KUBECONFIG.
600+
#
601+
# Create-NodePki() must be called first.
602+
function Write_KubeconfigFromMetadata {
603+
if (-not (ShouldWrite-File ${env:KUBECONFIG})) {
604+
return
542605
}
543-
elseif (${fetchBootstrapConfig}) {
544-
Log_NotImplemented `
545-
"fetching kubelet bootstrap-kubeconfig file from metadata"
546-
# get-metadata-value "instance/attributes/bootstrap-kubeconfig" >
547-
# /var/lib/kubelet/bootstrap-kubeconfig
548-
Log-Output ("kubelet bootstrap kubeconfig:`n" +
549-
"$(Get-Content -Raw ${env:BOOTSTRAP_KUBECONFIG})")
606+
607+
$kubeconfig = Get-InstanceMetadataAttribute 'kubeconfig'
608+
if ($kubeconfig -eq $null) {
609+
Log-Output `
610+
"kubeconfig metadata key not found, can't write ${env:KUBECONFIG}" `
611+
-Fatal
550612
}
551-
else {
552-
Log_NotImplemented "fetching kubelet kubeconfig file from metadata"
613+
Set-Content ${env:KUBECONFIG} $kubeconfig
614+
Log-Output ("kubelet kubeconfig from metadata (non-bootstrap):`n" +
615+
"$(Get-Content -Raw ${env:KUBECONFIG})")
616+
}
617+
618+
# Creates the kubelet kubeconfig at $env:KUBECONFIG for nodes that use an
619+
# authentication plugin, or at $env:BOOTSTRAP_KUBECONFIG for nodes that do not.
620+
#
621+
# Create-NodePki() must be called first.
622+
#
623+
# Required ${kube_env} keys:
624+
# KUBERNETES_MASTER_NAME: the apiserver IP address.
625+
function Create-KubeletKubeconfig {
626+
if (Test-NodeUsesAuthPlugin ${kube_env}) {
627+
Write_KubeconfigFromMetadata
628+
} else {
629+
Write_BootstrapKubeconfig
553630
}
554631
}
555632

@@ -1045,6 +1122,11 @@ function Start-WorkerServices {
10451122
"--pod-infra-container-image=${INFRA_CONTAINER}"
10461123
)
10471124
$kubelet_args = ${default_kubelet_args} + ${kubelet_args}
1125+
if (-not (Test-NodeUsesAuthPlugin ${kube_env})) {
1126+
Log-Output 'Using bootstrap kubeconfig for authentication'
1127+
$kubelet_args = (${kubelet_args} +
1128+
"--bootstrap-kubeconfig=${env:BOOTSTRAP_KUBECONFIG}")
1129+
}
10481130
Log-Output "Final kubelet_args: ${kubelet_args}"
10491131

10501132
# Compute kube-proxy args

0 commit comments

Comments
 (0)