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