#!/bin/bash # Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Common utilities for kube-up/kube-down set -o errexit set -o nounset set -o pipefail KUBE_ROOT=$(cd $(dirname "${BASH_SOURCE}")/.. && pwd) DEFAULT_KUBECONFIG="${HOME:-.}/.kube/config" source "${KUBE_ROOT}/hack/lib/util.sh" # KUBE_RELEASE_VERSION_REGEX matches things like "v1.2.3" or "v1.2.3-alpha.4" # # NOTE This must match the version_regex in build/common.sh # kube::release::parse_and_validate_release_version() KUBE_RELEASE_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-([a-zA-Z0-9]+)\\.(0|[1-9][0-9]*))?$" KUBE_RELEASE_VERSION_DASHED_REGEX="v(0|[1-9][0-9]*)-(0|[1-9][0-9]*)-(0|[1-9][0-9]*)(-([a-zA-Z0-9]+)-(0|[1-9][0-9]*))?" # KUBE_CI_VERSION_REGEX matches things like "v1.2.3-alpha.4.56+abcdefg" This # # NOTE This must match the version_regex in build/common.sh # kube::release::parse_and_validate_ci_version() KUBE_CI_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-([a-zA-Z0-9]+)\\.(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*)\\+[-0-9a-z]*)?$" KUBE_CI_VERSION_DASHED_REGEX="^v(0|[1-9][0-9]*)-(0|[1-9][0-9]*)-(0|[1-9][0-9]*)-([a-zA-Z0-9]+)-(0|[1-9][0-9]*)(-(0|[1-9][0-9]*)\\+[-0-9a-z]*)?" # Generate kubeconfig data for the created cluster. # Assumed vars: # KUBE_USER # KUBE_PASSWORD # KUBE_MASTER_IP # KUBECONFIG # CONTEXT # # If the apiserver supports bearer auth, also provide: # KUBE_BEARER_TOKEN # # If the kubeconfig context being created should NOT be set as the current context # SECONDARY_KUBECONFIG=true # # To explicitly name the context being created, use OVERRIDE_CONTEXT # # The following can be omitted for --insecure-skip-tls-verify # KUBE_CERT # KUBE_KEY # CA_CERT function create-kubeconfig() { KUBECONFIG=${KUBECONFIG:-$DEFAULT_KUBECONFIG} local kubectl="${KUBE_ROOT}/cluster/kubectl.sh" SECONDARY_KUBECONFIG=${SECONDARY_KUBECONFIG:-} OVERRIDE_CONTEXT=${OVERRIDE_CONTEXT:-} if [[ "$OVERRIDE_CONTEXT" != "" ]];then CONTEXT=$OVERRIDE_CONTEXT fi # KUBECONFIG determines the file we write to, but it may not exist yet OLD_IFS=$IFS IFS=':' for cfg in ${KUBECONFIG} ; do if [[ ! -e "${cfg}" ]]; then mkdir -p "$(dirname "${cfg}")" touch "${cfg}" fi done IFS=$OLD_IFS local cluster_args=( "--server=${KUBE_SERVER:-https://${KUBE_MASTER_IP}}" ) if [[ -z "${CA_CERT:-}" ]]; then cluster_args+=("--insecure-skip-tls-verify=true") else cluster_args+=( "--certificate-authority=${CA_CERT}" "--embed-certs=true" ) fi local user_args=() if [[ ! -z "${KUBE_BEARER_TOKEN:-}" ]]; then user_args+=( "--token=${KUBE_BEARER_TOKEN}" ) elif [[ ! -z "${KUBE_USER:-}" && ! -z "${KUBE_PASSWORD:-}" ]]; then user_args+=( "--username=${KUBE_USER}" "--password=${KUBE_PASSWORD}" ) fi if [[ ! -z "${KUBE_CERT:-}" && ! -z "${KUBE_KEY:-}" ]]; then user_args+=( "--client-certificate=${KUBE_CERT}" "--client-key=${KUBE_KEY}" "--embed-certs=true" ) fi KUBECONFIG="${KUBECONFIG}" "${kubectl}" config set-cluster "${CONTEXT}" "${cluster_args[@]}" if [[ -n "${user_args[@]:-}" ]]; then KUBECONFIG="${KUBECONFIG}" "${kubectl}" config set-credentials "${CONTEXT}" "${user_args[@]}" fi KUBECONFIG="${KUBECONFIG}" "${kubectl}" config set-context "${CONTEXT}" --cluster="${CONTEXT}" --user="${CONTEXT}" if [[ "${SECONDARY_KUBECONFIG}" != "true" ]];then KUBECONFIG="${KUBECONFIG}" "${kubectl}" config use-context "${CONTEXT}" --cluster="${CONTEXT}" fi # If we have a bearer token, also create a credential entry with basic auth # so that it is easy to discover the basic auth password for your cluster # to use in a web browser. if [[ ! -z "${KUBE_BEARER_TOKEN:-}" && ! -z "${KUBE_USER:-}" && ! -z "${KUBE_PASSWORD:-}" ]]; then KUBECONFIG="${KUBECONFIG}" "${kubectl}" config set-credentials "${CONTEXT}-basic-auth" "--username=${KUBE_USER}" "--password=${KUBE_PASSWORD}" fi echo "Wrote config for ${CONTEXT} to ${KUBECONFIG}" } # Clear kubeconfig data for a context # Assumed vars: # KUBECONFIG # CONTEXT # # To explicitly name the context being removed, use OVERRIDE_CONTEXT function clear-kubeconfig() { export KUBECONFIG=${KUBECONFIG:-$DEFAULT_KUBECONFIG} OVERRIDE_CONTEXT=${OVERRIDE_CONTEXT:-} if [[ "$OVERRIDE_CONTEXT" != "" ]];then CONTEXT=$OVERRIDE_CONTEXT fi local kubectl="${KUBE_ROOT}/cluster/kubectl.sh" # Unset the current-context before we delete it, as otherwise kubectl errors. local cc=$("${kubectl}" config view -o jsonpath='{.current-context}') if [[ "${cc}" == "${CONTEXT}" ]]; then "${kubectl}" config unset current-context fi "${kubectl}" config unset "clusters.${CONTEXT}" "${kubectl}" config unset "users.${CONTEXT}" "${kubectl}" config unset "users.${CONTEXT}-basic-auth" "${kubectl}" config unset "contexts.${CONTEXT}" echo "Cleared config for ${CONTEXT} from ${KUBECONFIG}" } # Gets username, password for the current-context in kubeconfig, if they exist. # Assumed vars: # KUBECONFIG # if unset, defaults to global # KUBE_CONTEXT # if unset, defaults to current-context # # Vars set: # KUBE_USER # KUBE_PASSWORD # # KUBE_USER,KUBE_PASSWORD will be empty if no current-context is set, or # the current-context user does not exist or contain basicauth entries. function get-kubeconfig-basicauth() { export KUBECONFIG=${KUBECONFIG:-$DEFAULT_KUBECONFIG} local cc=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.current-context}") if [[ ! -z "${KUBE_CONTEXT:-}" ]]; then cc="${KUBE_CONTEXT}" fi local user=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.contexts[?(@.name == \"${cc}\")].context.user}") get-kubeconfig-user-basicauth "${user}" if [[ -z "${KUBE_USER:-}" || -z "${KUBE_PASSWORD:-}" ]]; then # kube-up stores username/password in a an additional kubeconfig section # suffixed with "-basic-auth". Cloudproviders like GKE store in directly # in the top level section along with the other credential information. # TODO: Handle this uniformly, either get rid of "basic-auth" or # consolidate its usage into a function across scripts in cluster/ get-kubeconfig-user-basicauth "${user}-basic-auth" fi } # Sets KUBE_USER and KUBE_PASSWORD to the username and password specified in # the kubeconfig section corresponding to $1. # # Args: # $1 kubeconfig section to look for basic auth (eg: user or user-basic-auth). # Assumed vars: # KUBE_ROOT # Vars set: # KUBE_USER # KUBE_PASSWORD function get-kubeconfig-user-basicauth() { KUBE_USER=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.users[?(@.name == \"$1\")].user.username}") KUBE_PASSWORD=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.users[?(@.name == \"$1\")].user.password}") } # Generate basic auth user and password. # Vars set: # KUBE_USER # KUBE_PASSWORD function gen-kube-basicauth() { KUBE_USER=admin KUBE_PASSWORD=$(python -c 'import string,random; print("".join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(16)))') } # Get the bearer token for the current-context in kubeconfig if one exists. # Assumed vars: # KUBECONFIG # if unset, defaults to global # KUBE_CONTEXT # if unset, defaults to current-context # # Vars set: # KUBE_BEARER_TOKEN # # KUBE_BEARER_TOKEN will be empty if no current-context is set, or the # current-context user does not exist or contain a bearer token entry. function get-kubeconfig-bearertoken() { export KUBECONFIG=${KUBECONFIG:-$DEFAULT_KUBECONFIG} local cc=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.current-context}") if [[ ! -z "${KUBE_CONTEXT:-}" ]]; then cc="${KUBE_CONTEXT}" fi local user=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.contexts[?(@.name == \"${cc}\")].context.user}") KUBE_BEARER_TOKEN=$("${KUBE_ROOT}/cluster/kubectl.sh" config view -o jsonpath="{.users[?(@.name == \"${user}\")].user.token}") } # Generate bearer token. # # Vars set: # KUBE_BEARER_TOKEN function gen-kube-bearertoken() { KUBE_BEARER_TOKEN=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) } function load-or-gen-kube-basicauth() { if [[ ! -z "${KUBE_CONTEXT:-}" ]]; then get-kubeconfig-basicauth fi if [[ -z "${KUBE_USER:-}" || -z "${KUBE_PASSWORD:-}" ]]; then gen-kube-basicauth fi # Make sure they don't contain any funny characters. if ! [[ "${KUBE_USER}" =~ ^[-._@a-zA-Z0-9]+$ ]]; then echo "Bad KUBE_USER string." exit 1 fi if ! [[ "${KUBE_PASSWORD}" =~ ^[-._@#%/a-zA-Z0-9]+$ ]]; then echo "Bad KUBE_PASSWORD string." exit 1 fi } # Sets KUBE_VERSION variable to the proper version number (e.g. "v1.0.6", # "v1.2.0-alpha.1.881+376438b69c7612") or a version' publication of the form # / (e.g. "release/stable",' "ci/latest-1"). # # See the docs on getting builds for more information about version # publication. # # Args: # $1 version string from command line # Vars set: # KUBE_VERSION function set_binary_version() { if [[ "${1}" =~ "/" ]]; then IFS='/' read -a path <<< "${1}" if [[ "${path[0]}" == "release" ]]; then KUBE_VERSION=$(gsutil cat "gs://kubernetes-release/${1}.txt") else KUBE_VERSION=$(gsutil cat "gs://kubernetes-release-dev/${1}.txt") fi else KUBE_VERSION=${1} fi } # Search for the specified tarball in the various known output locations, # echoing the location if found. # # Assumed vars: # KUBE_ROOT # # Args: # $1 name of tarball to search for function find-tar() { local -r tarball=$1 locations=( "${KUBE_ROOT}/server/${tarball}" "${KUBE_ROOT}/_output/release-tars/${tarball}" "${KUBE_ROOT}/bazel-bin/build/release-tars/${tarball}" ) location=$( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 ) if [[ ! -f "${location}" ]]; then echo "!!! Cannot find ${tarball}" >&2 exit 1 fi echo "${location}" } # Verify and find the various tar files that we are going to use on the server. # # Assumed vars: # KUBE_ROOT # Vars set: # SERVER_BINARY_TAR # KUBE_MANIFESTS_TAR function find-release-tars() { SERVER_BINARY_TAR=$(find-tar kubernetes-server-linux-amd64.tar.gz) # This tarball is used by GCI, Ubuntu Trusty, and Container Linux. KUBE_MANIFESTS_TAR= if [[ "${MASTER_OS_DISTRIBUTION:-}" == "trusty" || "${MASTER_OS_DISTRIBUTION:-}" == "gci" || "${MASTER_OS_DISTRIBUTION:-}" == "ubuntu" ]] || \ [[ "${NODE_OS_DISTRIBUTION:-}" == "trusty" || "${NODE_OS_DISTRIBUTION:-}" == "gci" || "${NODE_OS_DISTRIBUTION:-}" == "ubuntu" ]] ; then KUBE_MANIFESTS_TAR=$(find-tar kubernetes-manifests.tar.gz) fi } # Run the cfssl command to generates certificate files for etcd service, the # certificate files will save in $1 directory. # # Optional vars: # GEN_ETCD_CA_CERT (CA cert encode with base64 and ZIP compression) # GEN_ETCD_CA_KEY (CA key encode with base64) # # If GEN_ETCD_CA_CERT or GEN_ETCD_CA_KEY is not specified, it will generates certs for CA. # # Args: # $1 (the directory that certificate files to save) # $2 (the ip of etcd member) # $3 (the type of etcd certificates, must be one of client, server, peer) # $4 (the prefix of the certificate filename, default is $3) function generate-etcd-cert() { local cert_dir=${1} local member_ip=${2} local type_cert=${3} local prefix=${4:-"${type_cert}"} local GEN_ETCD_CA_CERT=${GEN_ETCD_CA_CERT:-} local GEN_ETCD_CA_KEY=${GEN_ETCD_CA_KEY:-} mkdir -p "${cert_dir}" pushd "${cert_dir}" kube::util::ensure-cfssl . if [ ! -r "ca-config.json" ]; then cat >ca-config.json <ca-csr.json < ca.pem echo "${ca_key}" | base64 --decode > ca-key.pem fi if [[ ! -r "ca.pem" || ! -r "ca-key.pem" ]]; then ${CFSSL_BIN} gencert -initca ca-csr.json | ${CFSSLJSON_BIN} -bare ca - fi case "${type_cert}" in client) echo "Generate client certificates..." echo '{"CN":"client","hosts":["*"],"key":{"algo":"ecdsa","size":256}}' \ | ${CFSSL_BIN} gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client - \ | ${CFSSLJSON_BIN} -bare "${prefix}" ;; server) echo "Generate server certificates..." echo '{"CN":"'${member_ip}'","hosts":[""],"key":{"algo":"ecdsa","size":256}}' \ | ${CFSSL_BIN} gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server -hostname="${member_ip},127.0.0.1" - \ | ${CFSSLJSON_BIN} -bare "${prefix}" ;; peer) echo "Generate peer certificates..." echo '{"CN":"'${member_ip}'","hosts":[""],"key":{"algo":"ecdsa","size":256}}' \ | ${CFSSL_BIN} gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer -hostname="${member_ip},127.0.0.1" - \ | ${CFSSLJSON_BIN} -bare "${prefix}" ;; *) echo "Unknow, unsupported etcd certs type: ${type_cert}" >&2 echo "Supported type: client, server, peer" >&2 exit 2 esac popd } # Check whether required client and server binaries exist, prompting to download # if missing. # If KUBERNETES_SKIP_CONFIRM is set to y, we'll automatically download binaries # without prompting. function verify-kube-binaries() { local missing_binaries=false if ! "${KUBE_ROOT}/cluster/kubectl.sh" version --client >&/dev/null; then echo "!!! kubectl appears to be broken or missing" missing_binaries=true fi if ! $(find-release-tars); then missing_binaries=true fi if ! "${missing_binaries}"; then return fi get_binaries_script="${KUBE_ROOT}/cluster/get-kube-binaries.sh" local resp="y" if [[ ! "${KUBERNETES_SKIP_CONFIRM:-n}" =~ ^[yY]$ ]]; then echo "Required binaries appear to be missing. Do you wish to download them? [Y/n]" read resp fi if [[ "${resp}" =~ ^[nN]$ ]]; then echo "You must download binaries to continue. You can use " echo " ${get_binaries_script}" echo "to do this for your automatically." exit 1 fi "${get_binaries_script}" } # Run pushd without stack output function pushd() { command pushd $@ > /dev/null } # Run popd without stack output function popd() { command popd $@ > /dev/null }