sigs.k8s.io/cluster-api-provider-azure@v1.17.0/scripts/kind-with-registry.sh (about) 1 #!/usr/bin/env bash 2 # Copyright 2020 The Kubernetes Authors. 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 set -o errexit 17 set -o nounset 18 set -o pipefail 19 20 # Install kubectl and kind 21 REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. 22 # shellcheck source=hack/ensure-azcli.sh 23 source "${REPO_ROOT}/hack/ensure-azcli.sh" 24 # shellcheck source=hack/ensure-tags.sh 25 source "${REPO_ROOT}/hack/ensure-tags.sh" 26 27 KUBECTL="${REPO_ROOT}/hack/tools/bin/kubectl" 28 KIND="${REPO_ROOT}/hack/tools/bin/kind" 29 AZWI="${REPO_ROOT}/hack/tools/bin/azwi" 30 AZWI_ENABLED="${AZWI_ENABLED:-true}" 31 RANDOM_SUFFIX="${RANDOM_SUFFIX:-$(od -An -N4 -tu4 /dev/urandom | tr -d ' ' | head -c 8)}" 32 export AZWI_STORAGE_ACCOUNT="capzcioidcissuer${RANDOM_SUFFIX}" 33 export AZWI_STORAGE_CONTAINER="\$web" 34 export AZWI_LOCATION="${AZURE_LOCATION:-southcentralus}" 35 export SERVICE_ACCOUNT_ISSUER="${SERVICE_ACCOUNT_ISSUER:-}" 36 export SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH="${SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH:-}" 37 export SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH="${SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH:-}" 38 export AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY="${AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY:-}" 39 export AZURE_IDENTITY_ID_FILEPATH="${AZURE_IDENTITY_ID_FILEPATH:-$REPO_ROOT/azure_identity_id}" 40 make --directory="${REPO_ROOT}" "${KUBECTL##*/}" "${KIND##*/}" 41 42 # Export desired cluster name; default is "capz" 43 KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-capz}" 44 CONFORMANCE_FLAVOR="${CONFORMANCE_FLAVOR:-}" 45 export KIND_CLUSTER_NAME 46 47 if [[ "$("${KIND}" get clusters)" =~ .*"${KIND_CLUSTER_NAME}".* ]]; then 48 echo "cluster already exists, moving on" 49 exit 0 50 fi 51 52 # 1. Create registry container unless it already exists 53 reg_name='kind-registry' 54 reg_port="${KIND_REGISTRY_PORT:-5000}" 55 if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then 56 docker run \ 57 -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \ 58 registry:2 59 fi 60 61 # Environment variable inputs 62 # SERVICE_ACCOUNT_ISSUER - BYO existing service account issuer 63 # Accepts a URI string, e.g., https://${AZWI_STORAGE_ACCOUNT}.blob.core.windows.net/${AZWI_STORAGE_CONTAINER}/ 64 # Assumes that the required openid and jwks artifacts exist in the well-known locations at this URI 65 # SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH - A local filepath to a 2048 bit RSA public key 66 # Defaults to capz-wi-sa.pub, must exist locally and match the signed artifacts if using BYO 67 # SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH - A local filepath to a 2048 bit RSA private key 68 # Defaults to capz-wi-sa.key, must exist locally and match the signed artifacts if using BYO 69 # If the above keypair filepaths environment variables are not included, a keypair will be created at runtime 70 # Note: if a new keypair is created at runtime then you must not BYO service account issuer 71 # AZWI_RESOURCE_GROUP - Azure resource group where Workload Identity infra lives 72 # AZWI_LOCATION - Azure location for Workload Identity infra 73 # AZWI_STORAGE_ACCOUNT - Storage account in resource group $AZWI_RESOURCE_GROUP containing required artifacts 74 # Must be configured for static website hosting 75 # AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY - BYO existing user-assigned identity 76 # Should be a UUID that represents the clientID of the identity object 77 # USER_IDENTITY - Name to use when creating a new user-assigned identity 78 # Required if not bringing your own identity via $AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY 79 # AZURE_IDENTITY_ID_FILEPATH - A local filepath to store the newly created user-assigned identity if not bringing your own 80 function checkAZWIENVPreReqsAndCreateFiles() { 81 if [[ -z "${SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH}" || -z "${SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH}" ]]; then 82 export SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH="${REPO_ROOT}/capz-wi-sa.pub" 83 export SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH="${REPO_ROOT}/capz-wi-sa.key" 84 fi 85 if [ -z "${SERVICE_ACCOUNT_ISSUER}" ]; then 86 # check if user is logged into azure cli 87 if ! az account show > /dev/null 2>&1; then 88 echo "Please login to Azure CLI using 'az login'" 89 exit 1 90 fi 91 92 if [ -z "${AZWI_RESOURCE_GROUP}" ]; then 93 echo "AZWI_RESOURCE_GROUP environment variable required - Azure resource group to store required Workload Identity artifacts" 94 exit 1 95 fi 96 97 if [ "$(az group exists --name "${AZWI_RESOURCE_GROUP}" --output tsv)" == 'false' ]; then 98 echo "Creating resource group '${AZWI_RESOURCE_GROUP}' in '${AZWI_LOCATION}'" 99 az group create --name "${AZWI_RESOURCE_GROUP}" --location "${AZWI_LOCATION}" --output none --only-show-errors --tags creationTimestamp="${TIMESTAMP}" jobName="${JOB_NAME}" buildProvenance="${BUILD_PROVENANCE}" 100 fi 101 102 # Ensure that our connection to storage is inherited from the existing Azure login context 103 unset AZURE_STORAGE_KEY 104 unset AZURE_STORAGE_ACCOUNT 105 106 if ! az storage account show --name "${AZWI_STORAGE_ACCOUNT}" --resource-group "${AZWI_RESOURCE_GROUP}" > /dev/null 2>&1; then 107 echo "Creating storage account '${AZWI_STORAGE_ACCOUNT}' in '${AZWI_RESOURCE_GROUP}'" 108 az storage account create --resource-group "${AZWI_RESOURCE_GROUP}" --name "${AZWI_STORAGE_ACCOUNT}" --output none --only-show-errors --tags creationTimestamp="${TIMESTAMP}" jobName="${JOB_NAME}" buildProvenance="${BUILD_PROVENANCE}" 109 until az storage account show --name "${AZWI_STORAGE_ACCOUNT}" --resource-group "${AZWI_RESOURCE_GROUP}" > /dev/null 2>&1; do 110 sleep 5 111 done 112 echo "Configuring storage account '${AZWI_STORAGE_ACCOUNT}' as static website" 113 az storage blob service-properties update --account-name "${AZWI_STORAGE_ACCOUNT}" --static-website --auth-mode login 114 fi 115 116 if ! az storage container show --name "${AZWI_STORAGE_CONTAINER}" --account-name "${AZWI_STORAGE_ACCOUNT}" --auth-mode login > /dev/null 2>&1; then 117 echo "Creating storage container '${AZWI_STORAGE_CONTAINER}' in '${AZWI_STORAGE_ACCOUNT}'" 118 az storage container create --name "${AZWI_STORAGE_CONTAINER}" --account-name "${AZWI_STORAGE_ACCOUNT}" --output none --only-show-errors --auth-mode login 119 fi 120 121 SERVICE_ACCOUNT_ISSUER=$(az storage account show --name "${AZWI_STORAGE_ACCOUNT}" --resource-group "${AZWI_RESOURCE_GROUP}" -o json | jq -r .primaryEndpoints.web) 122 export SERVICE_ACCOUNT_ISSUER 123 AZWI_OPENID_CONFIG_FILEPATH="${REPO_ROOT}/openid-configuration.json" 124 cat <<EOF > "${AZWI_OPENID_CONFIG_FILEPATH}" 125 { 126 "issuer": "${SERVICE_ACCOUNT_ISSUER}", 127 "jwks_uri": "${SERVICE_ACCOUNT_ISSUER}openid/v1/jwks", 128 "response_types_supported": [ 129 "id_token" 130 ], 131 "subject_types_supported": [ 132 "public" 133 ], 134 "id_token_signing_alg_values_supported": [ 135 "RS256" 136 ] 137 } 138 EOF 139 openssl genrsa -out "${SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH}" 2048 140 openssl rsa -in "${SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH}" -pubout -out "${SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH}" 141 AZWI_JWKS_JSON_FILEPATH="${REPO_ROOT}/jwks.json" 142 "${AZWI}" jwks --public-keys "${SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH}" --output-file "${AZWI_JWKS_JSON_FILEPATH}" 143 144 echo "Uploading openid-configuration document to '${AZWI_STORAGE_ACCOUNT}' storage account" 145 upload_to_blob "${AZWI_OPENID_CONFIG_FILEPATH}" ".well-known/openid-configuration" 146 147 echo "Uploading jwks document to '${AZWI_STORAGE_ACCOUNT}' storage account" 148 upload_to_blob "${AZWI_JWKS_JSON_FILEPATH}" "openid/v1/jwks" 149 fi 150 151 if [ -z "${AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY}" ]; then 152 if [ -z "${USER_IDENTITY}" ]; then 153 echo "USER_IDENTITY environment variable required if not bringing your own identity via AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY" 154 exit 1 155 fi 156 157 az identity create -n "${USER_IDENTITY}" -g "${AZWI_RESOURCE_GROUP}" -l "${AZWI_LOCATION}" --output none --only-show-errors --tags creationTimestamp="${TIMESTAMP}" jobName="${JOB_NAME}" buildProvenance="${BUILD_PROVENANCE}" 158 AZURE_IDENTITY_ID=$(az identity show -n "${USER_IDENTITY}" -g "${AZWI_RESOURCE_GROUP}" --query clientId -o tsv) 159 AZURE_IDENTITY_ID_PRINCIPAL_ID=$(az identity show -n "${USER_IDENTITY}" -g "${AZWI_RESOURCE_GROUP}" --query principalId -o tsv) 160 161 echo "${AZURE_IDENTITY_ID}" > "${AZURE_IDENTITY_ID_FILEPATH}" 162 until az role assignment create --assignee-object-id "${AZURE_IDENTITY_ID_PRINCIPAL_ID}" --role "Contributor" --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}" --assignee-principal-type ServicePrincipal; do 163 sleep 5 164 done 165 until az role assignment create --assignee-object-id "${AZURE_IDENTITY_ID_PRINCIPAL_ID}" --role "Role Based Access Control Administrator" --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}" --assignee-principal-type ServicePrincipal; do 166 sleep 5 167 done 168 until az role assignment create --assignee-object-id "${AZURE_IDENTITY_ID_PRINCIPAL_ID}" --role "Storage Blob Data Reader" --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}" --assignee-principal-type ServicePrincipal; do 169 sleep 5 170 done 171 172 echo "Creating federated credentials for capz-federated-identity" 173 az identity federated-credential create -n "capz-federated-identity" \ 174 --identity-name "${USER_IDENTITY}" \ 175 -g "${AZWI_RESOURCE_GROUP}" \ 176 --issuer "${SERVICE_ACCOUNT_ISSUER}" \ 177 --subject "system:serviceaccount:capz-system:capz-manager" --output none --only-show-errors 178 179 echo "Creating federated credentials for aso-federated-identity" 180 az identity federated-credential create -n "aso-federated-identity" \ 181 --identity-name "${USER_IDENTITY}" \ 182 -g "${AZWI_RESOURCE_GROUP}" \ 183 --issuer "${SERVICE_ACCOUNT_ISSUER}" \ 184 --subject "system:serviceaccount:capz-system:azureserviceoperator-default" --output none --only-show-errors 185 fi 186 } 187 188 function upload_to_blob() { 189 local file_path=$1 190 local blob_name=$2 191 192 echo "Uploading ${file_path} to '${AZWI_STORAGE_ACCOUNT}' storage account" 193 az storage blob upload \ 194 --container-name "${AZWI_STORAGE_CONTAINER}" \ 195 --file "${file_path}" \ 196 --name "${blob_name}" \ 197 --account-name "${AZWI_STORAGE_ACCOUNT}" \ 198 --output none --only-show-errors \ 199 --auth-mode login 200 } 201 202 # This function create a kind cluster for Workload identity which requires key pairs path 203 # to be mounted on the kind cluster and hence extra mount flags are required. 204 function createKindForAZWI() { 205 echo "creating workload-identity-enabled kind configuration" 206 cat <<EOF | "${KIND}" create cluster --name "${KIND_CLUSTER_NAME}" --config=- 207 kind: Cluster 208 apiVersion: kind.x-k8s.io/v1alpha4 209 nodes: 210 - role: control-plane 211 extraMounts: 212 - hostPath: "${SERVICE_ACCOUNT_SIGNING_PUB_FILEPATH}" 213 containerPath: /etc/kubernetes/pki/sa.pub 214 - hostPath: "${SERVICE_ACCOUNT_SIGNING_KEY_FILEPATH}" 215 containerPath: /etc/kubernetes/pki/sa.key 216 kubeadmConfigPatches: 217 - | 218 kind: ClusterConfiguration 219 apiServer: 220 extraArgs: 221 service-account-issuer: ${SERVICE_ACCOUNT_ISSUER} 222 service-account-key-file: /etc/kubernetes/pki/sa.pub 223 service-account-signing-key-file: /etc/kubernetes/pki/sa.key 224 controllerManager: 225 extraArgs: 226 service-account-private-key-file: /etc/kubernetes/pki/sa.key 227 containerdConfigPatches: 228 - |- 229 [plugins."io.containerd.grpc.v1.cri".registry] 230 config_path = "/etc/containerd/certs.d" 231 EOF 232 } 233 234 # 2. Create kind cluster with containerd registry config dir enabled 235 # TODO: kind will eventually enable this by default and this patch will 236 # be unnecessary. 237 # 238 # See: 239 # https://github.com/kubernetes-sigs/kind/issues/2875 240 # https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration 241 # See: https://github.com/containerd/containerd/blob/main/docs/hosts.md 242 if [ "$AZWI_ENABLED" == 'true' ] 243 then 244 echo "workload-identity is enabled..." 245 checkAZWIENVPreReqsAndCreateFiles 246 createKindForAZWI 247 else 248 echo "workload-identity is not enabled..." 249 cat <<EOF | ${KIND} create cluster --name "${KIND_CLUSTER_NAME}" --config=- 250 kind: Cluster 251 apiVersion: kind.x-k8s.io/v1alpha4 252 containerdConfigPatches: 253 - |- 254 [plugins."io.containerd.grpc.v1.cri".registry] 255 config_path = "/etc/containerd/certs.d" 256 EOF 257 fi 258 259 # 3. Add the registry config to the nodes 260 # 261 # This is necessary because localhost resolves to loopback addresses that are 262 # network-namespace local. 263 # In other words: localhost in the container is not localhost on the host. 264 # 265 # We want a consistent name that works from both ends, so we tell containerd to 266 # alias localhost:${reg_port} to the registry container when pulling images 267 REGISTRY_DIR="/etc/containerd/certs.d/localhost:${reg_port}" 268 for node in $(${KIND} get nodes); do 269 docker exec "${node}" mkdir -p "${REGISTRY_DIR}" 270 cat <<EOF | docker exec -i "${node}" cp /dev/stdin "${REGISTRY_DIR}/hosts.toml" 271 [host."http://${reg_name}:5000"] 272 EOF 273 done 274 275 # 4. Connect the registry to the cluster network if not already connected 276 # This allows kind to bootstrap the network but ensures they're on the same network 277 if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then 278 docker network connect "kind" "${reg_name}" 279 fi 280 281 # 5. Document the local registry 282 # https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry 283 "${KIND}" get kubeconfig -n "${KIND_CLUSTER_NAME}" > "${REPO_ROOT}/${KIND_CLUSTER_NAME}.kubeconfig" 284 cat <<EOF | "${KUBECTL}" --kubeconfig "${REPO_ROOT}/${KIND_CLUSTER_NAME}.kubeconfig" apply -f - 285 apiVersion: v1 286 kind: ConfigMap 287 metadata: 288 name: local-registry-hosting 289 namespace: kube-public 290 data: 291 localRegistryHosting.v1: | 292 host: "localhost:${reg_port}" 293 help: "https://kind.sigs.k8s.io/docs/user/local-registry/" 294 EOF 295 296 # Wait 90s for the control plane node to be ready 297 "${KUBECTL}" --kubeconfig "${REPO_ROOT}/${KIND_CLUSTER_NAME}.kubeconfig" wait node "${KIND_CLUSTER_NAME}-control-plane" --for=condition=ready --timeout=90s