diff --git a/Makefile b/Makefile index ffec0a094..1a859a50e 100755 --- a/Makefile +++ b/Makefile @@ -79,13 +79,13 @@ allcover: logclean ground getdevdeps mod sdk generate lib cli minimock err vet s version: @printf "%b" "$(VERSION)-$$(git rev-parse --abbrev-ref HEAD | tr \"/\" \"_\")"; -release: logclean ground getdevdeps mod releasetags sdk generate lib cli test minimock err vet semgrep style metalint releasearchive +release: logclean ground getdevdeps mod releasetags tunnel sdk generate lib cli test minimock err vet semgrep style metalint releasearchive @printf "%b" "$(OK_COLOR)$(OK_STRING) Build for release, branch $$(git rev-parse --abbrev-ref HEAD) SUCCESSFUL $(NO_COLOR)\n"; @git ls-tree --full-tree --name-only -r HEAD | grep \.go | xargs $(MD5) 2>/dev/null > sums.log || true @git ls-tree --full-tree --name-only -r HEAD | grep \.sh | xargs $(MD5) 2>/dev/null >> sums.log || true @git ls-tree --full-tree --name-only -r HEAD | grep \.yml | xargs $(MD5) 2>/dev/null >> sums.log || true -releaserc: logclean ground getdevdeps mod releasetags sdk generate lib cli minimock err vet style metalint releasearchive +releaserc: logclean ground getdevdeps mod releasetags tunnel sdk generate lib cli minimock err vet style metalint releasearchive @printf "%b" "$(OK_COLOR)$(OK_STRING) Build for rc, branch $$(git rev-parse --abbrev-ref HEAD) SUCCESSFUL $(NO_COLOR)\n"; @git ls-tree --full-tree --name-only -r HEAD | grep \.go | xargs $(MD5) 2>/dev/null > sums.log || true @git ls-tree --full-tree --name-only -r HEAD | grep \.sh | xargs $(MD5) 2>/dev/null >> sums.log || true diff --git a/README.md b/README.md index 6a6f407ac..45e86cb5d 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,15 @@ SafeScale is an Infrastructure and Platform as Code tool. - [Description](#description) - [SafeScale Infra](#safescale-infra) - [SafeScale Platform](#safescale-platform) - - [SafeScale Security](#safescale-security) - [Available features](#available-features) - [Contributing](#contributing) - [License](#license) ## Description -SafeScale offers an APIs and a CLI tools to deploy versatile computing clusters that span multiple Clouds. These APIs and CLIs are divided in 3 service layers: +SafeScale offers an APIs and a CLI tools to deploy versatile computing clusters that span multiple Clouds. These APIs and CLIs are divided in 2 service layers: - SafeScale Infra to manage Cloud infrastructure (IaaS - Infrastructure as a Service) - SafeScale Platform to manage Cloud computing platforms (PaaS - Platform as a Service) -- SafeScale Security to secure user environments ![SafeScale](doc/img/SafeScale.png "SafeScale") @@ -70,7 +68,7 @@ For example the following command creates a Kubernetes cluster named `k8s-cluste $ safescale cluster create --flavor k8s --complexity Normal k8s-cluster ``` -Supplemental software and/or configurations can be installed in 2 ways on SafeScale Hosts or Clusters: +Supplemental software and/or configurations can be installed in 3 ways on SafeScale Hosts or Clusters: - using ssh command (the old and manual way): ``` $ safescale ssh run -c "apt install nginx" my-host @@ -78,7 +76,31 @@ Supplemental software and/or configurations can be installed in 2 ways on SafeSc - using "SafeScale `Feature`", that can be seen as the "ansible" for SafeScale: ``` - $ safescale cluster feature add mycluster keycloak + $ safescale cluster feature add mycluster ntpclient + ``` +- and using ansible, which is the PREFERRED method to install your software in a SafeScale cluster: + installing a simple script: + ``` + $ safescale cluster ansible playbook my-cluster my-ansible-script.yml + ``` + + where my-ansible-script.yml is something like: +```yml +--- +- hosts: nodes + tasks: + - name: Install golang + become: yes + apt: + pkg: + - golang + - bison + +``` + + or a more complex one (put all your files in a .zip) + ``` + $ safescale cluster ansible playbook my-cluster my-zipped-scripts.zip ``` A "SafeScale `Feature`" is a file in YAML format that describes the operations to check/add/remove software and/or configuration on a target (Host or Cluster). @@ -91,17 +113,6 @@ A `Feature` can describe operations using different methods: Additionally, a `Feature` is able to apply: - reverse proxy rules -- Security Group rules - -### SafeScale Security - -SafeScale Security is a Web API and a Web Portal to create on-demand security gateways to protect Web services along 5 axes: Encryption, Authentication, Authorization, Auditability and Intrusion detection. -SafeScale Security relies on Kong, an open source generic proxy to be put in between user and service. Kong intercepts user requests and service responses and executes plugins to empower any API. To build a SafeScale Security gateway 3 plugins are used: -- Dynamic SSL plugin to encrypt traffic between the user and the service protected -- Open ID plugin to connect the Identity and Access Management server, KeyCloak -- UDP Log plugin to connect the Log management system, Logstash -The design of a SafeScale Security gateway can be depicted as below: -![SafeScale Security](doc/img/SafeScale_Security.png "SafeScale Security") ## Available features SafeScale is currently under active development and does not yet offer all the abilities planned. However, we are already publishing it with the following ones: @@ -143,7 +154,7 @@ As much as possible, try following these guides: - [Go style guide](https://github.com/golang/go/wiki/CodeReviewComments) - [Effective Go](https://golang.org/doc/effective_go) -For bugs and feature requests, [please create an issue](../../issues/new). +For bugs and feature requests, [please create an issue](https://github.com/CS-SI/SafeScale/issues/new/choose). ## Build [See Build file](doc/build/BUILDING.md) diff --git a/build/Dockerfile2 b/build/Dockerfile2 new file mode 100644 index 000000000..ce192f6f9 --- /dev/null +++ b/build/Dockerfile2 @@ -0,0 +1,104 @@ +FROM ubuntu:focal as base +LABEL maintainer="CS SI" +ARG http_proxy="" +ARG https_proxy="" +ARG LC_ALL=C.UTF-8 +ARG LANG=C.UTF-8 +ENV DEBIAN_FRONTEND noninteractive +ENV BUILD_ENV docker +ENV BRANCH_NAME $BRANCH_NAME +ENV GOVERSION $GOVERSION +ENV PROTOVERSION $PROTOVERSION +ENV COMMITSHA $COMMITSHA +ENV GOOSX $GOOSX +ENV GOARCHX $GOARCHX + +RUN apt-get update -y \ +&& apt-get install -y --allow-unauthenticated --no-install-recommends \ +wget unzip apt-utils + +WORKDIR /tmp + +# ---------------------- +# Install GO $GOVERSION +# ---------------------- +RUN wget --no-check-certificate https://dl.google.com/go/go$GOVERSION.linux-amd64.tar.gz \ +&& tar -C /usr/local -xzf go$GOVERSION.linux-amd64.tar.gz \ +&& rm /tmp/go$GOVERSION.linux-amd64.tar.gz +ENV PATH $PATH:/usr/local/go/bin:/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +# ---------------------- +# Install Protoc $PROTOVERSION +# ---------------------- +RUN wget --no-check-certificate https://github.com/google/protobuf/releases/download/v$PROTOVERSION/protoc-$PROTOVERSION-linux-x86_64.zip \ +&& unzip -d /usr/local/protoc protoc-$PROTOVERSION-linux-x86_64.zip \ +&& ln -s /usr/local/protoc/bin/protoc /usr/local/bin \ +&& rm /tmp/protoc-$PROTOVERSION-linux-x86_64.zip + +FROM base AS builder + +WORKDIR /tmp + +# ----------------- +# Install Standard packages +# ----------------- +RUN apt-get install -y --allow-unauthenticated --no-install-recommends \ +locales \ +sudo \ +build-essential \ +make \ +curl \ +git \ +jq \ +python3 \ +python3-pip \ +&& apt-get autoclean -y \ +&& apt-get autoremove -y \ +&& rm -rf /var/lib/apt/lists/* + +# Set the locale +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +RUN python3 -c "print('testing UTF8: 👌')" + +ENV SHELL /bin/bash +ENV GOPATH /go +COPY build-safescale2.sh /opt/build-safescale.sh + +CMD sleep 5 + +COPY marker /dev/null + +RUN cd /opt && COMMITSHA=$COMMITSHA GOOSX=$GOOSX GOARCHX=$GOARCHX ./build-safescale.sh + +# -- + +FROM golang:$GOVERSION-alpine +LABEL maintainer="CS SI" +ARG http_proxy="" +ARG https_proxy="" +ARG LC_ALL=C.UTF-8 +ARG LANG=C.UTF-8 +ENV BUILD_ENV docker + +RUN apk update && \ + apk add --no-cache \ + nano \ + curl \ + wget \ + openssl \ + ca-certificates \ + iproute2 \ + iperf + +RUN ln -s /usr/lib/tc /lib/tc +RUN mkdir /exported-$GOOSX-$GOARCHX + +RUN apk add --no-cache bash + +COPY --from=builder /exported-$GOOSX-$GOARCHX/safescaled /exported-$GOOSX-$GOARCHX/safescaled +COPY --from=builder /exported-$GOOSX-$GOARCHX/safescale /exported-$GOOSX-$GOARCHX/safescale +COPY --from=builder /exported-$GOOSX-$GOARCHX/go.mod /exported-$GOOSX-$GOARCHX/go.mod +COPY --from=builder /exported-$GOOSX-$GOARCHX/go.sum /exported-$GOOSX-$GOARCHX/go.sum diff --git a/build/build-safescale.sh b/build/build-safescale.sh index f0bd20c46..edc202388 100755 --- a/build/build-safescale.sh +++ b/build/build-safescale.sh @@ -74,7 +74,7 @@ make generate sleep 4 echo "Make All" -make all +make release [ $? -ne 0 ] && echo "Build failure" && exit 1 echo "Install" diff --git a/build/build-safescale2.sh b/build/build-safescale2.sh new file mode 100755 index 000000000..ff17d8772 --- /dev/null +++ b/build/build-safescale2.sh @@ -0,0 +1,97 @@ +#! /bin/bash + +echo "Checks..." +if [[ ! -v BUILD_ENV ]]; then + echo "BUILD_ENV is not set, this script is intended to run inside a docker container" + [[ $SHLVL -gt 2 ]] && return 1 || exit 1 +fi + +# ---------------------- +# Create working directory +# ---------------------- +echo "Create working directory" +export WRKDIR=/opt +mkdir -p ${WRKDIR} +cd ${WRKDIR} +rm -rf SafeScale + +if [ -z "$COMMITSHA" ] +then + # ---------------------- + # Get source code + # ---------------------- + echo "Get source code" + BRANCH_NAME=${BRANCH_NAME:="develop"} + GIT_REPO_URL=${GIT_REPO_URL:="https://github.com/CS-SI/SafeScale.git"} + echo "Cloning branch '${BRANCH_NAME}' from repo '${GIT_REPO_URL}'" + + git clone ${GIT_REPO_URL} -b ${BRANCH_NAME} --depth=1 + + cd SafeScale + sed -i "s#\(.*\)develop#\1${BRANCH_NAME}#" common.mk +else + # ---------------------- + # Get source code + # ---------------------- + echo "Get source code, commit $COMMITSHA" + GIT_REPO_URL=${GIT_REPO_URL:="https://github.com/CS-SI/SafeScale.git"} + + git clone ${GIT_REPO_URL} + cd SafeScale + + git reset --hard $COMMITSHA + sed -i "s#\(.*\)develop#\1${BRANCH_NAME}#" common.mk +fi + +# ---------------------- +# Compile +# ---------------------- + +echo "deps" +make getdevdeps + +sleep 4 + +echo "mod" +make mod + +sleep 4 + +make sdk + +sleep 4 + +make force_sdk_python + +sleep 4 + +make force_sdk_js + +sleep 4 + +make generate + +sleep 4 + +export GOOS=$GOOSX +export GOARCH=$GOARCHX + +echo "Make All" +make release +[ $? -ne 0 ] && echo "Build failure" && exit 1 + +echo "Install" +make install +[ $? -ne 0 ] && echo "Install failure" && exit 1 + +echo "Export" +export CIBIN=/exported-$GOOS-$GOARCH +mkdir -p /exported-$GOOS-$GOARCH + +CIBIN=/exported-$GOOS-$GOARCH make installci +[ $? -ne 0 ] && echo "Export failure" && exit 1 + +cp ${WRKDIR}/SafeScale/go.mod /exported-$GOOS-$GOARCH +cp ${WRKDIR}/SafeScale/go.sum /exported-$GOOS-$GOARCH + +exit 0 diff --git a/build/create-docker2.sh b/build/create-docker2.sh new file mode 100755 index 000000000..c8529e399 --- /dev/null +++ b/build/create-docker2.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +if [ "$(uname -s)" = "Darwin" ]; then + WRKDIR=$(readlink -n $(dirname "$0")) + [ -z "$WRKDIR" ] && WRKDIR=$(dirname "$0") +else + WRKDIR=$(readlink -f $(dirname "$0")) +fi + +if [ ! -z "$1" ]; then + if [[ $1 == "-f" ]]; then + date >marker + fi +fi + +if [ ! -f ./marker ]; then + curl https://api.github.com/repos/CS-SI/SafeScale/commits/$(git rev-parse --abbrev-ref HEAD) 2>&1 | grep '"date"' | tail -n 1 >./marker +else + curl https://api.github.com/repos/CS-SI/SafeScale/commits/$(git rev-parse --abbrev-ref HEAD) 2>&1 | grep '"date"' | tail -n 1 >./newMarker + diff ./marker ./newMarker 1>/dev/null && rm ./newMarker && echo "Nothing to do !, if you want to force a docker build launch with the -f flag" && exit 0 +fi + +stamp=$(date +"%s") + +[ -z "$BRANCH_NAME" ] && BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) +[ -z "$GOVERSION" ] && GOVERSION=1.18.4 +[ -z "$PROTOVERSION" ] && PROTOVERSION=3.17.3 + +BRANCH_NAME=$BRANCH_NAME PROTOVERSION=$PROTOVERSION GOVERSION=$GOVERSION COMMITSHA=$COMMITSHA envsubst Dockerfile.$stamp +if [ -z "$COMMITSHA" ] +then + sed -i '/ENV COMMITSHA/d' Dockerfile.$stamp +fi + +echo docker build --rm --network host --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy --build-arg BRANCH_NAME=$BRANCH_NAME --build-arg GOVERSION=$GOVERSION -f ${WRKDIR}/Dockerfile.$stamp -t "safescale:${BRANCH_NAME/\//_}" $WRKDIR +docker build --rm --network host --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy --build-arg BRANCH_NAME=$BRANCH_NAME --build-arg GOVERSION=$GOVERSION -f ${WRKDIR}/Dockerfile.$stamp -t "safescale:${BRANCH_NAME/\//_}" $WRKDIR +[ $? -ne 0 ] && echo "Docker build failed !!" && { + rm -f ./marker + rm -f ./Dockerfile.$stamp + exit 1 +} + +echo "Docker build OK" + +docker create -ti --name dummy "safescale:${BRANCH_NAME/\//_}" bash +[ $? -ne 0 ] && echo "Failure extracting binaries 1/3" && exit 1 +docker cp dummy:/exported-$GOOSX-$GOARCHX . +[ $? -ne 0 ] && echo "Failure extracting binaries 2/3" && exit 1 +docker rm -f dummy +[ $? -ne 0 ] && echo "Failure extracting binaries 3/3" && exit 1 + +echo "Binaries extracted successfully" +if [ -f ./newMarker ]; then + mv ./newMarker ./marker +fi + +rm -f ./Dockerfile.$stamp + +exit 0 diff --git a/cli/safescaled/main.go b/cli/safescaled/main.go index 0ac27d450..0846c9a9c 100644 --- a/cli/safescaled/main.go +++ b/cli/safescaled/main.go @@ -36,7 +36,6 @@ import ( _ "github.com/CS-SI/SafeScale/v22/lib/backend" "github.com/CS-SI/SafeScale/v22/lib/backend/iaas" "github.com/CS-SI/SafeScale/v22/lib/backend/listeners" - "github.com/CS-SI/SafeScale/v22/lib/backend/resources/operations" "github.com/CS-SI/SafeScale/v22/lib/protocol" appwide "github.com/CS-SI/SafeScale/v22/lib/utils/app" "github.com/CS-SI/SafeScale/v22/lib/utils/debug" @@ -141,8 +140,6 @@ func work(c *cli.Context) { // - /debug/fgprof expose() - operations.StartFeatureFileWatcher() - version := Version + ", build " + Revision + " (" + BuildDate + ")" if //goland:noinspection GoBoolExpressions len(Tags) > 1 { // nolint diff --git a/common.mk b/common.mk index a2f3b3541..f464d8fd0 100755 --- a/common.mk +++ b/common.mk @@ -1,4 +1,4 @@ -VERSION=22.11.2 +VERSION=22.11.5 export VERSION ifeq ($(MAKE_LEVEL),) diff --git a/doc/USAGE.md b/doc/USAGE.md index 12676cfc7..e5186ca75 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -1196,11 +1196,6 @@ The following actions are proposed: "status": "success" } - response on failure: -
-{
-  "error":
-      
@@ -1230,11 +1225,6 @@ The following actions are proposed: "status":"success" } - response on failure: -
-{
-  "error": {
-      
diff --git a/go.mod b/go.mod index 173718e90..e66abfa54 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/dgraph-io/ristretto v0.1.1 github.com/eko/gocache/v2 v2.3.1 - github.com/farmergreg/rfsnotify v0.0.0-20200716145600-b37be6e4177f github.com/felixge/fgprof v0.9.3 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/gofrs/uuid v4.3.1+incompatible @@ -53,7 +52,6 @@ require ( google.golang.org/api v0.107.0 google.golang.org/grpc v1.52.3 google.golang.org/protobuf v1.28.1 - gopkg.in/fsnotify.v1 v1.4.7 ) require ( diff --git a/go.sum b/go.sum index 52016e0e2..c6aa480d9 100644 --- a/go.sum +++ b/go.sum @@ -146,8 +146,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/farmergreg/rfsnotify v0.0.0-20200716145600-b37be6e4177f h1:sRiluoTSaqFH+5cK4AsNX5ttFij+chr0Gj/HG/FkNGs= -github.com/farmergreg/rfsnotify v0.0.0-20200716145600-b37be6e4177f/go.mod h1:aRnKN8Geq81irQ1fxWr8sMkv9HY8GXn91Y82yKAfdD4= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -929,7 +927,6 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= diff --git a/lib/backend/iaas/userdata/newscripts/Makefile b/lib/backend/iaas/userdata/newscripts/Makefile new file mode 100644 index 000000000..4b0fa364e --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/Makefile @@ -0,0 +1,9 @@ +GO?=go + +all: test + +test: + @../../../../../helpers/bin/bats . + +bashtest: + @../../../../../helpers/bin/bats . diff --git a/lib/backend/iaas/userdata/newscripts/helpers.bash b/lib/backend/iaas/userdata/newscripts/helpers.bash new file mode 100644 index 000000000..0783194a0 --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/helpers.bash @@ -0,0 +1,46 @@ +#!/bin/bash -x + +function versionchk() { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } +export -f versionchk + +function checkVersion() { + case $3 in + ">") + if [ $(versionchk "$1") -gt $(versionchk "$2") ]; then + return 0 + fi + return 1 + ;; + ">=") + if [ $(versionchk "$1") -ge $(versionchk "$2") ]; then + return 0 + fi + return 1 + ;; + "=") + if [ $(versionchk "$1") -eq $(versionchk "$2") ]; then + return 0 + fi + return 1 + ;; + "<") + if [ $(versionchk "$1") -lt $(versionchk "$2") ]; then + return 0 + fi + return 1 + ;; + "<=") + if [ $(versionchk "$1") -le $(versionchk "$2") ]; then + return 0 + fi + return 1 + ;; + esac +} +export -f checkVersion + +function MyIP() { + MYIP="$(ip -br a | grep UP | awk '{print $3}') | head -n 1" + echo -n "$MYIP" +} +export -f MyIP \ No newline at end of file diff --git a/lib/backend/iaas/userdata/newscripts/helpers.bats b/lib/backend/iaas/userdata/newscripts/helpers.bats new file mode 100644 index 000000000..3d91f2c23 --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/helpers.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats + +load ./helpers + +@test "Check my IP" { + run MyIP + [ "$status" -eq 0 ] + [ "${lines[0]}" = "192.168.0.26/24" ] +} + +@test "Compare" { + run checkVersion "8.0" "7" ">" + [ "$status" -eq 0 ] + run checkVersion "8.0" "7.112" ">" + [ "$status" -eq 0 ] + run checkVersion "8.0" "8.112" "<" + [ "$status" -eq 0 ] + run checkVersion "8.0" "8" "=" + [ "$status" -eq 0 ] +} \ No newline at end of file diff --git a/lib/backend/iaas/userdata/newscripts/userdata.final.sh b/lib/backend/iaas/userdata/newscripts/userdata.final.sh new file mode 100644 index 000000000..e94ccaaaa --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/userdata.final.sh @@ -0,0 +1,152 @@ +#!/bin/bash -x +# +# Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu +# +# 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. + +#{{.Revision}} +# Script customized for {{.ProviderName}} driver + +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{.Header}} + +last_error= + +function print_error() { + read -r line file <<< "$(caller)" + echo "An error occurred in line $line of file $file:" "{$(sed "${line}q;d" "$file")}" >&2 + {{.ExitOnError}} +} +trap print_error ERR + +function fail() { + MYIP="$(ip -br a | grep UP | awk '{print $3}') | head -n 1" + if [ $# -eq 1 ]; then + echo "PROVISIONING_ERROR: $1" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$1" > /opt/safescale/var/state/user_data.final.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + # For compatibility with previous user_data implementation (until v19.03.x)... + ln -s ${SF_VARDIR}/state/user_data.final.done /var/tmp/user_data.done || true + exit $1 + elif [ $# -eq 2 -a $1 -ne 0 ]; then + echo "PROVISIONING_ERROR: $1, $2" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$2" > /opt/safescale/var/state/user_data.final.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + # For compatibility with previous user_data implementation (until v19.03.x)... + ln -s ${SF_VARDIR}/state/user_data.final.done /var/tmp/user_data.done || true + exit $1 + fi +} +export -f fail + +# Redirects outputs to /opt/safescale/var/log/user_data.final.log +LOGFILE=/opt/safescale/var/log/user_data.final.log + +### All output to one file and all output to the screen +{{- if .Debug }} +if [[ -e /home/{{.Username}}/tss ]]; then + exec > >(/home/{{.Username}}/tss | tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +else + exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +fi +{{- else }} +exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +{{- end }} + +set -x + +date + +# Tricks BashLibrary's waitUserData to believe the current phase 'user_data.final' is already done (otherwise will deadlock) +uptime > /opt/safescale/var/state/user_data.final.done + +# Includes the BashLibrary +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{ .reserved_BashLibrary }} +rm -f /opt/safescale/var/state/user_data.final.done + +function install_drivers_nvidia() { + lspci | grep -i nvidia &> /dev/null || return 0 + + case $LINUX_KIND in + ubuntu) + sfFinishPreviousInstall + add-apt-repository -y ppa:graphics-drivers &> /dev/null + sfApt update || fail 201 "failure running apt update" + sfApt -y install nvidia-410 &> /dev/null || { + sfApt -y install nvidia-driver-410 &> /dev/null || fail 201 "failure installing nvidia" + } + ;; + + debian) + if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then + echo -e "blacklist nouveau\nblacklist lbm-nouveau\noptions nouveau modeset=0\nalias nouveau off\nalias lbm-nouveau off" >> /etc/modprobe.d/blacklist-nouveau.conf + rmmod nouveau + fi + sfWaitForApt && apt update &> /dev/null + sfWaitForApt && apt install -y dkms build-essential linux-headers-$(uname -r) gcc make &> /dev/null || fail 202 "failure installing kernel" + dpkg --add-architecture i386 &> /dev/null + sfWaitForApt && apt update &> /dev/null + sfWaitForApt && apt install -y lib32z1 lib32ncurses5 &> /dev/null || fail 203 "failure installing ncurses" + wget http://us.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run &> /dev/null || fail 204 "failure downloading nvidia" + bash NVIDIA-Linux-x86_64-410.78.run -s || fail 205 "failure running nvidia installer" + ;; + + redhat | centos) + if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then + echo -e "blacklist nouveau\noptions nouveau modeset=0" >> /etc/modprobe.d/blacklist-nouveau.conf + dracut --force + rmmod nouveau + fi + sfYum -y -q install kernel-devel.$(uname -i) kernel-headers.$(uname -i) gcc make &> /dev/null || fail 206 "failure updating kernel" + wget http://us.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run || fail 207 "failure downloading nvidia drivers" + # if there is a version mismatch between kernel sources and running kernel, building the driver would require 2 reboots to get it done, right now this is unsupported + if [ $(uname -r) == $(sfYum list installed | grep kernel-headers | awk {'print $2'}).$(uname -i) ]; then + bash NVIDIA-Linux-x86_64-410.78.run -s || fail 208 "failure installing nvidia" + fi + rm -f NVIDIA-Linux-x86_64-410.78.run + ;; + *) + fail 209 "Unsupported Linux distribution '$LINUX_KIND'!" + ;; + esac +} + +# ---- Main +install_drivers_nvidia +# ---- EndMain + +echo -n "0,linux,${LINUX_KIND},${VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.final.done +# For compatibility with previous user_data implementation (until v19.03.x)... +ln -s ${SF_VARDIR}/state/user_data.final.done /var/tmp/user_data.done + +( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 +) || true + +set +x +exit 0 diff --git a/lib/backend/iaas/userdata/newscripts/userdata.gwha.sh b/lib/backend/iaas/userdata/newscripts/userdata.gwha.sh new file mode 100644 index 000000000..dd9a45a82 --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/userdata.gwha.sh @@ -0,0 +1,195 @@ +#!/bin/bash -x +# +# Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu +# +# 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. + +#{{.Revision}} +# Script customized for {{.ProviderName}} driver + +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{.Header}} + +last_error= + +function print_error() { + read -r line file <<< "$(caller)" + echo "An error occurred in line $line of file $file:" "{$(sed "${line}q;d" "$file")}" >&2 + {{.ExitOnError}} +} +trap print_error ERR + +function fail() { + MYIP="$(ip -br a | grep UP | awk '{print $3}') | head -n 1" + if [ $# -eq 1 ]; then + echo "PROVISIONING_ERROR: $1" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$1" > /opt/safescale/var/state/user_data.gwha.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + elif [ $# -eq 2 -a $1 -ne 0 ]; then + echo "PROVISIONING_ERROR: $1, $2" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$2" > /opt/safescale/var/state/user_data.gwha.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + fi +} +export -f fail + +# Redirects outputs to /opt/safescale/log/user_data.phase2.log +LOGFILE=/opt/safescale/var/log/user_data.phase2.log + +### All output to one file and all output to the screen +{{- if .Debug }} +if [[ -e /home/{{.Username}}/tss ]]; then + exec > >(/home/{{.Username}}/tss | tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +else + exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +fi +{{- else }} +exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +{{- end }} + +set -x + +date + +# Tricks BashLibrary's waitUserData to believe the current phase 'gwha' is already done (otherwise will deadlock) +uptime > /opt/safescale/var/state/user_data.gwha.done + +# Includes the BashLibrary +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{ .reserved_BashLibrary }} +rm -f /opt/safescale/var/state/user_data.gwha.done + +function install_keepalived() { + case $LINUX_KIND in + ubuntu | debian) + sfApt install -y keepalived || return 1 + ;; + + redhat | centos) + sfYum install -q -y keepalived || return 1 + ;; + *) + echo "Unsupported Linux distribution '$LINUX_KIND'!" + return 1 + ;; + esac + + NETMASK=$(echo {{ .CIDR }} | cut -d/ -f2) + read IF_PR ignore <<< $(cat ${SF_VARDIR}/state/private_nics) + read IF_PU ignore <<< $(cat ${SF_VARDIR}/state/public_nics) + + cat > /etc/keepalived/keepalived.conf <<- EOF + vrrp_instance vrrp_group_gws_internal { + state BACKUP + interface ${IF_PR} + virtual_router_id 1 + priority {{ if eq .IsPrimaryGateway true }}151{{ else }}100{{ end }} + nopreempt + advert_int 2 + authentication { + auth_type PASS + auth_pass "{{ .GatewayHAKeepalivedPassword }}" + } + {{ if eq .IsPrimaryGateway true }} + # Unicast specific option, this is the IP of the interface keepalived listens on + unicast_src_ip {{ .PrimaryGatewayPrivateIP }} + # Unicast specific option, this is the IP of the peer instance + unicast_peer { + {{ .SecondaryGatewayPrivateIP }} + } + {{ else }} + unicast_src_ip {{ .SecondaryGatewayPrivateIP }} + unicast_peer { + {{ .PrimaryGatewayPrivateIP }} + } + {{ end }} + virtual_ipaddress { + {{ .DefaultRouteIP }}/${NETMASK} + } + } + + # vrrp_instance vrrp_group_gws_external { + # state BACKUP + # interface ${IF_PU} + # virtual_router_id 2 + # priority {{ if eq .IsPrimaryGateway true }}151{{ else }}100{{ end }} + # nopreempt + # advert_int 2 + # authentication { + # auth_type PASS + # auth_pass password + # } + # virtual_ipaddress { + # {{ .EndpointIP }}/${NETMASK} + # } + # } + EOF + + if [ "$(sfGetFact "use_systemd")" = "1" ]; then + # Use systemd to ensure keepalived is restarted if network is restarted + # (otherwise, keepalived is in undetermined state) + mkdir -p /etc/systemd/system/keepalived.service.d + if [ "$(sfGetFact "redhat_like")" = "1" ]; then + cat > /etc/systemd/system/keepalived.service.d/override.conf << EOF +[Unit] +Requires=network.service +PartOf=network.service +EOF + else + cat > /etc/systemd/system/keepalived.service.d/override.conf << EOF +[Unit] +Requires=systemd-networkd.service +PartOf=systemd-networkd.service +EOF + fi + systemctl daemon-reload + fi + + sfService enable keepalived && sfService restart keepalived || return 1 + + return 0 +} + +# ---- Main +{{- if .IsGateway }} +{{- if .SecondaryGatewayPrivateIP }} +install_keepalived +[ $? -ne 0 ] && fail $? +{{ end }} +{{ end }} +# ---- EndMain + +echo -n "0,linux,${LINUX_KIND},${VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.gwha.done + +( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 +) || true + +set +x +exit 0 diff --git a/lib/backend/iaas/userdata/newscripts/userdata.init.sh b/lib/backend/iaas/userdata/newscripts/userdata.init.sh new file mode 100644 index 000000000..aa9ca25a3 --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/userdata.init.sh @@ -0,0 +1,327 @@ +#!/bin/bash -x +# +# Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu +# +# 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. + +#{{.Revision}} +# Script customized for {{.ProviderName}} driver + +{{.Header}} + +last_error= + +function print_error() { + read -r line file <<< "$(caller)" + echo "An error occurred in line $line of file $file:" "{$(sed "${line}q;d" "$file")}" >&2 + {{.ExitOnError}} +} +trap print_error ERR + +function fail() { + MYIP="$(ip -br a | grep UP | awk '{print $3}') | head -n 1" + if [ $# -eq 1 ]; then + echo "PROVISIONING_ERROR: $1" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$1" > /opt/safescale/var/state/user_data.init.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + elif [ $# -eq 2 -a $1 -ne 0 ]; then + echo "PROVISIONING_ERROR: $1, $2" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$2" > /opt/safescale/var/state/user_data.init.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + fi +} +export -f fail + +mkdir -p /opt/safescale/etc/rclone /opt/safescale/bin &> /dev/null +mkdir -p /opt/safescale/var/log &> /dev/null +mkdir -p /opt/safescale/var/run /opt/safescale/var/state /opt/safescale/var/tmp &> /dev/null + +LOGFILE=/opt/safescale/var/log/user_data.init.log + +### All output to one file and all output to the screen +exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +set -x + +date + +LINUX_KIND= +VERSION_ID= +FULL_HOSTNAME= +FULL_VERSION_ID= + +function sfDetectFacts() { + [ -f /etc/os-release ] && { + . /etc/os-release + LINUX_KIND=$ID + FULL_HOSTNAME=$VERSION_ID + FULL_VERSION_ID=$VERSION_ID + } || { + command -v lsb_release &> /dev/null && { + LINUX_KIND=$(lsb_release -is) + LINUX_KIND=${LINUX_KIND,,} + VERSION_ID=$(lsb_release -rs | cut -d. -f1) + FULL_VERSION_ID=$(lsb_release -rs) + } || { + [[ -f /etc/redhat-release ]] && { + LINUX_KIND=$(cat /etc/redhat-release | cut -d' ' -f1) + LINUX_KIND=${LINUX_KIND,,} + VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f3 | cut -d. -f1) + FULL_VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f3) + case $VERSION_ID in + '' | *[!0-9]*) + VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f4 | cut -d. -f1) + FULL_VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f4) + ;; + *) ;; + + esac + } + } + } +} +export -f sfDetectFacts + +# Detect facts +sfDetectFacts + +function drop_user() { + userdel -r {{.Username}} || echo "User {{.Username}} not exists" +} + +function create_user() { + echo "Creating user {{.Username}}..." + if getent passwd {{.Username}}; then + echo "User {{.Username}} already exists !" + useradd {{.Username}} --home-dir /home/{{.Username}} --shell /bin/bash --comment "" --create-home || true + else + useradd {{.Username}} --home-dir /home/{{.Username}} --shell /bin/bash --comment "" --create-home + fi + # This password will be changed at phase 2 and can be used only from console (not remotely) + echo "{{.Username}}:safescale" | chpasswd + + if getent group docker; then + echo "Group docker already exists !" + else + groupadd -r docker + fi + usermod -aG docker {{.Username}} + SUDOERS_FILE=/etc/sudoers.d/{{.Username}} + [ ! -d "$(dirname $SUDOERS_FILE)" ] && SUDOERS_FILE=/etc/sudoers + cat >> $SUDOERS_FILE <<- EOF + Defaults:{{.Username}} !requiretty + {{.Username}} ALL=(ALL) NOPASSWD:ALL +EOF + + mkdir /home/{{.Username}}/.ssh + echo "{{.FirstPublicKey}}" > /home/{{.Username}}/.ssh/authorized_keys + echo "{{.FirstPrivateKey}}" > /home/{{.Username}}/.ssh/id_rsa + chmod 0700 /home/{{.Username}}/.ssh + chmod -R 0600 /home/{{.Username}}/.ssh/* + cat /home/{{.Username}}/.ssh/id_rsa + + chown -R {{.Username}}:{{.Username}} /opt/safescale + chmod -R 0640 /opt/safescale + find /opt/safescale -type d -exec chmod a+rx {} \; + chmod 1777 /opt/safescale/var/tmp + + chown -R {{.Username}}:{{.Username}} /home/{{.Username}} + + for i in /home/{{.Username}}/.hushlogin /home/{{.Username}}/.cloud-warnings.skip; do + touch $i + chown root:{{.Username}} $i + chmod ug+r-wx,o-rwx $i + done + + echo "done" +} + +# Follows the CentOS rules: +# - /etc/hostname contains short hostname +function put_hostname_in_hosts() { + echo "{{ .HostName }}" > /etc/hostname + echo "127.0.0.1 {{ .HostName }}" >> /etc/hosts + hostname {{ .HostName }} + SHORT_HOSTNAME=$(hostname -s) + [[ "$SHORT_HOSTNAME" == "{{ .HostName }}" ]] && return + ping -n -c1 -w5 $SHORT_HOSTNAME 2> /dev/null || sed -i -nr '/^127.0.1.1/!p;$a127.0.1.1\t'"${SHORT_HOSTNAME}" /etc/hosts +} + +# Disable cloud-init automatic network configuration to be sure our configuration won't be replaced +function disable_cloudinit_network_autoconf() { + fname=/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg + mkdir -p $(dirname $fname) + echo "network: {config: disabled}" > $fname +} + +# For testing purposes +function failover_sshd() { + cp /etc/ssh/sshd_config /etc/ssh/sshd_config.old + cat > /etc/ssh/sshd_config <<- EOF + Port 22 + PubkeyAuthentication yes + PasswordAuthentication yes + ChallengeResponseAuthentication no + UsePAM yes + AllowTcpForwarding yes + X11Forwarding yes + PrintMotd no + AcceptEnv LANG LC_* + Subsystem sftp /usr/lib/openssh/sftp-server +EOF +} + +function unsafe_sshd() { + {{- if .IsGateway }} + sed -i -E 's/(#|)Port\ ([0-9]+)/Port\ {{ .SSHPort }}/g' /etc/ssh/sshd_config || fail 208 "failure changing ssh service port" + {{- end }} + sed -i '/^.*PasswordAuthentication / s/^.*$/PasswordAuthentication yes/' /etc/ssh/sshd_config && + sed -i '/^.*ChallengeResponseAuthentication / s/^.*$/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config && + sed -i '/^.*PubkeyAuthentication / s/^.*$/PubkeyAuthentication yes/' /etc/ssh/sshd_config && + systemctl restart sshd +} + +function secure_sshd() { + {{- if .IsGateway }} + sed -i -E 's/(#|)Port\ ([0-9]+)/Port\ {{ .SSHPort }}/g' /etc/ssh/sshd_config || fail 208 "failure changing ssh service port" + {{- end }} + sed -i '/^.*PasswordAuthentication / s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config && + sed -i '/^.*ChallengeResponseAuthentication / s/^.*$/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config && + sed -i '/^.*PubkeyAuthentication / s/^.*$/PubkeyAuthentication yes/' /etc/ssh/sshd_config && + systemctl restart sshd +} + +function disable_services() { + case $LINUX_KIND in + debian | ubuntu) + if [[ -n $(command -v systemctl) ]]; then + systemctl stop apt-daily.service &> /dev/null + systemctl kill --kill-who=all apt-daily.service &> /dev/null + fi + if [[ -n $(command -v system) ]]; then + which system && service stop apt-daily.service &> /dev/null + fi + ;; + esac +} + +function sfFinishPreviousInstall() { + local unfinished + unfinished=$(dpkg -l | grep -v ii | grep -v rc | tail -n +4 | wc -l) + if [[ "$unfinished" == 0 ]]; then + echo "good" + return 0 + else + echo "there are unconfigured packages !" + sudo dpkg --configure -a --force-all && { + return $? + } + return 0 + fi +} +export -f sfFinishPreviousInstall + +function disable_upgrades() { + case $LINUX_KIND in + ubuntu) + sfFinishPreviousInstall + dpkg --remove --force-remove-reinstreq unattended-upgrades || true + ;; + *) ;; + + esac +} + +function no_daily_update() { + case $LINUX_KIND in + debian | ubuntu) + # If it's not there, nothing to do + systemctl list-units --all apt-daily.service | egrep -q 'apt-daily' || return 0 + + # first kill apt-daily + systemctl stop apt-daily.service + systemctl kill --kill-who=all apt-daily.service + + # wait until apt-daily dies + while ! (systemctl list-units --all apt-daily.service | egrep -q '(dead|failed)'); do + systemctl stop apt-daily.service + systemctl kill --kill-who=all apt-daily.service + sleep 1 + done + ;; + redhat | fedora | centos) + # If it's not there, nothing to do + systemctl list-units --all yum-cron.service | egrep -q 'yum-cron' || return 0 + + systemctl stop yum-cron.service + systemctl kill --kill-who=all yum-cron.service + + # wait until yum-cron dies + while ! (systemctl list-units --all yum-cron.service | egrep -q '(dead|failed)'); do + systemctl stop yum-crom.service + systemctl kill --kill-who=all yum-cron.service + sleep 1 + done + ;; + esac +} + +# ---- Main +export DEBIAN_FRONTEND=noninteractive +export UCF_FORCE_CONFFNEW=1 + +put_hostname_in_hosts +disable_cloudinit_network_autoconf +disable_services +disable_upgrades + +{{- if .Debug}} +failover_sshd +{{- end}} + +{{- if .Debug }} +unsafe_sshd +{{- else }} +secure_sshd +{{- end }} + +{{- if .Debug }} +drop_user +{{- end }} + +create_user + +no_daily_update + +touch /etc/cloud/cloud-init.disabled +# ---- EndMain + +echo -n "0,linux,${LINUX_KIND},${VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.init.done + +( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 +) || true + +set +x +exit 0 diff --git a/lib/backend/iaas/userdata/newscripts/userdata.netsec.sh b/lib/backend/iaas/userdata/newscripts/userdata.netsec.sh new file mode 100644 index 000000000..dcef6ace1 --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/userdata.netsec.sh @@ -0,0 +1,1816 @@ +#!/bin/bash -x +# +# Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu +# +# 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. + +#{{.Revision}} +# Script customized for {{.ProviderName}} driver + +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{.Header}} + +last_error= + +function print_error() { + read -r line file <<< "$(caller)" + echo "An error occurred in line $line of file $file:" "{$(sed "${line}q;d" "$file")}" >&2 + {{.ExitOnError}} +} +trap print_error ERR + +function failure() { + MYIP="$(ip -br a | grep UP | awk '{print $3}') | head -n 1" + if [ $# -eq 1 ]; then + echo "PROVISIONING_ERROR: $1" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$1" > /opt/safescale/var/state/user_data.netsec.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + elif [ $# -eq 2 -a $1 -ne 0 ]; then + echo "PROVISIONING_ERROR: $1, $2" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$2" > /opt/safescale/var/state/user_data.netsec.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + fi +} +export -f failure + +function rv() { + declare -n ret=$1 + local message=$3 + ret=$message + return $2 +} + +function return_failure() { + rv last_error $1 "$2" +} +export -f return_failure + +# Redirects outputs to /opt/safescale/var/log/user_data.netsec.log +LOGFILE=/opt/safescale/var/log/user_data.netsec.log + +### All output to one file and all output to the screen +{{- if .Debug }} +if [[ -e /home/{{.Username}}/tss ]]; then + exec > >(/home/{{.Username}}/tss | tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +else + exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +fi +{{- else }} +exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +{{- end }} + +set -x + +date + +# Tricks BashLibrary's waitUserData to believe the current phase 'netsec' is already done (otherwise will deadlock) +uptime > /opt/safescale/var/state/user_data.netsec.done + +# Includes the BashLibrary +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{ .reserved_BashLibrary }} +rm -f /opt/safescale/var/state/user_data.netsec.done + +function reset_fw() { + {{- if .WithoutFirewall }} + return 0 + {{- end }} + + is_network_reachable || failure 206 "reset_fw(): failure resetting firewall because network is not reachable" + + case $LINUX_KIND in + debian) + echo "Reset firewall" + sfRetry4 "sfApt update" || failure 207 "reset_fw(): failure running apt update" + sfRetry4 "sfApt -y autoclean autoremove" || failure 210 "reset_fw(): failure running cleanup" + sfRetry4 "sfApt install -q -y --no-install-recommends iptables" || failure 210 "reset_fw(): failure installing iptables" + sfRetry4 "sfApt install -q -y --no-install-recommends firewalld python3 python3-pip" || failure 211 "reset_fw(): failure installing firewalld" + + systemctl is-active ufw &> /dev/null && { + echo "Stopping ufw" + systemctl stop ufw || true # set to true to fix issues + } + systemctl is-enabled ufw &> /dev/null && { + systemctl disable ufw || true # set to true to fix issues + } + dpkg --purge --force-remove-reinstreq ufw &>/dev/null || failure 212 "reset_fw(): failure purging ufw" + ;; + + ubuntu) + echo "Reset firewall" + sfRetry4 "sfApt update" || failure 213 "reset_fw(): failure running apt update" + sfRetry4 "sfApt -y autoclean autoremove" || failure 213 "reset_fw(): failure running cleanup" + sfRetry4 "sfApt install -q -y --no-install-recommends iptables" || failure 214 "reset_fw(): failure installing iptables" + sfRetry4 "sfApt install -q -y --no-install-recommends firewalld python3 python3-pip" || failure 215 "reset_fw(): failure installing firewalld" + + systemctl is-active ufw &> /dev/null && { + echo "Stopping ufw" + systemctl stop ufw || true # set to true to fix issues + } + systemctl is-enabled ufw &> /dev/null && { + systemctl disable ufw || true # set to true to fix issues + } + dpkg --purge --force-remove-reinstreq ufw &>/dev/null || failure 216 "reset_fw(): failure purging ufw" + ;; + + redhat | rhel | centos | fedora) + # firewalld may not be installed + if ! systemctl is-active firewalld &> /dev/null; then + if ! systemctl status firewalld &> /dev/null; then + is_network_reachable || failure 219 "reset_fw(): failure installing firewalld because repositories are not reachable" + if [ $(versionchk ${VERSION_ID}) -ge $(versionchk "8.0") ]; then + sudo dnf config-manager -y --disable epel-modular + sudo dnf config-manager -y --disable epel + fi + sfRetry4 "sfYum install -q -y firewalld python3 python3-pip" || failure 220 "reset_fw(): failure installing firewalld" + fi + fi + ;; + esac + + {{- if .DefaultFirewall }} + firewall-cmd --add-service={ssh,dhcpv6-client,dns,mdns} || failure 221 "reset_fw(): firewall-offline-cmd failed with $? adding services" + firewall-cmd --runtime-to-permanent || failure 221 "reset_fw(): firewall-offline-cmd failed with $? making permanent" + sudo sed -i 's/^LogDenied=.*$/LogDenied=all/g' /etc/firewalld/firewalld.conf + sudo systemctl restart firewalld.service + return 0 + {{- end }} + + # Clear interfaces attached to zones + for zone in public trusted; do + for nic in $(firewall-offline-cmd --zone=$zone --list-interfaces || true); do + firewall-offline-cmd --zone=$zone --remove-interface=$nic &> /dev/null || true + done + done + + # Attach Internet interface or source IP to zone public if host is gateway + [[ ! -z ${PU_IF} ]] && { + firewall-offline-cmd --zone=public --add-interface=${PU_IF} || failure 221 "reset_fw(): firewall-offline-cmd failed with $? adding interfaces" + } + + {{- if or .PublicIP .IsGateway }} + [[ -z ${PU_IF} ]] && { + firewall-offline-cmd --zone=public --add-source=${PU_IP}/32 || failure 222 "reset_fw(): firewall-offline-cmd failed with $? adding sources" + } + {{- end }} + + # Sets the default target of packets coming from public interface to DROP + firewall-offline-cmd --zone=public --set-target=DROP || failure 223 "reset_fw(): firewall-offline-cmd failed with $? dropping public zone" + + # Enable masquerade + # firewall-offline-cmd --zone=public --add-masquerade + + {{- if or .PublicIP .IsGateway }} + # Attach LAN interfaces to zone public, adding to trusted zone results in all ports visible from internet using nmap + [[ ! -z ${PR_IFs} ]] && { + for i in ${PR_IFs}; do + firewall-offline-cmd --zone=trusted --add-interface=${PR_IFs} || failure 224 "reset_fw(): firewall-offline-cmd failed with $? adding ${PR_IFs} to trusted" + done + } + {{- else }} + # Other machines can trust internal network... + [[ ! -z ${PR_IFs} ]] && { + for i in ${PR_IFs}; do + firewall-offline-cmd --zone=trusted --add-interface=${PR_IFs} || failure 224 "reset_fw(): firewall-offline-cmd failed with $? adding ${PR_IFs} to trusted" + done + } + {{- end }} + + # Attach lo interface to zone trusted + firewall-offline-cmd --zone=trusted --add-interface=lo || failure 225 "reset_fw(): firewall-offline-cmd failed with $? adding lo to trusted" + + # Allow service ssh on public zone + op=-1 + SSHEC=$(firewall-offline-cmd --zone=public --add-service=ssh) && op=$? || true + if [[ $op -eq 11 ]] || [[ $op -eq 12 ]] || [[ $op -eq 16 ]]; then + op=0 + fi + + if [[ $(echo $SSHEC | grep "ALREADY_ENABLED") ]]; then + op=0 + fi + + if [[ $op -ne 0 ]]; then + failure 226 "reset_fw(): firewall-offline-cmd failed with $op adding ssh service" + fi + + sfService enable firewalld &> /dev/null || failure 227 "reset_fw(): service firewalld enable failed with $?" + sfService start firewalld &> /dev/null || failure 228 "reset_fw(): service firewalld start failed with $?" + + sop=-1 + firewall-cmd --runtime-to-permanent && sop=$? || sop=$? + if [[ $sop -ne 0 ]]; then + if [[ $sop -ne 31 ]]; then + failure 229 "reset_fw(): saving rules with firewall-cmd failed with $sop" + fi + fi + + # Log dropped packets + sudo sed -i 's/^LogDenied=.*$/LogDenied=all/g' /etc/firewalld/firewalld.conf + + # Save current fw settings as permanent + sfFirewallReload || (echo "reloading firewall failed with $?" && return 1) + + firewall-cmd --list-all --zone=trusted > /tmp/firewall-trusted.cfg || true + firewall-cmd --list-all --zone=public > /tmp/firewall-public.cfg || true + + return 0 +} + +NICS= +# PR_IPs= +PR_IFs= +PU_IP= +PU_IF= +i_PR_IF= +o_PR_IF= +NETMASK= +AWS= +GCP= +OUT= +FEN= + +# Don't request dns name servers from DHCP server +# Don't update default route +function configure_dhclient() { + # kill any dhclient process already running + sudo pkill dhclient || true + + [ -f /etc/dhcp/dhclient.conf ] && (sed -i -e 's/, domain-name-servers//g' /etc/dhcp/dhclient.conf || true) + + if [ -d /etc/dhcp/ ]; then + HOOK_FILE=/etc/dhcp/dhclient-enter-hooks + cat >> $HOOK_FILE <<- EOF + make_resolv_conf() { + : + } + + {{- if .AddGateway }} + unset new_routers + {{- end}} +EOF + chmod +x $HOOK_FILE + fi +} + +function is_ip_private() { + ip=$1 + ipv=$(sfIP2long $ip) + + {{ if .EmulatedPublicNet}} + r=$(sfCidr2iprange {{ .EmulatedPublicNet }}) + bv=$(sfIP2long $(cut -d- -f1 <<< $r)) + ev=$(sfIP2long $(cut -d- -f2 <<< $r)) + [ $ipv -ge $bv -a $ipv -le $ev ] && return 0 + {{- end }} + for r in "192.168.0.0-192.168.255.255" "172.16.0.0-172.31.255.255" "10.0.0.0-10.255.255.255"; do + bv=$(sfIP2long $(cut -d- -f1 <<< $r)) + ev=$(sfIP2long $(cut -d- -f2 <<< $r)) + [ $ipv -ge $bv -a $ipv -le $ev ] && return 0 + done + return 1 +} + +function check_providers() { + if [[ "{{.ProviderName}}" == "aws" ]]; then + echo "It actually IS AWS" + AWS=1 + else + echo "It is NOT AWS" + AWS=0 + fi + + if [[ "{{.ProviderName}}" == "gcp" ]]; then + echo "It actually IS GCP" + GCP=1 + else + echo "It is NOT GCP" + GCP=0 + fi + + if [[ "{{.ProviderName}}" == "outscale" ]]; then + echo "It actually IS Outscale" + OUT=1 + else + echo "It is NOT Outscale" + OUT=0 + fi + + if [[ "{{.ProviderName}}" == "huaweicloud" ]]; then + echo "It actually IS huaweicloud" + FEN=1 + else + echo "It is NOT huaweicloud" + FEN=0 + fi +} + +function identify_nics() { + NICS=$(for i in $(find /sys/devices -name net -print | grep -v virtual); do ls $i; done) + NICS=${NICS/[[:cntrl:]]/ } + + NETMASK=$(echo {{ .CIDR }} | cut -d/ -f2) + + for IF in ${NICS}; do + IP=$(ip a | grep $IF | grep inet | awk '{print $2}' | cut -d '/' -f1) || true + [[ ! -z $IP ]] && is_ip_private $IP && PR_IFs="$PR_IFs $IF" + done + PR_IFs=$(echo ${PR_IFs} | xargs) || true + PU_IF=$(ip route get 8.8.8.8 | awk -F"dev " 'NR==1{split($2,a," ");print a[1]}' 2> /dev/null) || true + PU_IP=$(ip a | grep ${PU_IF} | grep inet | awk '{print $2}' | cut -d '/' -f1) || true + if [[ ! -z ${PU_IP} ]]; then + if is_ip_private $PU_IP; then + PU_IF= + + NO404=$(curl -s -o /dev/null -w "%{http_code}" http://169.254.169.254/latest/meta-data/public-ipv4 2> /dev/null | grep 404) || true + if [[ -z $NO404 ]]; then + # Works with FlexibleEngine and potentially with AWS (not tested yet) + PU_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4 2> /dev/null) || true + [[ -z $PU_IP ]] && PU_IP=$(curl ipinfo.io/ip 2> /dev/null) + fi + fi + fi + [[ -z ${PR_IFs} ]] && PR_IFs=$(substring_diff "$NICS" "$PU_IF") + + # Keeps track of interfaces identified for future scripting use + echo "$PR_IFs" > ${SF_VARDIR}/state/private_nics + echo "$PU_IF" > ${SF_VARDIR}/state/public_nics + + check_providers + + echo "NICS identified: $NICS" + echo " private NIC(s): $PR_IFs" + echo " public NIC: $PU_IF" + echo +} + +function substring_diff() { + read -a l1 <<< $1 + read -a l2 <<< $2 + echo "${l1[@]}" "${l2[@]}" | tr ' ' '\n' | sort | uniq -u +} + +function collect_original_packages() { + case $LINUX_KIND in + debian | ubuntu) + dpkg-query -l > ${SF_VARDIR}/log/packages_installed_before.phase2.list + ;; + redhat | rhel | centos | fedora) + rpm -qa | sort > ${SF_VARDIR}/log/packages_installed_before.phase2.list + ;; + *) ;; + esac +} + +function ensure_curl_is_installed() { + case $LINUX_KIND in + ubuntu | debian) + if [[ -n $(which curl) ]]; then + return 0 + fi + DEBIAN_FRONTEND=noninteractive UCF_FORCE_CONFFNEW=1 apt-get update || return 1 + DEBIAN_FRONTEND=noninteractive UCF_FORCE_CONFFNEW=1 apt-get install --no-install-recommends -y curl || return 1 + ;; + redhat | rhel | centos | fedora) + if [[ -n $(which curl) ]]; then + return 0 + fi + sfRetry4 "sfYum install -y -q curl &>/dev/null" || return 1 + ;; + *) + failure 216 "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND'!" + ;; + esac + + return 0 +} + +function collect_installed_packages() { + case $LINUX_KIND in + debian | ubuntu) + dpkg-query -l > ${SF_VARDIR}/log/packages_installed_after.phase2.list + ;; + redhat | rhel | centos | fedora) + rpm -qa | sort > ${SF_VARDIR}/log/packages_installed_after.phase2.list + ;; + *) ;; + + esac +} + +# If host isn't a gateway, we need to configure temporarily and manually gateway on private hosts to be able to update packages +function ensure_network_connectivity() { + op=-1 + CONNECTED=$(curl -I www.google.com -m 5 | grep "200 OK") && op=$? || true + [ $op -ne 0 ] && echo "ensure_network_connectivity started WITHOUT network..." || echo "ensure_network_connectivity started WITH network..." + + {{- if .AddGateway }} + if [[ -n $(PATH=$PATH:/usr/sbin:/sbin which route) ]]; then + PATH=$PATH:/usr/sbin:/sbin sudo route del -net default &> /dev/null + PATH=$PATH:/usr/sbin:/sbin sudo route add -net default gw {{ .DefaultRouteIP }} + else + PATH=$PATH:/usr/sbin:/sbin sudo ip route del default + PATH=$PATH:/usr/sbin:/sbin sudo ip route add default via {{ .DefaultRouteIP }} + fi + {{- else }} + : + {{- end}} + + op=-1 + CONNECTED=$(curl -I www.google.com -m 5 | grep "200 OK") && op=$? || true + [ $op -ne 0 ] && echo "ensure_network_connectivity finished WITHOUT network..." || echo "ensure_network_connectivity finished WITH network..." + + if [[ $op -ne 0 ]]; then + return 1 + fi + + return 0 +} + +function configure_dns() { + if systemctl status systemd-resolved &> /dev/null; then + echo "Configuring dns with resolved" + configure_dns_systemd_resolved + elif systemctl status resolvconf &> /dev/null; then + echo "Configuring dns with resolvconf" + configure_dns_resolvconf + else + echo "Configuring dns legacy" + configure_dns_legacy + fi +} + +# adds entry in /etc/hosts corresponding to FQDN hostname with private IP +# Follows CentOS rules : +# - if there is a domain suffix in hostname, /etc/hosts contains FQDN as first entry and short hostname as second, after the IP +# - if there is no domain suffix in hostname, /etc/hosts contains short hostname as first entry, after the IP +function update_fqdn() { + IF=${PR_IFs[0]} + [ -z ${IF} ] && return + IP=$(ip a | grep $IF | grep inet | awk '{print $2}' | cut -d '/' -f1) || true + sed -i -nr "/^${IP}"'/!p;$a'"${IP}"'\t{{ .HostName }}' /etc/hosts +} + +function install_route_if_needed() { + case $LINUX_KIND in + debian) + if [[ -z $(which route) ]]; then + for iter in {1..4} + do + sfApt install -y --no-install-recommends net-tools && break + [[ "$iter" == '4' ]] && return 1 + done + fi + ;; + ubuntu) + if [[ -z $(which route) ]]; then + for iter in {1..4} + do + sfApt install -y --no-install-recommends net-tools && break + [[ "$iter" == '4' ]] && return 1 + done + fi + ;; + redhat | rhel | centos) + if [[ -z $(which route) ]]; then + sfRetry4 "sfYum install -y net-tools" || return 1 + fi + ;; + fedora) + if [[ -z $(which route) ]]; then + sfRetry4 "sfYum install -y net-tools" || return 1 + fi + ;; + *) + echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" + return 1 + ;; + esac + return 0 +} + +function allow_custom_env_ssh_vars() { + cat >> /etc/ssh/sshd_config <<- EOF + AcceptEnv SAFESCALESSHUSER + AcceptEnv SAFESCALESSHPASS +EOF + systemctl reload sshd +} + +function configure_network() { + case $LINUX_KIND in + debian | ubuntu) + if systemctl status systemd-networkd &> /dev/null; then + install_route_if_needed + configure_network_systemd_networkd + elif systemctl status networking &> /dev/null; then + install_route_if_needed + configure_network_debian + else + failure 192 "failed to determine how to configure network" + fi + ;; + + redhat | rhel | centos) + # Network configuration + if systemctl status systemd-networkd &> /dev/null; then + install_route_if_needed + configure_network_systemd_networkd + else + install_route_if_needed + configure_network_redhat + fi + ;; + + fedora) + install_route_if_needed + configure_network_redhat + ;; + + *) + failure 193 "Unsupported Linux distribution '$LINUX_KIND'!" + ;; + esac + + {{- if .IsGateway }} + configure_as_gateway || failure 194 "failed to configure machine as a gateway" + {{- end }} + + update_fqdn + allow_custom_env_ssh_vars + + check_for_network || { + failure 195 "missing or incomplete network connectivity" + } +} + +# Configure network for Debian distribution +function configure_network_debian() { + echo "Configuring network (debian-like)..." + + local path=/etc/network/interfaces.d + mkdir -p ${path} + local cfg=${path}/50-cloud-init.cfg + rm -f ${cfg} + + for IF in ${NICS}; do + if [[ "$IF" == "$PU_IF" ]]; then + cat > ${path}/10-${IF}-public.cfg <<- EOF + auto ${IF} + iface ${IF} inet dhcp +EOF + else + cat > ${path}/11-${IF}-private.cfg <<- EOF + auto ${IF} + iface ${IF} inet dhcp + {{- if .AddGateway }} + up route add -net default gw {{ .DefaultRouteIP }} + {{- end}} +EOF + fi + done + + {{- if .IsGateway }} + {{- else }} + for IF in ${NICS}; do + if [[ "$IF" == "$PU_IF" ]]; then + : + else + local tmppath=/tmp + local altpath=/etc/network/if-up.d + cat <<- EOF > ${tmppath}/my-route + #!/bin/sh + if [ "\${IFACE}" = "${IF}" ]; then + ip route add default via {{ .DefaultRouteIP }} + sudo /usr/sbin/resolvconf -u + fi +EOF + sudo cp ${tmppath}/my-route ${altpath}/my-route + sudo chmod 751 ${altpath}/my-route + fi + done + {{- end }} + + echo "Looking for network..." + check_for_network || { + failure 196 "failed network cfg 0" + } + + configure_dhclient + + sudo /sbin/dhclient || true + + echo "Looking for network..." + check_for_network || { + failure 197 "failed network cfg 1" + } + + systemctl restart networking + + PATH=$PATH:/usr/sbin:/sbin sudo dhclient || true + + echo "Looking for network..." + check_for_network || { + failure 199 "failed network cfg 2" + } + + is_network_reachable || { + failure 200 "without network" + } + + reset_fw || failure 200 "failure resetting firewall" + + echo "done" +} + +# Configure network using systemd-networkd +function configure_network_systemd_networkd() { + echo "Configuring network (using netplan and systemd-networkd)..." + + {{- if .IsGateway }} + ISGW=1 + {{- else}} + ISGW=0 + {{- end}} + + mkdir -p /etc/netplan + rm -f /etc/netplan/* + + # Recreate netplan configuration with last netplan version and more settings + for IF in ${NICS}; do + if [[ "$IF" == "$PU_IF" ]]; then + cat <<- EOF > /etc/netplan/10-${IF}-public.yaml + network: + version: 2 + renderer: networkd + + ethernets: + $IF: + dhcp4: true + dhcp6: false + critical: true + dhcp4-overrides: + use-dns: false + use-routes: true +EOF + else + cat <<- EOF > /etc/netplan/11-${IF}-private.yaml + network: + version: 2 + renderer: networkd + + ethernets: + ${IF}: + dhcp4: true + dhcp6: false + critical: true + dhcp4-overrides: + use-dns: false + {{- if .AddGateway }} + use-routes: false + routes: + - to: 0.0.0.0/0 + via: {{ .DefaultRouteIP }} + scope: global + on-link: true + {{- else }} + use-routes: true + {{- end}} +EOF + fi + done + + {{- if .IsGateway }} + {{- else }} + case $LINUX_KIND in + debian) + for IF in ${NICS}; do + if [[ "$IF" == "$PU_IF" ]]; then + : + else + local tmppath=/tmp + local altpath=/etc/network/if-up.d + cat <<- EOF > ${tmppath}/my-route + #!/bin/sh + if [ "\${IFACE}" = "${IF}" ]; then + ip route add default via {{ .DefaultRouteIP }} + sudo /usr/sbin/resolvconf -u + fi +EOF + sudo cp ${tmppath}/my-route ${altpath}/my-route + sudo chmod 751 ${altpath}/my-route + fi + done + ;; + *);; + esac + + {{- end }} + + if [[ "{{.ProviderName}}" == "aws" ]]; then + echo "It actually IS AWS" + AWS=1 + else + echo "It is NOT AWS" + AWS=0 + fi + + if [[ $AWS -eq 1 ]]; then + if [[ $ISGW -eq 0 ]]; then + rm -f /etc/netplan/* + # Recreate netplan configuration with last netplan version and more settings + for IF in ${NICS}; do + if [[ "$IF" == "$PU_IF" ]]; then + cat <<- EOF > /etc/netplan/10-$IF-public.yaml + network: + version: 2 + renderer: networkd + + ethernets: + ${IF}: + dhcp4: true + dhcp6: false + critical: true + dhcp4-overrides: + use-dns: true + use-routes: true +EOF + else + cat <<- EOF > /etc/netplan/11-${IF}-private.yaml + network: + version: 2 + renderer: networkd + + ethernets: + ${IF}: + dhcp4: true + dhcp6: false + critical: true + dhcp4-overrides: + use-dns: true + {{- if .AddGateway }} + use-routes: true + routes: + - to: 0.0.0.0/0 + via: {{ .DefaultRouteIP }} + scope: global + on-link: true + {{- else }} + use-routes: true + {{- end}} +EOF + fi + done + fi + fi + + NETERR=0 + + netplan generate || failure 202 "failure running netplan generate" + netplan apply || failure 203 "failure running netplan apply" + + configure_dhclient + sleep 5 + + if [[ $AWS -eq 1 ]]; then + echo "Looking for network..." + check_for_network || { + failure 204 "failed networkd cfg 2" + } + fi + + systemctl restart systemd-networkd + sleep 5 + + if [[ $AWS -eq 1 ]]; then + echo "Looking for network..." + check_for_network || { + failure 205 "failed networkd cfg 3" + } + fi + + case $LINUX_KIND in + ubuntu) + if [[ $(lsb_release -rs | cut -d. -f1) -eq 20 ]]; then + if [[ ${NETERR} -eq 1 ]]; then + echo "Ignoring problems on AWS, ubuntu 20.04 LTS ..." + fi + else + if [[ ${NETERR} -eq 1 ]]; then + check_for_network || failure 206 "no network found" + fi + fi + ;; + *) ;; + esac + + reset_fw || failure 206 "failure resetting firewall" + + echo "done" +} + +# Configure network for redhat alike distributions (rhel, centos, ...) +function configure_network_redhat() { + echo "Configuring network (redhat-like)..." + + if [ $VERSION_ID -eq 8 ]; then + echo "Configuring network (redhat8-like)..." + nmcli c mod eth0 connection.autoconnect yes || true + fi + + if [[ -z $VERSION_ID || $VERSION_ID -lt 7 ]]; then + disable_svc() { + chkconfig $1 off + } + enable_svc() { + chkconfig $1 on + } + stop_svc() { + service $1 stop + } + restart_svc() { + service $1 restart + } + else + disable_svc() { + systemctl disable $1 + } + enable_svc() { + systemctl enable $1 + } + stop_svc() { + systemctl stop $1 + } + restart_svc() { + systemctl restart $1 + } + fi + + NMCLI=$(which nmcli 2> /dev/null) || true + if [[ ${AWS} -eq 1 && $(sfGetFact "distrib_version") -ge 8 ]]; then + configure_network_redhat_without_nmcli || { + failure 208 "failed to set network without NetworkManager" + } + elif [[ ${GCP} -eq 1 ]]; then + configure_network_redhat_without_nmcli || { + failure 208 "failed to set network without NetworkManager" + } + elif [[ ${OUT} -eq 1 ]]; then + configure_network_redhat_without_nmcli || { + failure 208 "failed to set network without NetworkManager" + } + elif [[ ${FEN} -eq 1 ]]; then + configure_network_redhat_without_nmcli || { + failure 208 "failed to set network without NetworkManager" + } + else + NMCLI=$(which nmcli 2> /dev/null) || true + if [[ -z "${NMCLI}" ]]; then + configure_network_redhat_without_nmcli || { + failure 208 "failed to set network without NetworkManager" + } + else + configure_network_redhat_with_nmcli || { + failure 209 "failed to set network with NetworkManager" + } + fi + fi + + reset_fw || { + failure 210 "failure setting firewall" + } + + echo "done" +} + +# Configure network for redhat 6- alike distributions (rhel, centos, ...) +function configure_network_redhat_without_nmcli() { + echo "Configuring network (RedHat 6- alike)..." + + # We don't want NetworkManager if RedHat/CentOS < 7 + stop_svc NetworkManager &> /dev/null + disable_svc NetworkManager &> /dev/null + if [[ ${FEN} -eq 0 ]]; then + sfRetry4 "sfYum remove -y NetworkManager &>/dev/null" + echo "exclude=NetworkManager" >> /etc/yum.conf + + if which dnf; then + dnf install -q -y network-scripts || { + dnf install -q -y NetworkManager-config-routing-rules + echo net.ipv4.ip_forward=1 >> /etc/sysctl.d/90-override.conf + sysctl -w net.ipv4.ip_forward=1 + sysctl -p + firewall-cmd --complete-reload + } + else + sfYum install -q -y network-scripts || { + sfYum install -q -y NetworkManager-config-routing-rules + echo net.ipv4.ip_forward=1 >> /etc/sysctl.d/90-override.conf + sysctl -w net.ipv4.ip_forward=1 + sysctl -p + firewall-cmd --complete-reload + } + fi + else + sfYum remove -y NetworkManager &> /dev/null + fi + + # Configure all network interfaces in dhcp + for IF in $NICS; do + if [[ ${IF} != "lo" ]]; then + cat > /etc/sysconfig/network-scripts/ifcfg-${IF} <<- EOF + DEVICE=$IF + BOOTPROTO=dhcp + ONBOOT=yes + NM_CONTROLLED=no +EOF + {{- if .DNSServers }} + i=1 + {{- range .DNSServers }} + echo "DNS${i}={{ . }}" >> /etc/sysconfig/network-scripts/ifcfg-${IF} + i=$((i + 1)) + {{- end }} + {{- else }} + EXISTING_DNS=$(grep nameserver /etc/resolv.conf | awk '{print $2}') + if [[ -z ${EXISTING_DNS} ]]; then + echo "DNS1=1.1.1.1" >> /etc/sysconfig/network-scripts/ifcfg-${IF} + else + echo "DNS1=$EXISTING_DNS" >> /etc/sysconfig/network-scripts/ifcfg-${IF} + fi + {{- end }} + + {{- if .AddGateway }} + echo "GATEWAY={{ .DefaultRouteIP }}" >> /etc/sysconfig/network-scripts/ifcfg-${IF} + {{- end }} + fi + done + + configure_dhclient + sleep 5 + + {{- if .AddGateway }} + echo "GATEWAY={{ .DefaultRouteIP }}" > /etc/sysconfig/network + {{- end }} + + enable_svc network + restart_svc network + + echo "exclude=NetworkManager" >> /etc/yum.conf + sleep 5 + + reset_fw || failure 206 "problem resetting firewall" + + echo "done" +} + +# Configure network for redhat7+ alike distributions (rhel, centos, ...) +function configure_network_redhat_with_nmcli() { + echo "Configuring network (RedHat 7+ alike with Network Manager)..." + + # Do some cleanup inside /etc/sysconfig/network-scripts + CONNECTIONS=$(nmcli -f "NAME,DEVICE" -c no -t con) + for i in $CONNECTIONS; do + NAME=$(echo ${i} | cut -d':' -f1) + DEVICE=$(echo ${i} | cut -d':' -f2) + [[ -z "${DEVICE}" ]] && mv /etc/sysconfig/network-scripts/ifcfg-${NAME} /etc/sysconfig/network-scripts/disabled.ifcfg-${NAME} + done + + # Configure all network interfaces + for IF in $(nmcli -f 'DEVICE' -c no -t dev); do + # We change nothing on device lo + [[ "${IF}" == "lo" ]] && continue + + DEV_IP=$(nmcli -g IP4.ADDRESS dev show "${IF}") + # We change nothing on device with Public IP Address + is_ip_private $(echo ${DEV_IP} | cut -d/ -f1) || continue + + CONN=$(nmcli -c no -t -f "NAME,DEVICE" con | grep ${IF} | cut -d: -f1) + nmcli con mod "${CONN}" connection.autoconnect yes || return 1 + # Assume the IP Address cannot change even if the first time it is affected by DHCP + nmcli con mod "${CONN}" ipv4.addresses "${DEV_IP}" || return 1 + nmcli con mod "${CONN}" ipv4.method manual || return 1 + {{- if .DNSServers }} + {{- range .DNSServers }} + nmcli con mod "${CONN}" +ipv4.dns {{ . }} || return 1 + {{- end }} + {{- else }} + EXISTING_DNS=$(grep nameserver /etc/resolv.conf | awk '{print $2}') + if [[ -z ${EXISTING_DNS} ]]; then + ${NMCLI} con mod "${CONN}" +ipv4.dns 1.1.1.1 || return 1 + else + ${NMCLI} con mod "${CONN}" +ipv4.dns ${EXISTING_DNS} || return 1 + fi + {{- end }} + + {{- if .AddGateway }} + nmcli con mod "${CONN}" ipv4.gateway "{{ .DefaultRouteIP }}" + {{- end}} + + done + + nmcli con reload + + configure_dhclient || return 1 + + enable_svc NetworkManager + restart_svc NetworkManager + + return 0 +} + +function check_for_ip() { + ip=$(ip -f inet -o addr show $1 | cut -d' ' -f7 | cut -d' ' -f1) + [ -z "$ip" ] && echo "Failure checking for ip '$ip' when evaluating '$1'" && return 1 + return 0 +} +export -f check_for_ip + +function check_for_network_refined() { + NETROUNDS=$1 + REACHED=0 + + for i in $(seq $NETROUNDS); do + if which wget; then + wget -T 10 -O /dev/null www.google.com &> /dev/null && REACHED=1 && break + ping -n -c1 -w10 -i5 www.google.com && REACHED=1 && break + else + ping -n -c1 -w10 -i5 www.google.com && REACHED=1 && break + fi + done + + [ $REACHED -eq 0 ] && echo "Unable to reach network" && return 1 + + [ ! -z "$PU_IF" ] && { + sfRetry4 check_for_ip $PU_IF || return 1 + } + for i in $PR_IFs; do + sfRetry4 check_for_ip $i || return 1 + done + return 0 +} + +# Checks network is set correctly +# - DNS and routes (by pinging a FQDN) +# - IP address on "physical" interfaces +function check_for_network() { + op=-1 + check_for_network_refined 12 && op=$? || true + return $op +} + +function configure_as_gateway() { + echo "Configuring host as gateway..." + + if [[ ! -z ${PR_IFs} ]]; then + # Enable forwarding + for i in /etc/sysctl.d/* /etc/sysctl.conf; do + grep -v "net.ipv4.ip_forward=" ${i} > ${i}.new + mv -f ${i}.new ${i} + done + cat > /etc/sysctl.d/21-gateway.conf <<- EOF + net.ipv4.ip_forward=1 + net.ipv4.ip_nonlocal_bind=1 +EOF + case $LINUX_KIND in + ubuntu) systemctl restart systemd-sysctl ;; + *) sysctl -p ;; + esac + fi + + if [[ ! -z ${PU_IF} ]]; then + # Dedicated public interface available... + + # Allows ping + firewall-offline-cmd --direct --add-rule ipv4 filter INPUT 0 -p icmp -m icmp --icmp-type 8 -s 0.0.0.0/0 -d 0.0.0.0/0 -j ACCEPT + # Allows masquerading on public zone + firewall-offline-cmd --zone=public --add-masquerade + fi + # Enables masquerading on trusted zone (mainly for docker networks) + firewall-offline-cmd --zone=trusted --add-masquerade + + # Allows default services on public zone + firewall-offline-cmd --zone=public --add-service=ssh 2> /dev/null + # Applies fw rules + + # Update ssh port + [ ! -f /etc/firewalld/services/ssh.xml ] && [ -f /usr/lib/firewalld/services/ssh.xml ] && cp /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/ssh.xml + sed -i -E "s///gm" /etc/firewalld/services/ssh.xml + sed -i -E "s///gm" /etc/firewalld/services/ssh.xml + sfFirewallReload + + sed -i '/^\#*AllowTcpForwarding / s/^.*$/AllowTcpForwarding yes/' /etc/ssh/sshd_config || failure 208 "failure allowing tcp forwarding" + + systemctl restart sshd + + case $LINUX_KIND in + centos) + echo net.ipv4.ip_forward=1 >> /etc/sysctl.d/90-override.conf + sysctl -w net.ipv4.ip_forward=1 + sysctl -p + firewall-cmd --complete-reload + ;; + *) ;; + esac + + echo "done" +} + +function configure_dns_legacy_issues() { + case $LINUX_KIND in + debian) + if [ $VERSION_ID -eq 9 ]; then + cp /etc/resolv.conf.tested /etc/resolv.conf + fi + ;; + *) ;; + + esac +} + +function configure_dns_fallback() { + echo "Configuring /etc/resolv.conf..." + cp /etc/resolv.conf /etc/resolv.conf.bak + + rm -f /etc/resolv.conf + if [[ -e /etc/dhcp/dhclient.conf ]]; then + echo "prepend domain-name-servers 1.1.1.1;" >> /etc/dhcp/dhclient.conf + else + echo "/etc/dhcp/dhclient.conf not modified" + fi + cat > /etc/resolv.conf <<- EOF + nameserver 1.1.1.1 +EOF + + cp /etc/resolv.conf /etc/resolv.conf.tested + touch /etc/resolv.conf && sleep 2 || true + + # give it a try + PATH=$PATH:/usr/sbin:/sbin sudo dhclient + sleep 2 + + op=-1 + CONNECTED=$(curl -I www.google.com -m 5 | grep "200 OK") && op=$? || true + [ ${op} -ne 0 ] && echo "changing dns wasn't a good idea..." && cp /etc/resolv.conf.bak /etc/resolv.conf || echo "dns change OK..." + + configure_dns_legacy_issues + + echo "done" +} + +function configure_dns_legacy() { + echo "Configuring /etc/resolv.conf..." + cp /etc/resolv.conf /etc/resolv.conf.bak + + rm -f /etc/resolv.conf + {{- if .DNSServers }} + if [[ -e /etc/dhcp/dhclient.conf ]]; then + dnsservers= + for i in {{range .DNSServers}} {{end}}; do + [[ ! -z ${dnsservers} ]] && dnsservers="$dnsservers, " + done + [[ ! -z ${dnsservers} ]] && echo "prepend domain-name-servers $dnsservers;" >> /etc/dhcp/dhclient.conf + else + echo "dhclient.conf not modified" + fi + {{- else }} + if [[ -e /etc/dhcp/dhclient.conf ]]; then + echo "prepend domain-name-servers 1.1.1.1;" >> /etc/dhcp/dhclient.conf + else + echo "/etc/dhcp/dhclient.conf not modified" + fi + {{- end }} + cat > /etc/resolv.conf <<- EOF + {{- if .DNSServers }} + {{- range .DNSServers }} + nameserver {{ . }} + {{- end }} + {{- else }} + nameserver 1.1.1.1 + {{- end }} +EOF + + cp /etc/resolv.conf /etc/resolv.conf.tested + touch /etc/resolv.conf && sleep 2 || true + + # give it a try + PATH=$PATH:/usr/sbin:/sbin sudo dhclient + sleep 2 + + op=-1 + CONNECTED=$(curl -I www.google.com -m 5 | grep "200 OK") && op=$? || true + [ ${op} -ne 0 ] && echo "changing dns wasn't a good idea..." && cp /etc/resolv.conf.bak /etc/resolv.conf || echo "dns change OK..." + + configure_dns_legacy_issues + + echo "done" +} + +function configure_dns_resolvconf() { + echo "Configuring resolvconf..." + + EXISTING_DNS=$(grep nameserver /etc/resolv.conf | awk '{print $2}') + + cat > /etc/resolvconf/resolv.conf.d/head <<- EOF + {{- if .DNSServers }} + {{- range .DNSServers }} + nameserver {{ . }} + {{- end }} + {{- else }} + nameserver 1.1.1.1 + {{- end }} +EOF + + resolvconf -u + echo "done" +} + +function configure_dns_systemd_resolved() { + echo "Configuring systemd-resolved..." + + {{- if not .DefaultRouteIP }} + rm -f /etc/resolv.conf + ln -s /run/systemd/resolve/resolv.conf /etc + {{- end }} + + cat > /etc/systemd/resolved.conf <<- EOF + [Resolve] + {{- if .DNSServers }} + DNS={{ range .DNSServers }}{{ . }} {{ end }} + {{- else }} + DNS=1.1.1.1 + {{- end}} + Cache=yes + DNSStubListener=yes +EOF + systemctl restart systemd-resolved + echo "done" +} + +function is_network_reachable() { + NETROUNDS=4 + REACHED=0 + TRIED=0 + + for i in $(seq ${NETROUNDS}); do + if which curl; then + TRIED=1 + curl -s -I www.google.com -m 4 | grep "200 OK" && REACHED=1 && break + fi + + if [[ ${TRIED} -eq 1 ]]; then + continue + fi + + if which wget; then + TRIED=1 + wget -T 4 -O /dev/null www.google.com &> /dev/null && REACHED=1 && break + fi + + if [[ ${TRIED} -eq 1 ]]; then + continue + fi + + ping -n -c1 -w4 -i1 www.google.com && REACHED=1 && break + done + + if [[ ${REACHED} -eq 0 ]]; then + echo "Unable to reach network" + return 1 + fi + + return 0 +} + +function install_drivers_nvidia() { + case $LINUX_KIND in + ubuntu) + sfFinishPreviousInstall + add-apt-repository -y ppa:graphics-drivers &> /dev/null + sfRetry4 "sfApt update" || failure 201 "apt update failed" + sfRetry4 "sfApt -y install nvidia-410 &>/dev/null" || { + sfRetry4 "sfApt -y install nvidia-driver-410 &>/dev/null" || failure 201 "failed nvidia driver install" + } + ;; + + debian) + if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then + echo -e "blacklist nouveau\nblacklist lbm-nouveau\noptions nouveau modeset=0\nalias nouveau off\nalias lbm-nouveau off" >> /etc/modprobe.d/blacklist-nouveau.conf + rmmod nouveau + fi + sfRetry4 "sfApt update" + sfRetry4 "sfApt install -y --no-install-recommends dkms build-essential linux-headers-$(uname -r) gcc make &>/dev/null" || failure 202 "failure installing nvdiia requirements" + dpkg --add-architecture i386 &> /dev/null + sfRetry4 "sfApt update" + sfRetry4 "sfApt install -y --no-install-recommends lib32z1 lib32ncurses5 &>/dev/null" || failure 203 "failure installing nvidia requirements" + wget http://us.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run &> /dev/null || failure 204 "failure downloading nvidia installer" + bash NVIDIA-Linux-x86_64-410.78.run -s || failure 205 "failure running nvidia installer" + ;; + + redhat | rhel | centos | fedora) + if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then + echo -e "blacklist nouveau\noptions nouveau modeset=0" >> /etc/modprobe.d/blacklist-nouveau.conf + dracut --force + rmmod nouveau + fi + sfRetry4 "sfYum -y -q install kernel-devel.$(uname -i) kernel-headers.$(uname -i) gcc make &>/dev/null" || failure 206 "failure installing nvidia requirements" + wget http://us.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run || failure 207 "failure downloading nvidia installer" + # if there is a version mismatch between kernel sources and running kernel, building the driver would require 2 reboots to get it done, right now this is unsupported + if [ $(uname -r) == $(sfYum list installed | grep kernel-headers | awk {'print $2'}).$(uname -i) ]; then + bash NVIDIA-Linux-x86_64-410.78.run -s || failure 208 "failure running nvidia installer" + fi + rm -f NVIDIA-Linux-x86_64-410.78.run + ;; + *) + failure 209 "Unsupported Linux distribution '$LINUX_KIND'!" + ;; + esac +} + +function early_packages_update() { + # Ensure IPv4 will be used before IPv6 when resolving hosts (the latter shouldn't work regarding the network configuration we set) + cat > /etc/gai.conf <<- EOF + precedence ::ffff:0:0/96 100 + scopev4 ::ffff:169.254.0.0/112 2 + scopev4 ::ffff:127.0.0.0/104 2 + scopev4 ::ffff:0.0.0.0/96 14 +EOF + + case $LINUX_KIND in + debian) + # Disable interactive installations + export DEBIAN_FRONTEND=noninteractive + export UCF_FORCE_CONFFNEW=1 + # # Force use of IPv4 addresses when installing packages + # echo 'Acquire::ForceIPv4 "true";' >/etc/apt/apt.conf.d/99force-ipv4 + + sfApt update || { + echo "problem updating package repos" + return 209 + } + # Force update of systemd, pciutils + sfApt install -q -y systemd pciutils sudo || { + echo "failure installing systemd and other basic requirements" + return 209 + } + # systemd, if updated, is restarted, so we may need to ensure again network connectivity + ensure_network_connectivity + ;; + + ubuntu) + # Disable interactive installations + export DEBIAN_FRONTEND=noninteractive + export UCF_FORCE_CONFFNEW=1 + # # Force use of IPv4 addresses when installing packages + # echo 'Acquire::ForceIPv4 "true";' >/etc/apt/apt.conf.d/99force-ipv4 + + sfApt update || { + echo "problem updating package repos" + return 210 + } + # Force update of systemd, pciutils and netplan + + if dpkg --compare-versions $(sfGetFact "linux_version") ge 17.10; then + sfApt install -y --no-install-recommends pciutils || { + echo "problem installing pciutils" + return 210 + } + if [[ ! -z ${FEN} && ${FEN} -eq 0 ]]; then + which netplan || { + sfApt install -y --no-install-recommends netplan.io || { + echo "problem installing netplan.io" + return 210 + } + } + else + sfApt install -y --no-install-recommends netplan.io || { + echo "problem installing netplan.io" + return 210 + } + fi + # netplan.io may break networking... So ensure networking is working as expected + ensure_network_connectivity + sfApt install -y --no-install-recommends sudo || { + echo "problem installing sudo" + return 210 + } + else + sfApt install -y --no-install-recommends systemd pciutils sudo || { + echo "problem installing pciutils and sudo" + return 211 + } + fi + + if dpkg --compare-versions $(sfGetFact "linux_version") ge 20.04; then + if [ "{{.ProviderName}}" == "aws" ]; then + : # do nothing + else + sfApt install -y --no-install-recommends systemd || { + echo "problem installing systemd" + return 210 + } + fi + else + sfApt install -y --no-install-recommends systemd || { + echo "problem installing systemd" + return 210 + } + # systemd, if updated, is restarted, so we may need to ensure again network connectivity + ensure_network_connectivity + fi + + # # Security updates ... + # sfApt update &>/dev/null && sfApt install -qy unattended-upgrades && unattended-upgrades -v + ;; + + redhat | centos) + # # Force use of IPv4 addresses when installing packages + # echo "ip_resolve=4" >>/etc/yum.conf + + # Force update of systemd and pciutils + sfYum install -q -y systemd pciutils yum-utils sudo || { + echo "failure installing systemd and other basic requirements" + return 212 + } + # systemd, if updated, is restarted, so we may need to ensure again network connectivity + ensure_network_connectivity + + # # install security updates + # sfYum install -y yum-plugin-security yum-plugin-changelog && sfYum update -y --security + ;; + esac + sfProbeGPU +} + +function install_packages() { + case $LINUX_KIND in + ubuntu | debian) + sfApt install -y -qq --no-install-recommends wget curl jq zip unzip time at sshpass &> /dev/null || failure 213 "failure installing utility packages: jq zip time at" + ;; + redhat | centos) + if [ $(versionchk ${VERSION_ID}) -ge $(versionchk "8.0") ]; then + sfYum install -y -q wget curl jq zip unzip time at sshpass &> /dev/null || failure 214 "failure installing utility packages: jq zip time at" + else + sfYum install --enablerepo=epel -y -q wget curl jq zip unzip time at sshpass &> /dev/null || failure 214 "failure installing utility packages: jq zip time at" + fi + ;; + *) + failure 215 "Unsupported Linux distribution '$LINUX_KIND'!" + ;; + esac +} + +function install_rclone() { + case $LINUX_KIND in + debian | ubuntu) + curl -kqSsL --fail -O https://downloads.rclone.org/rclone-current-linux-amd64.zip && + unzip rclone-current-linux-amd64.zip && + cp rclone-*-linux-amd64/rclone /usr/bin && + mkdir -p /usr/share/man/man1 && + cp rclone-*-linux-amd64/rclone.1 /usr/share/man/man1/ && + rm -rf rclone-* && + chown root:root /usr/bin/rclone && + chmod 755 /usr/bin/rclone && + mandb + ;; + redhat | centos) + if [ $(versionchk ${VERSION_ID}) -ge $(versionchk "8.0") ]; then + curl -kqSsL --fail -O https://downloads.rclone.org/rclone-current-linux-amd64.zip && + unzip rclone-current-linux-amd64.zip && + cp rclone-*-linux-amd64/rclone /usr/bin && + mkdir -p /usr/share/man/man1 && + cp rclone-*-linux-amd64/rclone.1 /usr/share/man/man1/ && + rm -rf rclone-* && + chown root:root /usr/bin/rclone && + chmod 755 /usr/bin/rclone && + mandb + else + sfYum install -y rclone || sfFail 192 "Problem installing node common requirements" + fi + ;; + fedora) + dnf install -y rclone || sfFail 192 "Problem installing node common requirements" + ;; + *) + sfFail 1 "Unmanaged linux distribution type '$(sfGetFact "linux_kind")'" + ;; + esac + + ln -s /usr/bin/rclone /sbin/mount.rclone && ln -s /usr/bin/rclone /usr/bin/rclonefs || sfFail 192 "failed to create rclone soft links" + return 0 +} + +function no_daily_update() { + case $LINUX_KIND in + debian | ubuntu) + # If it's not there, nothing to do + systemctl list-units --all apt-daily.service | egrep -q 'apt-daily' || return 0 + + # first kill apt-daily + systemctl stop apt-daily.service + systemctl kill --kill-who=all apt-daily.service + + # wait until apt-daily dies + while ! (systemctl list-units --all apt-daily.service | egrep -q '(dead|failed)'); do + systemctl stop apt-daily.service + systemctl kill --kill-who=all apt-daily.service + sleep 1 + done + ;; + redhat | fedora | centos) + # If it's not there, nothing to do + systemctl list-units --all yum-cron.service | egrep -q 'yum-cron' || return 0 + systemctl stop yum-cron.service + systemctl kill --kill-who=all yum-cron.service + + # wait until yum-cron dies + while ! (systemctl list-units --all yum-cron.service | egrep -q '(dead|failed)'); do + systemctl stop yum-crom.service + systemctl kill --kill-who=all yum-cron.service + sleep 1 + done + ;; + esac +} + +function add_common_repos() { + case $LINUX_KIND in + ubuntu) + sfFinishPreviousInstall + no_daily_update + add-apt-repository universe -y || return 1 + codename=$(sfGetFact "linux_codename") + echo "deb http://archive.ubuntu.com/ubuntu/ ${codename}-proposed main" > /etc/apt/sources.list.d/${codename}-proposed.list + ;; + debian) + sfFinishPreviousInstall + ;; + redhat | rhel | centos) + if which dnf; then + # Install EPEL repo ... + if [ $(versionchk ${VERSION_ID}) -ge $(versionchk "8.0") ]; then + sudo bash -c "echo '8-stream' > /etc/yum/vars/releasever" + sfRetry4 "dnf install -y epel-release" || { + echo "failure installing custom epel repo" + return 217 + } + else + sfRetry4 "dnf install -y epel-release" || { + echo "failure installing default epel repo" + return 217 + } + fi + sfRetry4 "dnf makecache fast -y || dnf makecache -y" || { + echo "failure updating cache" + return 218 + } + # ... but don't enable it by default + dnf config-manager --set-disabled epel &> /dev/null || true + else + # Install EPEL repo ... + sfRetry4 "yum install -y epel-release" || { + echo "failure installing epel repo" + return 217 + } + sfRetry4 "yum makecache fast || yum makecache" || { + echo "failure updating cache" + return 218 + } + # ... but don't enable it by default + yum-config-manager --disablerepo=epel &> /dev/null || true + fi + ;; + fedora) + sfRetry4 "dnf makecache fast -y || dnf makecache -y" || { + echo "failure updating cache" + return 218 + } + ;; + esac +} + +function configure_locale() { + case $LINUX_KIND in + ubuntu | debian) + locale-gen en_US.UTF-8 + ;; + esac + export LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 + return 0 +} + +function force_dbus_restart() { + case $LINUX_KIND in + ubuntu) + sudo sed -i 's/^RefuseManualStart=.*$/RefuseManualStart=no/g' /lib/systemd/system/dbus.service + sudo systemctl daemon-reexec + sudo systemctl restart dbus.service + ;; + esac +} + +function check_dns_configuration() { + if [[ -r /etc/resolv.conf ]]; then + echo "Getting DNS using resolv.conf..." + THE_DNS=$(cat /etc/resolv.conf | grep -i '^nameserver' | head -n1 | cut -d ' ' -f2) || true + + if [[ -n ${THE_DNS} ]]; then + timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" || echo "Failure connecting to DNS ${THE_DNS}" + fi + fi + + if which systemd-resolve; then + echo "Getting DNS using systemd-resolve" + THE_DNS=$(systemd-resolve --status | grep "Current DNS" | awk '{print $4}') || true + if [[ -n ${THE_DNS} ]]; then + timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" || echo "Failure connecting to DNS ${THE_DNS}" + fi + fi + + if which resolvectl; then + echo "Getting DNS using resolvectl" + THE_DNS=$(resolvectl | grep "Current DNS" | awk '{print $4}') || true + if [[ -n ${THE_DNS} ]]; then + timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" || echo "Failure connecting to DNS ${THE_DNS}" + fi + fi + + timeout 2s bash -c "echo > /dev/tcp/www.google.com/80" && echo "Network OK" && return 0 || echo "Network not reachable" + return 1 +} + +# sets root password to the same as the one for SafeScale OperatorUsername (on distribution where root needs password), +# to be able to connect root on console when emergency shell arises. +# Root account not being usable remotely (and OperatorUsername being able to become root with sudo), this is not +# considered a security risk. Especially when set after SSH and Firewall configuration applied. +function configure_root_password_if_needed() { + case ${LINUX_KIND} in + redhat | rhel | centos | fedora) + echo "root:{{.Password}}" | chpasswd + ;; + esac +} + +function update_kernel_settings() { + cat > /etc/sysctl.d/20-safescale.conf <<- EOF + vm.max_map_count=262144 +EOF + case $LINUX_KIND in + ubuntu) systemctl restart systemd-sysctl ;; + *) sysctl -p ;; + esac +} + +function update_credentials() { + echo "{{.Username}}:{{.Password}}" | chpasswd + + dd if=/dev/urandom of=/home/{{.Username}}/.ssh/authorized_keys conv=notrunc bs=4096 count=8 + echo "{{.FinalPublicKey}}" > /home/{{.Username}}/.ssh/authorized_keys + dd if=/dev/urandom of=/home/{{.Username}}/.ssh/id_rsa conv=notrunc bs=4096 count=8 + echo "{{.FinalPrivateKey}}" > /home/{{.Username}}/.ssh/id_rsa + chmod 0700 /home/{{.Username}}/.ssh + chmod -R 0600 /home/{{.Username}}/.ssh/* +} + +function enable_at_daemon() { + case $LINUX_KIND in + redhat | rhel | centos | fedora) + if [ $VERSION_ID -eq 8 ]; then + crontab -l | { + cat + echo "@reboot sleep 30 && /usr/sbin/dhclient eth0" + } | crontab - + fi + sfRetry4 "service atd start" || true + sleep 4 + ;; + *) ;; + esac +} + +# for testing purposes +function unsafe_update_credentials() { + echo "{{.Username}}:safescale" | chpasswd + + dd if=/dev/urandom of=/home/{{.Username}}/.ssh/authorized_keys conv=notrunc bs=4096 count=8 + echo "{{.FinalPublicKey}}" > /home/{{.Username}}/.ssh/authorized_keys + dd if=/dev/urandom of=/home/{{.Username}}/.ssh/id_rsa conv=notrunc bs=4096 count=8 + echo "{{.FinalPrivateKey}}" > /home/{{.Username}}/.ssh/id_rsa + chmod 0700 /home/{{.Username}}/.ssh + chmod -R 0600 /home/{{.Username}}/.ssh/* +} + +function check_unsupported() { + case $LINUX_KIND in + centos) + {{- if or .PublicIP .IsGateway }} + if [ $(versionchk ${VERSION_ID}) -ge $(versionchk "8.0") ]; then + failure 211 "unsupported CentOS version for gateways: ${VERSION_ID}" + fi + {{- end }} + ;; + *) ;; + + esac +} + +# ---- Main +check_unsupported +check_providers + +{{- if .Debug }} +unsafe_update_credentials +{{- else }} +update_credentials +{{- end }} + +configure_locale + +{{- if .IsGateway }} +{{- else }} +# Without the route in place, we won't have working DNS either, so we set the route first +ensure_network_connectivity || echo "Network not ready yet: setting the route for machines other than the gateways" +{{- end }} + +# Now, we can check if DNS works, if it's a gateway it should work every time; if not it depends on the previous route working +check_dns_configuration && echo "DNS is ready" || echo "DNS NOT ready yet" +configure_dns || failure 213 "problem configuring DNS" +check_dns_configuration || { + configure_dns_fallback || failure 213 "problem configuring DNS, fallback didn't work either" + check_dns_configuration || { + failure 214 "DNS NOT ready after being configured" + } +} + +{{- if .IsGateway }} +ensure_network_connectivity || echo "Network not ready yet" +{{- end }} + +cr=-1 +ep=-1 +is_network_reachable && { + add_common_repos && cr=0 || echo "failure adding common repos, 1st try" + early_packages_update && ep=0 || echo "failure in early packages update, 1st try" +} + +identify_nics +configure_network || failure 215 "failure configuring network" +is_network_reachable || failure 215 "network is NOT ready after trying changing its DNS and configuration" + +[[ $cr -eq -1 ]] && { + add_common_repos || failure 215 "failure adding common repos, 2nd try" +} + +[[ $ep -eq -1 ]] && { + early_packages_update || failure 215 "failure in early packages update, 2nd try" +} + +install_packages || failure 215 "failure installing packages" +install_rclone || failure 216 "failure installing rclone" + +update_kernel_settings || failure 217 "failure updating kernel settings" + +force_dbus_restart || failure 218 "failure restarting dbus" + +systemctl restart sshd || failure 219 "failure restarting sshd" + +enable_at_daemon || failure 220 "failure starting at daemon" +# ---- EndMain + +echo -n "0,linux,${LINUX_KIND},${VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.netsec.done + +# !!! DON'T REMOVE !!! #insert_tag allows to add something just before exiting, +# but after the template has been realized (cf. libvirt Stack) +#insert_tag + +( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 +) || true + +set +x +exit 0 diff --git a/lib/backend/iaas/userdata/newscripts/userdata.sysfix.sh b/lib/backend/iaas/userdata/newscripts/userdata.sysfix.sh new file mode 100644 index 000000000..b5adfca56 --- /dev/null +++ b/lib/backend/iaas/userdata/newscripts/userdata.sysfix.sh @@ -0,0 +1,98 @@ +#!/bin/bash -x +# +# Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu +# +# 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. + +#{{.Revision}} +# Script customized for {{.ProviderName}} driver + +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{.Header}} + +last_error= + +function print_error() { + read -r line file <<< "$(caller)" + echo "An error occurred in line $line of file $file:" "{$(sed "${line}q;d" "$file")}" >&2 + {{.ExitOnError}} +} +trap print_error ERR + +function fail() { + MYIP="$(ip -br a | grep UP | awk '{print $3}') | head -n 1" + if [ $# -eq 1 ]; then + echo "PROVISIONING_ERROR: $1" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$1" > /opt/safescale/var/state/user_data.sysfix.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + elif [ $# -eq 2 -a $1 -ne 0 ]; then + echo "PROVISIONING_ERROR: $1, $2" + echo -n "$1,${LINUX_KIND},${VERSION_ID},$(hostname),$MYIP,$(date +%Y/%m/%d-%H:%M:%S),PROVISIONING_ERROR:$2" > /opt/safescale/var/state/user_data.sysfix.done + ( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 + ) || true + exit $1 + fi +} +export -f fail + +# Redirects outputs to /opt/safescale/var/log/user_data.sysfix.log +LOGFILE=/opt/safescale/var/log/user_data.sysfix.log + +### All output to one file and all output to the screen +{{- if .Debug }} +if [[ -e /home/{{.Username}}/tss ]]; then + exec > >(/home/{{.Username}}/tss | tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +else + exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +fi +{{- else }} +exec > >(tee -a ${LOGFILE} /opt/safescale/var/log/ss.log) 2>&1 +{{- end }} + +set -x + +date + +# Tricks BashLibrary's waitUserData to believe the current phase 'sysfix' is already done (otherwise will deadlock) +uptime > /opt/safescale/var/state/user_data.sysfix.done + +# Includes the BashLibrary +# shellcheck disable=SC1009 +# shellcheck disable=SC1073 +# shellcheck disable=SC1054 +{{ .reserved_BashLibrary }} +rm -f /opt/safescale/var/state/user_data.sysfix.done + +# ---- Main +# ---- EndMain + +echo -n "0,linux,${LINUX_KIND},${VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.sysfix.done + +( + sync + echo 3 > /proc/sys/vm/drop_caches + sleep 2 +) || true + +set +x +exit 0 diff --git a/lib/backend/iaas/userdata/oldscripts/userdata.phase1.sh b/lib/backend/iaas/userdata/oldscripts/userdata.phase1.sh deleted file mode 100644 index 14af8819d..000000000 --- a/lib/backend/iaas/userdata/oldscripts/userdata.phase1.sh +++ /dev/null @@ -1,504 +0,0 @@ -#!/bin/bash -# -# Copyright 2018-2020, CS Systemes d'Information, http://csgroup.eu -# -# 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. - -# Script customized for {{.ProviderName}} driver - -{{.Header}} - -function print_error() { - read line file <<< $(caller) - echo "An error occurred in line $line of file $file:" "{"$(sed "${line}q;d" "$file")"}" >&2 - echo -n "2,${LINUX_KIND},${FULL_VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.phase1.done -} -trap print_error ERR - -function fail() { - echo "PROVISIONING_ERROR: $1" - echo -n "$1,${LINUX_KIND},${FULL_VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.phase1.done - set +x - exit $1 -} - -mkdir -p /opt/safescale/etc /opt/safescale/bin &> /dev/null -mkdir -p /opt/safescale/var/log &> /dev/null -mkdir -p /opt/safescale/var/run /opt/safescale/var/state /opt/safescale/var/tmp &> /dev/null - -exec 1<&- -exec 2<&- -exec 1<> /opt/safescale/var/log/user_data.phase1.log -exec 2>&1 -set -x - -function sfApt() { - rc=-1 - DEBIAN_FRONTEND=noninteractive apt "$@" && rc=$? - [ $rc -eq -1 ] && return 1 - return $rc -} -export -f sfApt - -# try using dnf instead of yum if available -function sfYum() { - rc=-1 - if [[ -n $(which dnf) ]]; then - dnf "$@" && rc=$? - else - yum "$@" && rc=$? - fi - [ $rc -eq -1 ] && return 1 - return $rc -} -export -f sfYum - -# sfRetry command -# retries command until success, with sleep of seconds -function sfRetry() { - local timeout=$1 - local delay=$2 - shift 2 - local result - - { code=$(< /dev/stdin); } <<- EOF - fn() { - local r - local rc - while true; do - r=\$($*) - rc=\$? - [ \$rc -eq 0 ] && echo \$r && break - sleep $delay - done - return \$rc - } - export -f fn -EOF - eval "$code" - result=$(timeout $timeout bash -c -x fn) - rc=$? - unset fn - [ $rc -eq 0 ] && echo $result && return 0 - echo "sfRetry: timeout!" - return $rc -} -export -f sfRetry - -LINUX_KIND= -VERSION_ID= -FULL_VERSION_ID= -FULL_HOSTNAME= - -function sfAvail() { - rc=-1 - case $LINUX_KIND in - redhat | rhel | centos | fedora) - if [[ -n $(which dnf) ]]; then - dnf list available "$@" &> /dev/null && rc=$? - else - yum list available "$@" &> /dev/null && rc=$? - fi - ;; - debian | ubuntu) - DEBIAN_FRONTEND=noninteractive apt search "$@" &> /dev/null && rc=$? - ;; - esac - [ $rc -eq -1 ] && return 1 - return $rc -} -export -f sfAvail - -function sfDetectFacts() { - [[ -f /etc/os-release ]] && { - . /etc/os-release - LINUX_KIND=$ID - FULL_HOSTNAME=$VERSION_ID - FULL_VERSION_ID=$VERSION_ID - } || { - which lsb_release &> /dev/null && { - LINUX_KIND=$(lsb_release -is) - LINUX_KIND=${LINUX_KIND,,} - VERSION_ID=$(lsb_release -rs | cut -d. -f1) - FULL_VERSION_ID=$(lsb_release -rs) - } || { - [[ -f /etc/redhat-release ]] && { - LINUX_KIND=$(cat /etc/redhat-release | cut -d' ' -f1) - LINUX_KIND=${LINUX_KIND,,} - VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f3 | cut -d. -f1) - FULL_VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f3) - case $VERSION_ID in - '' | *[!0-9]*) - VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f4 | cut -d. -f1) - FULL_VERSION_ID=$(cat /etc/redhat-release | cut -d' ' -f4) - ;; - *) ;; - - esac - } - } - } -} -export -f sfDetectFacts - -# Detect facts -sfDetectFacts - -function create_user() { - echo "Creating user {{.User}}..." - useradd {{.User}} --home-dir /home/{{.User}} --shell /bin/bash --comment "" --create-home || true - echo "{{.User}}:{{.Password}}" | chpasswd - groupadd -r docker - usermod -aG docker {{.User}} - SUDOERS_FILE=/etc/sudoers.d/{{.User}} - [ ! -d "$(dirname $SUDOERS_FILE)" ] && SUDOERS_FILE=/etc/sudoers - cat >> $SUDOERS_FILE <<- 'EOF' -Defaults:{{.User}} !requiretty -{{.User}} ALL=(ALL) NOPASSWD:ALL -EOF - - mkdir /home/{{.User}}/.ssh - echo "{{.PublicKey}}" >> /home/{{.User}}/.ssh/authorized_keys - echo "{{.PrivateKey}}" > /home/{{.User}}/.ssh/id_rsa - chmod 0700 /home/{{.User}}/.ssh - chmod -R 0600 /home/{{.User}}/.ssh/* - - cat >> /home/{{.User}}/.bashrc <<- 'EOF' -pathremove() { - local IFS=':' - local NEWPATH="" - local DIR - local PATHVARIABLE=${2:-PATH} - for DIR in ${!PATHVARIABLE} ; do - if [ "$DIR" != "$1" ] ; then - NEWPATH=${NEWPATH:+$NEWPATH:}$DIR - fi - done - export $PATHVARIABLE="$NEWPATH" -} -pathprepend() { - pathremove $1 $2 - local PATHVARIABLE=${2:-PATH} - export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}" -} -pathappend() { - pathremove $1 $2 - local PATHVARIABLE=${2:-PATH} - export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1" -} -pathprepend $HOME/.local/bin -pathappend /opt/safescale/bin - -if [[ ! -v SAFESCALESSHUSER ]]; then - : -elif [[ -z "$SAFESCALESSHUSER" ]]; then - : -else - if [[ ! -v SAFESCALESSHPASS ]]; then - : - elif [[ -z "$SAFESCALESSHPASS" ]]; then - : - else - echo "$SAFESCALESSHPASS" | sudo -S -u $SAFESCALESSHUSER sudo -S -l 2>&1 | grep "incorrect" && exit - sudo -u $SAFESCALESSHUSER -i;exit - fi -fi -EOF - - chown -R {{.User}}:{{.User}} /opt/safescale - chmod -R 0640 /opt/safescale - find /opt/safescale -type d -exec chmod a+rx {} \; - chmod 1777 /opt/safescale/var/tmp - - chown -R {{.User}}:{{.User}} /home/{{.User}} - - for i in /home/{{.User}}/.hushlogin /home/{{.User}}/.cloud-warnings.skip; do - touch $i - chown root:{{.User}} $i - chmod ug+r-wx,o-rwx $i - done - - echo "done" -} - -# Follows the CentOS rules: -# - /etc/hostname contains short hostname -function put_hostname_in_hosts() { - FULL_HOSTNAME="{{ .HostName }}" - SHORT_HOSTNAME="${FULL_HOSTNAME%%.*}" - - echo "" >> /etc/hosts - echo "127.0.0.1 ${SHORT_HOSTNAME}" >> /etc/hosts - echo "${SHORT_HOSTNAME}" > /etc/hostname - hostname "${SHORT_HOSTNAME}" -} - -# Disable cloud-init automatic network configuration to be sure our configuration won't be replaced -function disable_cloudinit_network_autoconf() { - fname=/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg - mkdir -p $(dirname $fname) - echo "network: {config: disabled}" > $fname -} - -function disable_services() { - case $LINUX_KIND in - debian | ubuntu) - if [[ -n $(which systemctl) ]]; then - systemctl stop apt-daily.service &> /dev/null - systemctl kill --kill-who=all apt-daily.service &> /dev/null - fi - if [[ -n $(which system) ]]; then - which system && service stop apt-daily.service &> /dev/null - fi - ;; - esac -} - -function check_dns_configuration() { - if [[ -r /etc/resolv.conf ]]; then - echo "Getting DNS using resolv.conf..." - THE_DNS=$(cat /etc/resolv.conf | grep -i '^nameserver' | head -n1 | cut -d ' ' -f2) || true - - if [[ -n ${THE_DNS} ]]; then - timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" && return 0 || echo "Failure connecting to DNS ${THE_DNS}" - fi - fi - - if which systemd-resolve; then - echo "Getting DNS using systemd-resolve" - THE_DNS=$(systemd-resolve --status | grep "Current DNS" | awk '{print $4}') || true - if [[ -n ${THE_DNS} ]]; then - timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" && return 0 || echo "Failure connecting to DNS ${THE_DNS}" - fi - fi - - if which resolvectl; then - echo "Getting DNS using resolvectl" - THE_DNS=$(resolvectl | grep "Current DNS" | awk '{print $4}') || true - if [[ -n ${THE_DNS} ]]; then - timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" && return 0 || echo "Failure connecting to DNS ${THE_DNS}" - fi - fi - - timeout 2s bash -c "echo > /dev/tcp/www.google.com/80" && echo "Network OK" && return 0 || echo "Network not reachable" - return 1 -} - -function is_network_reachable() { - NETROUNDS=4 - REACHED=0 - TRIED=0 - - for i in $(seq ${NETROUNDS}); do - if which curl; then - TRIED=1 - curl -s -I www.google.com -m 4 | grep "200 OK" && REACHED=1 && break - fi - - if [[ ${TRIED} -eq 1 ]]; then - continue - fi - - if which wget; then - TRIED=1 - wget -T 4 -O /dev/null www.google.com &> /dev/null && REACHED=1 && break - fi - - if [[ ${TRIED} -eq 1 ]]; then - continue - fi - - ping -n -c1 -w4 -i1 www.google.com && REACHED=1 && break - done - - if [[ ${REACHED} -eq 0 ]]; then - echo "Unable to reach network" - return 1 - fi - - return 0 -} - -# If host isn't a gateway, we need to configure temporarily and manually gateway on private hosts to be able to update packages -function ensure_network_connectivity() { - op=-1 - is_network_reachable && op=$? || true - if [[ ${op} -eq 0 ]]; then - echo "ensure_network_connectivity started WITH network..." - else - echo "ensure_network_connectivity started WITHOUT network..." - fi - - {{- if .AddGateway }} - echo "This is NOT a gateway" - route del -net default &> /dev/null - route add -net default gw {{ .DefaultRouteIP }} - {{- else }} - echo "This IS a gateway" - : - {{- end}} - - op=-1 - is_network_reachable && op=$? || true - if [[ ${op} -eq 0 ]]; then - echo "ensure_network_connectivity finished WITH network..." - return 0 - else - echo "ensure_network_connectivity finished WITHOUT network..." - fi - - echo "" >> /etc/resolv.conf - echo "nameserver 1.1.1.1" >> /etc/resolv.conf - - op=-1 - is_network_reachable && op=$? || true - if [[ ${op} -eq 0 ]]; then - echo "ensure_network_connectivity finished WITH network AFTER putting custom DNS..." - return 0 - else - echo "ensure_network_connectivity finished WITHOUT network, not even custom DNS was enough..." - return 1 - fi -} - -function fail_fast_unsupported_distros() { - case $LINUX_KIND in - debian) - lsb_release -rs | grep "8." && { - echo "PROVISIONING_ERROR: Unsupported Linux distribution (docker) '$LINUX_KIND $(lsb_release -rs)'!" - fail 199 - } || true - ;; - ubuntu) - if [[ $(lsb_release -rs | cut -d. -f1) -le 17 ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -ne 16 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 199 - fi - fi - ;; - redhat | rhel | centos) - if [[ -n $(which lsb_release) ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -lt 7 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution (firewalld) '$LINUX_KIND $(lsb_release -rs)'!" - fail 199 - fi - else - if [[ $(echo ${VERSION_ID}) -lt 7 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution (firewalld) '$LINUX_KIND $VERSION_ID'!" - fail 199 - fi - fi - ;; - fedora) - if [[ -n $(which lsb_release) ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -lt 30 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 199 - fi - else - if [[ $(echo ${VERSION_ID}) -lt 30 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $VERSION_ID'!" - fail 199 - fi - fi - ;; - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 199 - ;; - esac -} - -function compatible_network() { - # Try installing network-scripts if available - case $LINUX_KIND in - redhat | rhel | centos | fedora) - op=-1 - sfAvail network-scripts && op=$? - if [[ ${op} -ne 0 ]]; then - return 0 - fi - sfRetry 3m 5 "sfYum install -q -y network-scripts" || true - ;; - *) ;; - esac -} - -function silent_compatible_network() { - # If network works, try to install network-scripts; if install fails, no need to log this because the gateway is not yet configured - op=-1 - is_network_reachable && op=$? || true - if [[ ${op} -eq 0 ]]; then - case $LINUX_KIND in - redhat | rhel | centos | fedora) - nop=-1 - sfAvail network-scripts && nop=$? - if [[ ${nop} -ne 0 ]]; then - return 0 - fi - sfRetry 3m 5 "sfYum install -q -y network-scripts &>/dev/null" || true - ;; - *) ;; - esac - fi -} - -function track_time() { - uptime - last -} - -# ---- Main - -export DEBIAN_FRONTEND=noninteractive - -PHASE_DONE=/opt/safescale/var/state/user_data.phase1.done -if [[ -f "$PHASE_DONE" ]]; then - echo "$PHASE_DONE already there." - set +x - exit 0 -fi - -PHASE_DONE=/opt/safescale/var/state/user_data.phase2.done -if [[ -f "$PHASE_DONE" ]]; then - echo "$PHASE_DONE already there." - set +x - exit 0 -fi - -put_hostname_in_hosts - -track_time - -check_dns_configuration || true - -disable_cloudinit_network_autoconf -disable_services -create_user - -silent_compatible_network - -ensure_network_connectivity || true - -compatible_network - -touch /etc/cloud/cloud-init.disabled - -fail_fast_unsupported_distros - -track_time - -echo -n "0,linux,${LINUX_KIND},${FULL_VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.phase1.done -set +x -exit 0 diff --git a/lib/backend/iaas/userdata/oldscripts/userdata.phase2.sh b/lib/backend/iaas/userdata/oldscripts/userdata.phase2.sh deleted file mode 100644 index bc3b35535..000000000 --- a/lib/backend/iaas/userdata/oldscripts/userdata.phase2.sh +++ /dev/null @@ -1,1930 +0,0 @@ -#!/bin/bash -# -# Copyright 2018-2020, CS Systemes d'Information, http://csgroup.eu -# -# 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. - -# Script customized for {{.ProviderName}} driver - -{{.Header}} - -function print_error() { - read line file <<< $(caller) - echo "An error occurred in line $line of file $file:" "{"$(sed "${line}q;d" "$file")"}" >&2 - {{.ExitOnError}} -} -trap print_error ERR - -function fail() { - echo "PROVISIONING_ERROR: $1" - echo -n "$1,${LINUX_KIND},${FULL_VERSION_ID},$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.phase2.done - - collect_installed_packages - - # For compatibility with previous user_data implementation (until v19.03.x)... - mkdir -p /var/tmp || true - ln -s ${SF_VARDIR}/state/user_data.phase2.done /var/tmp/user_data.done || true - - exit $1 -} - -# Redirects outputs to /opt/safescale/log/user_data.phase2.log -exec 1<&- -exec 2<&- -exec 1<> /opt/safescale/var/log/user_data.phase2.log -exec 2>&1 -set -x - -# Includes the BashLibrary -{{ .BashLibrary }} -sfDetectFacts - -function lkw_reset_fw() { - case $LINUX_KIND in - debian) - sfRetry 3m 5 "sfApt update &>/dev/null" || return 1 - if [[ $(lsb_release -rs | cut -d. -f1) -eq 10 ]]; then - codename=$(sfGetFact "linux_codename") - sfRetry 3m 5 "sfApt install -q -y -t ${codename}-backports iptables" || return 1 - sfRetry 3m 5 "sfApt install -q -y -t ${codename}-backports firewalld" || return 1 - else - sfRetry 3m 5 "sfApt install -q -y iptables" || return 1 - sfRetry 3m 5 "sfApt install -q -y firewalld" || return 1 - fi - - systemctl stop ufw - systemctl disable ufw - sfRetry 3m 5 "sfApt purge -q -y ufw &>/dev/null" || return 1 - ;; - ubuntu) - sfRetry 3m 5 "sfApt update &>/dev/null" || return 1 - sfRetry 3m 5 "sfApt install -q -y iptables" || return 1 - sfRetry 3m 5 "sfApt install -q -y firewalld" || return 1 - - systemctl stop ufw - systemctl disable ufw - sfRetry 3m 5 "sfApt purge -q -y ufw &>/dev/null" || return 1 - ;; - - redhat | rhel | centos | fedora) - # firewalld may not be installed - if ! systemctl is-active firewalld &> /dev/null; then - if ! systemctl status firewalld &> /dev/null; then - sfRetry 3m 5 "sfYum install -q -y firewalld" || return 1 - fi - fi - ;; - esac - - # Clear interfaces attached to zones - for zone in public trusted; do - for nic in $(firewall-offline-cmd --zone=$zone --list-interfaces || true); do - firewall-offline-cmd --zone=$zone --remove-interface=$nic &> /dev/null || true - done - done - - # Attach Internet interface or source IP to zone public if host is gateway - [ ! -z $PU_IF ] && { - # sfFirewallAdd --zone=public --add-interface=$PU_IF || return 1 - firewall-offline-cmd --zone=public --add-interface=$PU_IF || return 1 - } - {{- if or .PublicIP .IsGateway }} - [[ -z ${PU_IF} ]] && { - # sfFirewallAdd --zone=public --add-source=${PU_IP}/32 || return 1 - firewall-offline-cmd --zone=public --add-source=${PU_IP}/32 || return 1 - } - {{- end }} - # Attach LAN interfaces to zone trusted - [[ ! -z ${PR_IFs} ]] && { - for i in $PR_IFs; do - # sfFirewallAdd --zone=trusted --add-interface=$PR_IFs || return 1 - firewall-offline-cmd --zone=trusted --add-interface=$PR_IFs || return 1 - done - } - # Attach lo interface to zone trusted - # sfFirewallAdd --zone=trusted --add-interface=lo || return 1 - firewall-offline-cmd --zone=trusted --add-interface=lo || return 1 - - # Allow service ssh on public zone - op=-1 - SSHEC=$(firewall-offline-cmd --zone=public --add-service=ssh) && op=$? || true - if [[ $op -eq 11 ]] || [[ $op -eq 12 ]] || [[ $op -eq 16 ]]; then - op=0 - fi - - if [[ $(echo $SSHEC | grep "ALREADY_ENABLED") ]]; then - op=0 - fi - - if [[ $op -ne 0 ]]; then - return 1 - fi - - sfService enable firewalld &> /dev/null || return 1 - sfService start firewalld &> /dev/null || return 1 - - sop=-1 - firewall-cmd --runtime-to-permanent && sop=$? || sop=$? - if [[ $sop -ne 0 ]]; then - if [[ $sop -ne 31 ]]; then - return 1 - fi - fi - - # Save current fw settings as permanent - sfFirewallReload || return 1 - - firewall-cmd --list-all --zone=trusted > /tmp/firewall-trusted.cfg || true - firewall-cmd --list-all --zone=public > /tmp/firewall-public.cfg || true - - return 0 -} - -function reset_fw() { - case $LINUX_KIND in - debian) - sfRetry 3m 5 "sfApt update &>/dev/null" || return 1 - if [[ $(lsb_release -rs | cut -d. -f1) -eq 10 ]]; then - codename=$(sfGetFact "linux_codename") - sfRetry 3m 5 "sfApt install -q -y -t ${codename}-backports iptables" || return 1 - sfRetry 3m 5 "sfApt install -q -y -t ${codename}-backports firewalld" || return 1 - else - sfRetry 3m 5 "sfApt install -q -y iptables" || return 1 - sfRetry 3m 5 "sfApt install -q -y firewalld" || return 1 - fi - - systemctl stop ufw - systemctl disable ufw - sfRetry 3m 5 "sfApt purge -q -y ufw &>/dev/null" || return 1 - ;; - ubuntu) - sfRetry 3m 5 "sfApt update &>/dev/null" || return 1 - sfRetry 3m 5 "sfApt install -q -y iptables" || return 1 - sfRetry 3m 5 "sfApt install -q -y firewalld" || return 1 - - systemctl stop ufw - systemctl disable ufw - sfRetry 3m 5 "sfApt purge -q -y ufw &>/dev/null" || return 1 - ;; - - redhat | rhel | centos | fedora) - # firewalld may not be installed - if ! systemctl is-active firewalld &> /dev/null; then - if ! systemctl status firewalld &> /dev/null; then - sfRetry 3m 5 "sfYum install -q -y firewalld" || return 1 - fi - fi - ;; - esac - - FWCMD=firewall-offline-cmd # firewall-cmd --permanent "like" command - FWCMDnop=$FWCMD # firewall-cmd "like" command without --permanent - FWPERSIST="firewall-cmd --runtime-to-permanent" - FWRELOAD="systemctl enable firewalld" - if [[ $(sfGetFact "redhat_like") == 1 && $(sfGetFact "distrib_version") -ge 8 ]]; then - FWCMD=sfFirewallAdd - FWCMDnop=sfFirewall - FWRELOAD=sfFirewallReload - systemctl enable firewalld &> /dev/null || return 1 - systemctl start firewalld &> /dev/null || return 1 - else - systemctl disable firewalld &> /dev/null || return 1 - systemctl stop firewalld &> /dev/null || return 1 - fi - - # Clear interfaces attached to zones - for zone in public trusted; do - for nic in $($FWCMD --zone=$zone --list-interfaces); do - $FWCMD --zone=$zone --remove-interface=$nic &> /dev/null || return 1 - done - done - - # Attach Internet interface or source IP to zone public if host is gateway - [ ! -z $PU_IF ] && { - $FWCMD --zone=public --add-interface=$PU_IF || return 1 - } - {{- if or .PublicIP .IsGateway }} - [[ -z ${PU_IF} ]] && { - $FWCMD --zone=public --add-source=${PU_IP}/32 || return 1 - op=-1 - SSHEC=$($FWCMDnop --set-default-zone=public 2>&1) && op=$? || op=$? - if [[ $op -eq 11 ]] || [[ $op -eq 12 ]] || [[ $op -eq 16 ]]; then - op=0 - fi - - if [[ $(echo $SSHEC | grep "ALREADY") ]]; then - op=0 - fi - - if [[ $op -ne 0 ]]; then - return 1 - fi - } - {{- else }} - op=-1 - SSHEC=$($FWCMDnop --set-default-zone=trusted) && op=$? || op=$? - if [[ $op -eq 11 ]] || [[ $op -eq 12 ]] || [[ $op -eq 16 ]]; then - op=0 - fi - if [[ $(echo $SSHEC | grep "ALREADY") ]]; then - op=0 - fi - - if [[ $op -ne 0 ]]; then - return 1 - fi - {{- end }} - # Attach LAN interfaces to zone trusted - [[ ! -z ${PR_IFs} ]] && { - for i in $PR_IFs; do - $FWCMD --zone=trusted --add-interface=$PR_IFs || return 1 - done - } - # Attach lo interface to zone trusted - $FWCMD --zone=trusted --add-interface=lo || return 1 - - # Allow service ssh on public zone - op=-1 - SSHEC=$($FWCMD --zone=public --add-service=ssh 2>&1) && op=$? || op=$? - if [[ $op -eq 11 ]] || [[ $op -eq 12 ]] || [[ $op -eq 16 ]]; then - op=0 - fi - - if [[ $(echo $SSHEC | grep "ALREADY_ENABLED") ]]; then - op=0 - fi - - if [[ $op -ne 0 ]]; then - return 1 - fi - - sfService enable firewalld &> /dev/null || return 1 - sfService start firewalld &> /dev/null || return 1 - - sop=-1 - firewall-cmd --runtime-to-permanent && sop=$? || sop=$? - if [[ $sop -ne 0 ]]; then - if [[ $sop -ne 31 ]]; then - return 1 - fi - fi - - # Save current fw settings as permanent - $FWRELOAD || return 1 - - firewall-cmd --list-all --zone=trusted > /tmp/firewall-trusted.cfg || true - firewall-cmd --list-all --zone=public > /tmp/firewall-public.cfg || true - - return 0 -} - -NICS= -# PR_IPs= -PR_IFs= -PU_IP= -PU_IF= -i_PR_IF= -o_PR_IF= -NETMASK= -AWS= -GCP= -OUT= - -# Don't request dns name servers from DHCP server -# Don't update default route -function configure_dhclient() { - # kill any dhclient process already running - pkill dhclient || true - - [ -f /etc/dhcp/dhclient.conf ] && (sed -i -e 's/, domain-name-servers//g' /etc/dhcp/dhclient.conf || true) - - if [ -d /etc/dhcp/ ]; then - HOOK_FILE=/etc/dhcp/dhclient-enter-hooks - cat >> $HOOK_FILE <<- EOF -make_resolv_conf() { - : -} - -{{- if .AddGateway }} -unset new_routers -{{- end}} -EOF - chmod +x $HOOK_FILE - fi -} - -function is_ip_private() { - ip=$1 - ipv=$(sfIP2long $ip) - - {{ if .EmulatedPublicNet}} - r=$(sfCidr2iprange {{ .EmulatedPublicNet }}) - bv=$(sfIP2long $(cut -d- -f1 <<< $r)) - ev=$(sfIP2long $(cut -d- -f2 <<< $r)) - [ $ipv -ge $bv -a $ipv -le $ev ] && return 0 - {{- end }} - for r in "192.168.0.0-192.168.255.255" "172.16.0.0-172.31.255.255" "10.0.0.0-10.255.255.255"; do - bv=$(sfIP2long $(cut -d- -f1 <<< $r)) - ev=$(sfIP2long $(cut -d- -f2 <<< $r)) - [ $ipv -ge $bv -a $ipv -le $ev ] && return 0 - done - return 1 -} - -function identify_nics() { - NICS=$(for i in $(find /sys/devices -name net -print | grep -v virtual); do ls $i; done) - NICS=${NICS/[[:cntrl:]]/ } - - NETMASK=$(echo {{ .CIDR }} | cut -d/ -f2) - - for IF in ${NICS}; do - IP=$(ip a | grep $IF | grep inet | awk '{print $2}' | cut -d '/' -f1) || true - [[ ! -z $IP ]] && is_ip_private $IP && PR_IFs="$PR_IFs $IF" - done - PR_IFs=$(echo ${PR_IFs} | xargs) || true - PU_IF=$(ip route get 8.8.8.8 | awk -F"dev " 'NR==1{split($2,a," ");print a[1]}' 2> /dev/null) || true - PU_IP=$(ip a | grep ${PU_IF} | grep inet | awk '{print $2}' | cut -d '/' -f1) || true - if [[ ! -z ${PU_IP} ]]; then - if is_ip_private $PU_IP; then - PU_IF= - - NO404=$(curl -s -o /dev/null -w "%{http_code}" http://169.254.169.254/latest/meta-data/public-ipv4 2> /dev/null | grep 404) || true - if [[ -z $NO404 ]]; then - # Works with FlexibleEngine and potentially with AWS (not tested yet) - PU_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4 2> /dev/null) || true - [[ -z $PU_IP ]] && PU_IP=$(curl ipinfo.io/ip 2> /dev/null) - fi - fi - fi - [[ -z ${PR_IFs} ]] && PR_IFs=$(substring_diff "$NICS" "$PU_IF") - - # Keeps track of interfaces identified for future scripting use - echo "$PR_IFs" > ${SF_VARDIR}/state/private_nics - echo "$PU_IF" > ${SF_VARDIR}/state/public_nics - - if [[ ! -z ${PU_IP} ]]; then - if [[ -z ${PU_IF} ]]; then - if [[ -z ${NO404} ]]; then - echo "It seems AWS" - AWS=1 - else - AWS=0 - fi - fi - fi - - if [[ "{{.ProviderName}}" == "aws" ]]; then - echo "It actually IS AWS" - AWS=1 - else - echo "It is NOT AWS" - AWS=0 - fi - - if [[ "{{.ProviderName}}" == "gcp" ]]; then - echo "It actually IS GCP" - GCP=1 - else - echo "It is NOT GCP" - GCP=0 - fi - - if [[ "{{.ProviderName}}" == "outscale" ]]; then - echo "It actually IS Outscale" - OUT=1 - else - echo "It is NOT Outscale" - OUT=0 - fi - - echo "NICS identified: $NICS" - echo " private NIC(s): $PR_IFs" - echo " public NIC: $PU_IF" - echo -} - -function substring_diff() { - read -a l1 <<< $1 - read -a l2 <<< $2 - echo "${l1[@]}" "${l2[@]}" | tr ' ' '\n' | sort | uniq -u -} - -function collect_original_packages() { - case $LINUX_KIND in - debian | ubuntu) - dpkg-query -l > ${SF_VARDIR}/log/packages_installed_before.phase2.list - ;; - redhat | rhel | centos | fedora) - rpm -qa | sort > ${SF_VARDIR}/log/packages_installed_before.phase2.list - ;; - *) ;; - esac -} - -function ensure_curl_is_installed() { - case $LINUX_KIND in - ubuntu | debian) - if [[ -n $(which curl) ]]; then - return 0 - fi - DEBIAN_FRONTEND=noninteractive apt-get update || return 1 - DEBIAN_FRONTEND=noninteractive apt-get install -y curl || return 1 - ;; - redhat | rhel | centos | fedora) - if [[ -n $(which curl) ]]; then - return 0 - fi - sfRetry 3m 5 "sfYum install -y -q curl &>/dev/null" || return 1 - ;; - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND'!" - fail 216 - ;; - esac - - return 0 -} - -function collect_installed_packages() { - case $LINUX_KIND in - debian | ubuntu) - dpkg-query -l > ${SF_VARDIR}/log/packages_installed_after.phase2.list - ;; - redhat | rhel | centos | fedora) - rpm -qa | sort > ${SF_VARDIR}/log/packages_installed_after.phase2.list - ;; - *) ;; - - esac -} - -# If host isn't a gateway, we need to configure temporarily and manually gateway on private hosts to be able to update packages -function ensure_network_connectivity() { - op=1 - is_network_reachable && op=$? || true - if [[ $op -ne 0 ]]; then - echo "ensure_network_connectivity started WITHOUT network..." - else - echo "ensure_network_connectivity started WITH network..." - fi - - {{- if .AddGateway }} - if [[ -n $(which route) ]]; then - route del -net default &> /dev/null - route add -net default gw {{ .DefaultRouteIP }} - else - ip route del default - ip route add default via {{ .DefaultRouteIP }} - fi - {{- else }} - : - {{- end}} - - op=1 - is_network_reachable && op=$? || true - if [[ $op -ne 0 ]]; then - echo "ensure_network_connectivity finished WITHOUT network..." - else - echo "ensure_network_connectivity finished WITH network..." - fi - - if [[ $op -ne 0 ]]; then - return 1 - fi - - return 0 -} - -function configure_dns() { - if systemctl status systemd-resolved &> /dev/null; then - echo "Configuring dns with resolved" - configure_dns_systemd_resolved - elif systemctl status resolvconf &> /dev/null; then - echo "Configuring dns with resolvconf" - configure_dns_resolvconf - else - echo "Configuring dns legacy" - configure_dns_legacy - fi -} - -# adds entry in /etc/hosts corresponding to FQDN hostname with private IP -# Follows CentOS rules : -# - if there is a domain suffix in hostname, /etc/hosts contains FQDN as first entry and short hostname as second, after the IP -# - if there is no domain suffix in hostname, /etc/hosts contains short hostname as first entry, after the IP -function update_fqdn() { - cat /etc/hosts - - FULL_HOSTNAME="{{ .HostName }}" - SHORT_HOSTNAME="${FULL_HOSTNAME%%.*}" - - # FlexibleEngine seems to add an entry "not not" in /etc/hosts, replace it with - sed -i -nr '/^not /!p' /etc/hosts - - IF=${PR_IFs[0]} - if [[ -z ${IF} ]]; then - sed -i -nr '/^127.0.1.1/!p;$a127.0.1.1\t'"${SHORT_HOSTNAME}" /etc/hosts - else - IP=$(ip a | grep ${IF} | grep inet | awk '{print $2}' | cut -d '/' -f1) || true - sed -i -nr '/^127.0.1.1/!p' /etc/hosts - if [[ "${SHORT_HOSTNAME}" == "${FULL_HOSTNAME}" ]]; then - sed -i -nr '/^'"${IP}"'/!p;$a'"${IP}"'\t'"${SHORT_HOSTNAME}" /etc/hosts - else - sed -i -nr '/^'"${IP}"'/!p;$a'"${IP}"'\t'"${FULL_HOSTNAME} ${SHORT_HOSTNAME}" /etc/hosts - fi - fi - - cat /etc/hosts -} - -function install_route_if_needed() { - case $LINUX_KIND in - debian) - if [[ -z $(which route) ]]; then - sfRetry 3m 5 "sfApt install -y net-tools" || return 1 - fi - ;; - ubuntu) - if [[ -z $(which route) ]]; then - sfRetry 3m 5 "sfApt install -y net-tools" || return 1 - fi - ;; - redhat | rhel | centos) - if [[ -z $(which route) ]]; then - sfRetry 3m 5 "sfYum install -y net-tools" || return 1 - fi - ;; - fedora) - if [[ -z $(which route) ]]; then - sfRetry 3m 5 "sfYum install -y net-tools" || return 1 - fi - ;; - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - return 1 - ;; - esac - return 0 -} - -function allow_custom_env_ssh_vars() { - cat >> /etc/ssh/sshd_config <<- EOF -AcceptEnv SAFESCALESSHUSER -AcceptEnv SAFESCALESSHPASS -EOF - systemctl reload sshd -} - -function configure_network() { - case $LINUX_KIND in - debian | ubuntu) - if systemctl status systemd-networkd &> /dev/null; then - install_route_if_needed - configure_network_systemd_networkd - elif systemctl status networking &> /dev/null; then - install_route_if_needed - configure_network_debian - else - echo "PROVISIONING_ERROR: failed to determine how to configure network" - fail 192 - fi - ;; - - redhat | rhel | centos) - # Network configuration - if systemctl status systemd-networkd &> /dev/null; then - install_route_if_needed - configure_network_systemd_networkd - else - install_route_if_needed - configure_network_redhat - fi - ;; - - fedora) - install_route_if_needed - configure_network_redhat - ;; - - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND'!" - fail 193 - ;; - esac - - {{- if .IsGateway }} - configure_as_gateway || fail 194 - install_keepalived || fail 195 - {{- end }} - - update_fqdn - allow_custom_env_ssh_vars - - check_for_network || { - echo "PROVISIONING_ERROR: missing or incomplete network connectivity" - fail 196 - } -} - -# Configure network for Debian distribution -function configure_network_debian() { - echo "Configuring network (debian-like)..." - - local path=/etc/network/interfaces.d - mkdir -p ${path} - local cfg=${path}/50-cloud-init.cfg - rm -f ${cfg} - - for IF in ${NICS}; do - if [[ "$IF" == "$PU_IF" ]]; then - cat <<- EOF > ${path}/10-${IF}-public.cfg -auto ${IF} -iface ${IF} inet dhcp -EOF - else - cat <<- EOF > ${path}/11-${IF}-private.cfg -auto ${IF} -iface ${IF} inet dhcp -{{- if .AddGateway }} - up route add -net default gw {{ .DefaultRouteIP }} -{{- end}} -EOF - fi - done - - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed network cfg 0" - fail 197 - } - - configure_dhclient - - /sbin/dhclient || true - - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed network cfg 1" - fail 198 - } - - systemctl restart networking - - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed network cfg 2" - fail 199 - } - - reset_fw || { - echo "PROVISIONING_ERROR: failure setting firewall" - fail 200 - } - - echo "done" -} - -# Configure network using systemd-networkd -function configure_network_systemd_networkd() { - echo "Configuring network (using netplan and systemd-networkd)..." - - {{- if .IsGateway }} - ISGW=1 - {{- else}} - ISGW=0 - {{- end}} - - mkdir -p /etc/netplan - rm -f /etc/netplan/* - - # Recreate netplan configuration with last netplan version and more settings - for IF in ${NICS}; do - if [[ "$IF" == "$PU_IF" ]]; then - cat <<- EOF > /etc/netplan/10-${IF}-public.yaml -network: - version: 2 - renderer: networkd - - ethernets: - $IF: - dhcp4: true - dhcp6: false - critical: true - dhcp4-overrides: - use-dns: false - use-routes: true -EOF - else - cat <<- EOF > /etc/netplan/11-${IF}-private.yaml -network: - version: 2 - renderer: networkd - - ethernets: - ${IF}: - dhcp4: true - dhcp6: false - critical: true - dhcp4-overrides: - use-dns: false -{{- if .AddGateway }} - use-routes: false - routes: - - to: 0.0.0.0/0 - via: {{ .DefaultRouteIP }} - scope: global - on-link: true -{{- else }} - use-routes: true -{{- end}} -EOF - fi - done - - if [[ "{{.ProviderName}}" == "aws" ]]; then - echo "It actually IS AWS" - AWS=1 - else - echo "It is NOT AWS" - AWS=0 - fi - - if [[ $AWS -eq 1 ]]; then - if [[ $ISGW -eq 0 ]]; then - rm -f /etc/netplan/* - # Recreate netplan configuration with last netplan version and more settings - for IF in ${NICS}; do - if [[ "$IF" == "$PU_IF" ]]; then - cat <<- EOF > /etc/netplan/10-$IF-public.yaml -network: - version: 2 - renderer: networkd - - ethernets: - ${IF}: - dhcp4: true - dhcp6: false - critical: true - dhcp4-overrides: - use-dns: true - use-routes: true -EOF - else - cat <<- EOF > /etc/netplan/11-${IF}-private.yaml -network: - version: 2 - renderer: networkd - - ethernets: - ${IF}: - dhcp4: true - dhcp6: false - critical: true - dhcp4-overrides: - use-dns: true -{{- if .AddGateway }} - use-routes: true - routes: - - to: 0.0.0.0/0 - via: {{ .DefaultRouteIP }} - scope: global - on-link: true -{{- else }} - use-routes: true -{{- end}} -EOF - fi - done - fi - fi - - NETERR=0 - - if [[ ${AWS} -eq 1 ]]; then - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed networkd cfg 0" - NETERR=1 - } - fi - - # netplan generate - netplan generate && netplan apply || fail 198 - - if [[ ${AWS} -eq 1 ]]; then - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed networkd cfg 1" - NETERR=1 - } - fi - - configure_dhclient - - if [[ ${AWS} -eq 1 ]]; then - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed networkd cfg 2" - NETERR=1 - } - fi - - systemctl restart systemd-networkd - - if [[ ${AWS} -eq 1 ]]; then - echo "Looking for network..." - check_for_network || { - echo "PROVISIONING_ERROR: failed networkd cfg 3" - NETERR=1 - } - fi - - case $LINUX_KIND in - ubuntu) - if [[ $(lsb_release -rs | cut -d. -f1) -eq 20 ]]; then - if [[ ${NETERR} -eq 1 ]]; then - echo "Ignoring problems on AWS, ubuntu 20.04 LTS ..." - fi - else - if [[ ${NETERR} -eq 1 ]]; then - check_for_network || fail 196 - fi - fi - ;; - *) ;; - esac - - if [[ $GCP -eq 1 ]]; then - lkw_reset_fw || (echo "PROVISIONING_ERROR: failure setting firewall" && fail 199) - elif [[ $OUT -eq 1 ]]; then - lkw_reset_fw || (echo "PROVISIONING_ERROR: failure setting firewall" && fail 199) - else - reset_fw || (echo "PROVISIONING_ERROR: failure setting firewall" && fail 199) - fi - - echo "done" -} - -# Configure network for redhat alike distributions (rhel, centos, ...) -function configure_network_redhat() { - echo "Configuring network (redhat-like)..." - - if [[ -z $VERSION_ID || $VERSION_ID -lt 7 ]]; then - disable_svc() { - chkconfig $1 off - } - enable_svc() { - chkconfig $1 on - } - stop_svc() { - service $1 stop - } - restart_svc() { - service $1 restart - } - else - disable_svc() { - systemctl disable $1 - } - enable_svc() { - systemctl enable $1 - } - stop_svc() { - systemctl stop $1 - } - restart_svc() { - systemctl restart $1 - } - fi - - NMCLI=$(which nmcli 2> /dev/null) || true - if [[ ${AWS} -eq 1 && $(sfGetFact "distrib_version") -ge 8 ]]; then - configure_network_redhat_without_nmcli || { - echo "PROVISIONING_ERROR: failed to set network without NetworkManager" - fail 208 - } - elif [[ ${GCP} -eq 1 ]]; then - configure_network_redhat_without_nmcli || { - echo "PROVISIONING_ERROR: failed to set network without NetworkManager" - fail 208 - } - elif [[ ${OUT} -eq 1 ]]; then - configure_network_redhat_without_nmcli || { - echo "PROVISIONING_ERROR: failed to set network without NetworkManager" - fail 208 - } - else - NMCLI=$(which nmcli 2> /dev/null) || true - if [[ -z "${NMCLI}" ]]; then - configure_network_redhat_without_nmcli || { - echo "PROVISIONING_ERROR: failed to set network without NetworkManager" - fail 208 - } - else - configure_network_redhat_with_nmcli || { - echo "PROVISIONING_ERROR: failed to set network with NetworkManager" - fail 209 - } - fi - fi - - if [[ $GCP -eq 1 ]]; then - lkw_reset_fw || { - echo "PROVISIONING_ERROR: failure setting firewall" - fail 210 - } - elif [[ $OUT -eq 1 ]]; then - lkw_reset_fw || { - echo "PROVISIONING_ERROR: failure setting firewall" - fail 210 - } - else - reset_fw || { - echo "PROVISIONING_ERROR: failure setting firewall" - fail 210 - } - fi - - echo "done" -} - -# Configure network for redhat 6- alike distributions (rhel, centos, ...) -function configure_network_redhat_without_nmcli() { - echo "Configuring network (RedHat 6- alike)..." - - # We don't want NetworkManager if RedHat/CentOS < 7 - stop_svc NetworkManager &> /dev/null - disable_svc NetworkManager &> /dev/null - sfRetry 3m 5 "sfYum remove -y NetworkManager &>/dev/null" - echo "exclude=NetworkManager" >> /etc/yum.conf - - if which dnf; then - dnf install -q -y network-scripts || true - else - yum install -q -y network-scripts || true - fi - - # Configure all network interfaces in dhcp - for IF in $NICS; do - if [[ $IF != "lo" ]]; then - cat > /etc/sysconfig/network-scripts/ifcfg-${IF} <<- EOF -DEVICE=$IF -BOOTPROTO=dhcp -ONBOOT=yes -NM_CONTROLLED=no -EOF - {{- if .DNSServers }} - i=1 - {{- range .DNSServers }} - echo "DNS${i}={{ . }}" >> /etc/sysconfig/network-scripts/ifcfg-${IF} - i=$((i + 1)) - {{- end }} - {{- else }} - EXISTING_DNS=$(grep nameserver /etc/resolv.conf | awk '{print $2}') - if [[ -z ${EXISTING_DNS} ]]; then - echo "DNS1=1.1.1.1" >> /etc/sysconfig/network-scripts/ifcfg-${IF} - else - echo "DNS1=$EXISTING_DNS" >> /etc/sysconfig/network-scripts/ifcfg-${IF} - fi - {{- end }} - - {{- if .AddGateway }} - echo "GATEWAY={{ .DefaultRouteIP }}" >> /etc/sysconfig/network-scripts/ifcfg-${IF} - {{- end}} - fi - done - - configure_dhclient - - {{- if .AddGateway }} - echo "GATEWAY={{ .DefaultRouteIP }}" > /etc/sysconfig/network - {{- end }} - - enable_svc network - restart_svc network - - return 0 -} - -# Configure network for redhat7+ alike distributions (rhel, centos, ...) -function configure_network_redhat_with_nmcli() { - echo "Configuring network (RedHat 7+ alike with Network Manager)..." - - # Do some cleanup inside /etc/sysconfig/network-scripts - CONNECTIONS=$(nmcli -f "NAME,DEVICE" -c no -t con) - for i in $CONNECTIONS; do - NAME=$(echo ${i} | cut -d':' -f1) - DEVICE=$(echo ${i} | cut -d':' -f2) - [[ -z "${DEVICE}" ]] && mv /etc/sysconfig/network-scripts/ifcfg-${NAME} /etc/sysconfig/network-scripts/disabled.ifcfg-${NAME} - done - - # Configure all network interfaces - for IF in $(nmcli -f 'DEVICE' -c no -t dev); do - # We change nothing on device lo - [[ "${IF}" == "lo" ]] && continue - - DEV_IP=$(nmcli -g IP4.ADDRESS dev show "${IF}") - # We change nothing on device with Public IP Address - is_ip_private $(echo ${DEV_IP} | cut -d/ -f1) || continue - - CONN=$(nmcli -c no -t -f "NAME,DEVICE" con | grep ${IF} | cut -d: -f1) - nmcli con mod "${CONN}" connection.autoconnect yes || return 1 - # Assume the IP Address cannot change even if the first time it is affected by DHCP - nmcli con mod "${CONN}" ipv4.addresses "${DEV_IP}" || return 1 - nmcli con mod "${CONN}" ipv4.method manual || return 1 - {{- if .DNSServers }} - {{- range .DNSServers }} - nmcli con mod "${CONN}" +ipv4.dns {{ . }} || return 1 - {{- end }} - {{- else }} - EXISTING_DNS=$(grep nameserver /etc/resolv.conf | awk '{print $2}') - if [[ -z ${EXISTING_DNS} ]]; then - ${NMCLI} con mod "${CONN}" +ipv4.dns 1.1.1.1 || return 1 - else - ${NMCLI} con mod "${CONN}" +ipv4.dns ${EXISTING_DNS} || return 1 - fi - {{- end }} - - {{- if .AddGateway }} - nmcli con mod "${CONN}" ipv4.gateway "{{ .DefaultRouteIP }}" - {{- end}} - - done - - nmcli con reload - - configure_dhclient || return 1 - - enable_svc NetworkManager - restart_svc NetworkManager - - return 0 -} - -function check_for_ip() { - case $LINUX_KIND in - debian) - lsb_release -rs | grep "8." && return 0 - ;; - *) ;; - esac - - allip=$(ip -f inet -o addr show) - ip=$(ip -f inet -o addr show $1 | cut -d' ' -f7 | cut -d' ' -f1) - - case $LINUX_KIND in - ubuntu) - if [[ $(lsb_release -rs | cut -d. -f1) -eq 16 ]]; then - if [[ $(echo $allip | grep $1) == "" ]]; then - echo "Ubuntu 16.04 is expected to fail this test..." - return 0 - fi - fi - ;; - debian) - if [[ $(lsb_release -rs | cut -d. -f1) -eq 8 ]]; then - if [[ $(echo $allip | grep $1) == "" ]]; then - echo "Debian 8 is expected to fail this test..." - return 0 - fi - fi - ;; - *) ;; - esac - - [[ -z "$ip" ]] && echo "Failure checking for ip '$ip' when evaluating '$1'" && return 1 - return 0 -} - -# Checks network is set correctly -# - DNS and routes (by pinging a FQDN) -# - IP address on "physical" interfaces -function check_for_network() { - is_network_reachable || return 1 - - [[ ! -z "$PU_IF" ]] && { - check_for_ip ${PU_IF} || return 1 - } - for i in ${PR_IFs}; do - check_for_ip ${i} || return 1 - done - - return 0 -} - -function configure_as_gateway() { - echo "Configuring host as gateway..." - - if [[ ! -z $PR_IFs ]]; then - # Enable forwarding - for i in /etc/sysctl.d/* /etc/sysctl.conf; do - grep -v "net.ipv4.ip_forward=" ${i} > ${i}.new - mv -f ${i}.new ${i} - done - cat > /etc/sysctl.d/21-gateway.conf <<- EOF -net.ipv4.ip_forward=1 -net.ipv4.ip_nonlocal_bind=1 -EOF - case $LINUX_KIND in - ubuntu) systemctl restart systemd-sysctl ;; - *) sysctl -p ;; - esac - fi - - if [[ ! -z $PU_IF ]]; then - # Dedicated public interface available... - - # Allows ping - firewall-offline-cmd --direct --add-rule ipv4 filter INPUT 0 -p icmp -m icmp --icmp-type 8 -s 0.0.0.0/0 -d 0.0.0.0/0 -j ACCEPT - # Allows masquerading on public zone - firewall-offline-cmd --zone=public --add-masquerade - fi - # Enables masquerading on trusted zone (mainly for docker networks) - firewall-offline-cmd --zone=trusted --add-masquerade - - # Allows default services on public zone - firewall-offline-cmd --zone=public --add-service=ssh 2> /dev/null - # Applies fw rules - # sfFirewallReload - - sed -i '/^\#*AllowTcpForwarding / s/^.*$/AllowTcpForwarding yes/' /etc/ssh/sshd_config || sfFail 196 - sed -i '/^.*PasswordAuthentication / s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config || sfFail 197 - systemctl restart sshd || sfFail 197 - - echo "done" -} - -function install_keepalived() { - # Try installing network-scripts if available - case $LINUX_KIND in - redhat | rhel | centos | fedora) - sfRetry 3m 5 "sfYum install -q -y network-scripts" || true - ;; - *) ;; - - esac - - case $LINUX_KIND in - ubuntu | debian) - sfRetry 3m 5 "sfApt update" || return 1 - sfRetry 3m 5 "sfApt -y install keepalived" || return 1 - ;; - - redhat | rhel | centos | fedora) - sfRetry 3m 5 "sfYum install -q -y keepalived" || return 1 - ;; - *) - echo "Unsupported Linux distribution '$LINUX_KIND'!" - return 1 - ;; - esac - - NETMASK=$(echo {{ .CIDR }} | cut -d/ -f2) - - cat > /etc/keepalived/keepalived.conf <<- EOF -vrrp_instance vrrp_group_gws_internal { - state {{ if eq .IsPrimaryGateway true }}MASTER{{ else }}BACKUP{{ end }} - interface ${PR_IFs[0]} - virtual_router_id 1 - priority {{ if eq .IsPrimaryGateway true }}151{{ else }}100{{ end }} - nopreempt - advert_int 2 - authentication { - auth_type PASS - auth_pass {{ .GatewayHAKeepalivedPassword }} - } -{{ if eq .IsPrimaryGateway true }} - # Unicast specific option, this is the IP of the interface keepalived listens on - unicast_src_ip {{ .PrimaryGatewayPrivateIP }} - # Unicast specific option, this is the IP of the peer instance - unicast_peer { - {{ .SecondaryGatewayPrivateIP }} - } -{{ else }} - unicast_src_ip {{ .SecondaryGatewayPrivateIP }} - unicast_peer { - {{ .PrimaryGatewayPrivateIP }} - } -{{ end }} - virtual_ipaddress { - {{ .PrivateVIP }}/${NETMASK} - } -} - -# vrrp_instance vrrp_group_gws_external { -# state BACKUP -# interface ${PU_IF} -# virtual_router_id 2 -# priority {{ if eq .IsPrimaryGateway true }}151{{ else }}100{{ end }} -# nopreempt -# advert_int 2 -# authentication { -# auth_type PASS -# auth_pass password -# } -# virtual_ipaddress { -# {{ .PublicVIP }}/${NETMASK} -# } -# } -EOF - - if [ "$(sfGetFact "use_systemd")" = "1" ]; then - # Use systemd to ensure keepalived is restarted if network is restarted - # (otherwise, keepalived is in undetermined state) - mkdir -p /etc/systemd/system/keepalived.service.d - if [[ $(sfGetFact "redhat_like") -eq 1 ]]; then - cat > /etc/systemd/system/keepalived.service.d/override.conf << EOF -[Unit] -Requires=network.service -PartOf=network.service -EOF - else - cat > /etc/systemd/system/keepalived.service.d/override.conf << EOF -[Unit] -Requires=systemd-networkd.service -PartOf=systemd-networkd.service -EOF - fi - systemctl daemon-reload - fi - - sfService enable keepalived || return 1 - - op=-1 - msg=$(sfService restart keepalived 2>&1) && op=$? || true - - kop=-1 - echo $msg | grep "Unit network.service not found" && kop=$? || true - - if [[ op -ne 0 ]]; then - if [[ kop -eq 0 ]]; then - case $LINUX_KIND in - redhat | rhel | centos | fedora) - sfRetry 3m 5 "sfYum install -q -y network-scripts" || return 1 - ;; - *) ;; - - esac - fi - fi - - sfService restart keepalived || return 1 - return 0 -} - -function configure_dns_legacy() { - echo "Configuring /etc/resolv.conf..." - cp /etc/resolv.conf /etc/resolv.conf.bak - - rm -f /etc/resolv.conf - {{- if .DNSServers }} - if [[ -e /etc/dhcp/dhclient.conf ]]; then - dnsservers= - for i in {{range .DNSServers}} {{end}}; do - [ ! -z $dnsservers ] && dnsservers="$dnsservers, " - done - [ ! -z $dnsservers ] && echo "prepend domain-name-servers $dnsservers;" >> /etc/dhcp/dhclient.conf - else - echo "dhclient.conf not modified" - fi - {{- else }} - if [[ -e /etc/dhcp/dhclient.conf ]]; then - echo "prepend domain-name-servers 1.1.1.1;" >> /etc/dhcp/dhclient.conf - else - echo "/etc/dhcp/dhclient.conf not modified" - fi - {{- end }} - cat <<- 'EOF' > /etc/resolv.conf -{{- if .DNSServers }} - {{- range .DNSServers }} -nameserver {{ . }} - {{- end }} -{{- else }} -nameserver 1.1.1.1 -{{- end }} -EOF - - # VPL: need to determine if it's a good idea to update resolv.conf with search domain... - # The DNS servers will not be able to resolve hosts from the DNSDOMAIN by themselves, there is a need for an internal DNS server - # DNSDOMAIN="$(hostname -d)" - # if [[ ! -z "$DNSDOMAIN" ]]; then - #cat <<-EOF >>/etc/resolv.conf - #search $DNSDOMAIN - #EOF - # fi - - cp /etc/resolv.conf /etc/resolv.conf.edited - touch /etc/resolv.conf && sleep 2 || true - - op=-1 - is_network_reachable && op=$? || true - - [[ ${op} -ne 0 ]] && echo "changing dns wasn't a good idea..." && cp /etc/resolv.conf.bak /etc/resolv.conf && touch /etc/resolv.conf && sleep 2 || echo "dns change OK..." - - echo "done" -} - -function configure_dns_resolvconf() { - echo "Configuring resolvconf..." - - EXISTING_DNS=$(grep nameserver /etc/resolv.conf | awk '{print $2}') - - cat <<- 'EOF' > /etc/resolvconf/resolv.conf.d/head -{{- if .DNSServers }} - {{- range .DNSServers }} -nameserver {{ . }} - {{- end }} -{{- else }} -nameserver 1.1.1.1 -{{- end }} -EOF - - # VPL: need to determine if it's a good idea to update resolv.conf with search domain... - # The DNS servers will not be able to resolve hosts from the DNSDOMAIN by themselves, there is a need for an internal DNS server - # DNSDOMAIN="$(hostname -d)" - # if [[ ! -z "$DNSDOMAIN" ]]; then - # cat <<-EOF >>/etc/resolvconf/resolv.conf.d/head - #search $DNSDOMAIN - #EOF - # fi - - resolvconf -u - echo "done" -} - -function configure_dns_systemd_resolved() { - echo "Configuring systemd-resolved..." - - {{- if not .DefaultRouteIP }} - rm -f /etc/resolv.conf - ln -s /run/systemd/resolve/resolv.conf /etc - {{- end }} - - cat <<- 'EOF' > /etc/systemd/resolved.conf -[Resolve] -{{- if .DNSServers }} -DNS={{ range .DNSServers }}{{ . }} {{ end }} -{{- else }} -DNS=1.1.1.1 -{{- end}} -Cache=yes -DNSStubListener=yes -EOF - - # VPL: need to determine if it's a good idea to update resolv.conf with search domain... - # The DNS servers will not be able to resolve hosts from the DNSDOMAIN by themselves, there is a need for an internal DNS server - # DNSDOMAIN=$(hostname -d) - # if [[ ! -z "$DNSDOMAIN" ]]; then - # cat <<-EOF >>/etc/systemd/resolved.conf - #Domains=$DNSDOMAIN - #EOF - # fi - - systemctl restart systemd-resolved - echo "done" -} - -function install_drivers_nvidia() { - case $LINUX_KIND in - ubuntu) - sfFinishPreviousInstall - add-apt-repository -y ppa:graphics-drivers &> /dev/null - sfRetry 3m 5 "sfApt update" || fail 201 - sfRetry 3m 5 "sfApt -y install nvidia-410 &>/dev/null" || { - sfRetry 3m 5 "sfApt -y install nvidia-driver-410 &>/dev/null" || fail 201 - } - ;; - - debian) - if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then - echo -e "blacklist nouveau\nblacklist lbm-nouveau\noptions nouveau modeset=0\nalias nouveau off\nalias lbm-nouveau off" >> /etc/modprobe.d/blacklist-nouveau.conf - rmmod nouveau - fi - sfRetry 3m 5 "sfApt update &>/dev/null" - sfRetry 3m 5 "sfApt install -y dkms build-essential linux-headers-$(uname -r) gcc make &>/dev/null" || fail 202 - dpkg --add-architecture i386 &> /dev/null - sfRetry 3m 5 "sfApt update &>/dev/null" - sfRetry 3m 5 "sfApt install -y lib32z1 lib32ncurses5 &>/dev/null" || fail 203 - wget http://us.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run &> /dev/null || fail 204 - bash NVIDIA-Linux-x86_64-410.78.run -s || fail 205 - ;; - - redhat | rhel | centos | fedora) - if [ ! -f /etc/modprobe.d/blacklist-nouveau.conf ]; then - echo -e "blacklist nouveau\noptions nouveau modeset=0" >> /etc/modprobe.d/blacklist-nouveau.conf - dracut --force - rmmod nouveau - fi - sfRetry 3m 5 "sfYum -y -q install kernel-devel.$(uname -i) kernel-headers.$(uname -i) gcc make &>/dev/null" || fail 206 - wget http://us.download.nvidia.com/XFree86/Linux-x86_64/410.78/NVIDIA-Linux-x86_64-410.78.run || fail 207 - # if there is a version mismatch between kernel sources and running kernel, building the driver would require 2 reboots to get it done, right now this is unsupported - if [ $(uname -r) == $(sfYum list installed | grep kernel-headers | awk {'print $2'}).$(uname -i) ]; then - bash NVIDIA-Linux-x86_64-410.78.run -s || fail 208 - fi - rm -f NVIDIA-Linux-x86_64-410.78.run - ;; - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND'!" - fail 209 - ;; - esac -} - -function early_packages_update() { - is_network_reachable || return 1 - - # Ensure IPv4 will be used before IPv6 when resolving hosts (the latter shouldn't work regarding the network configuration we set) - cat > /etc/gai.conf <<- EOF -precedence ::ffff:0:0/96 100 -scopev4 ::ffff:169.254.0.0/112 2 -scopev4 ::ffff:127.0.0.0/104 2 -scopev4 ::ffff:0.0.0.0/96 14 -EOF - - case $LINUX_KIND in - debian) - # Disable interactive installations - export DEBIAN_FRONTEND=noninteractive - # # Force use of IPv4 addresses when installing packages - # echo 'Acquire::ForceIPv4 "true";' >/etc/apt/apt.conf.d/99force-ipv4 - - sfRetry 3m 5 "sfApt update" - # Force update of systemd, pciutils - sfRetry 3m 5 "sfApt install -q -y systemd pciutils" || fail 210 - # systemd, if updated, is restarted, so we may need to ensure again network connectivity - ensure_network_connectivity - is_network_reachable - ;; - - ubuntu) - # Disable interactive installations - export DEBIAN_FRONTEND=noninteractive - # # Force use of IPv4 addresses when installing packages - # echo 'Acquire::ForceIPv4 "true";' >/etc/apt/apt.conf.d/99force-ipv4 - - sfRetry 3m 5 "sfApt update" - # Force update of systemd, pciutils and netplan - if dpkg --compare-versions $(sfGetFact "distrib_version") ge 17.10; then - sfRetry 3m 5 "sfApt install -y systemd pciutils netplan.io" || fail 211 - else - sfRetry 3m 5 "sfApt install -y systemd pciutils" || fail 212 - fi - # systemd, if updated, is restarted, so we may need to ensure again network connectivity - ensure_network_connectivity - is_network_reachable - - # # Security updates ... - # sfApt update &>/dev/null && sfApt install -qy unattended-upgrades && unattended-upgrades -v - ;; - - redhat | rhel | centos | fedora) - # # Force use of IPv4 addresses when installing packages - # echo "ip_resolve=4" >>/etc/yum.conf - - # Force update of systemd and pciutils - sfRetry 3m 5 "sfYum install -q -y pciutils yum-utils" || fail 213 - - if [[ "{{.ProviderName}}" == "huaweicloud" ]]; then - if [ "$(lscpu --all --parse=CORE,SOCKET | grep -Ev "^#" | sort -u | wc -l)" = "1" ]; then - echo "Skipping upgrade of systemd when only 1 core is available" - else - # systemd, if updated, is restarted, so we may need to ensure again network connectivity - if which dnf; then - op=-1 - msg=$(sfRetry 3m 5 "sfYum install -q -y systemd 2>&1") && op=$? || true - echo $msg | grep "Nothing to do" && return - [ $op -ne 0 ] && sfFail 213 - else - op=-1 - msg=$(sfRetry 3m 5 "sfYum install -q -y systemd 2>&1") && op=$? || true - echo $msg | grep "Nothing to do" && return - [ $op -ne 0 ] && sfFail 213 - fi - ensure_network_connectivity - is_network_reachable - fi - else - if which dnf; then - op=-1 - msg=$(sfRetry 3m 5 "sfYum install -q -y systemd 2>&1") && op=$? || true - echo $msg | grep "Nothing to do" && return - [ $op -ne 0 ] && sfFail 213 - else - op=-1 - msg=$(sfRetry 3m 5 "sfYum install -q -y systemd 2>&1") && op=$? || true - echo $msg | grep "Nothing to do" && return - [ $op -ne 0 ] && sfFail 213 - fi - ensure_network_connectivity - is_network_reachable - fi - - # # install security updates - # yum install -y yum-plugin-security yum-plugin-changelog && yum update -y --security - ;; - esac - sfProbeGPU -} - -function install_packages() { - case $LINUX_KIND in - ubuntu | debian) - sfRetry 4m 5 "sfApt install -y -qq jq zip time &>/dev/null" || fail 214 - ;; - redhat | rhel | centos) - sfRetry 4m 5 "sfYum install --enablerepo=epel -y -q wget jq time zip &>/dev/null" || fail 215 - ;; - fedora) - sfRetry 4m 5 "sfYum install -y -q wget jq time zip &>/dev/null" || fail 215 - ;; - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND'!" - fail 216 - ;; - esac -} - -function add_backport_repos() { - case $LINUX_KIND in - debian) - sfFinishPreviousInstall - codename=$(sfGetFact "linux_codename") - echo "deb http://deb.debian.org/debian ${codename}-backports main" >> /etc/apt/sources.list - ;; - esac -} - -function add_common_repos() { - case $LINUX_KIND in - ubuntu) - sfFinishPreviousInstall - add-apt-repository universe -y || fail 217 - codename=$(sfGetFact "linux_codename") - echo "deb http://archive.ubuntu.com/ubuntu/ ${codename}-proposed main" > /etc/apt/sources.list.d/${codename}-proposed.list - ;; - redhat | rhel | centos) - if which dnf; then - # Install EPEL repo ... - sfRetry 3m 5 "dnf install -y epel-release" || fail 217 - sfRetry 3m 5 "dnf makecache -y" || fail 218 - # ... but don't enable it by default - sfRetry 3m 5 "sfYum config-manager --set-disabled epel &>/dev/null" || true - else - # Install EPEL repo ... - sfRetry 3m 5 "yum install -y epel-release" || fail 217 - sfRetry 3m 5 "yum makecache" || fail 218 - # ... but don't enable it by default - yum-config-manager --disablerepo=epel &> /dev/null || true - fi - ;; - fedora) - sfRetry 3m 5 "dnf makecache -y" || fail 218 - ;; - esac -} - -function configure_locale() { - case $LINUX_KIND in - ubuntu | debian) - locale-gen en_US.UTF-8 - ;; - esac - export LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 -} - -function force_dbus_restart() { - case $LINUX_KIND in - ubuntu) - sudo sed -i 's/^RefuseManualStart=.*$/RefuseManualStart=no/g' /lib/systemd/system/dbus.service - sudo systemctl daemon-reexec - sudo systemctl restart dbus.service - ;; - esac -} - -# sets root password to the same as the one for SafeScale OperatorUsername (on distribution where root needs password), -# to be able to connect root on console when emergency shell arises. -# Root account not being usable remotely (and OperatorUsername being able to become root with sudo), this is not -# considered a security risk. Especially when set after SSH and Firewall configuration applied. -function configure_root_password_if_needed() { - case ${LINUX_KIND} in - redhat | rhel | centos | fedora) - echo "root:{{.Password}}" | chpasswd - ;; - esac -} - -function update_kernel_settings() { - cat > /etc/sysctl.d/20-safescale.conf <<- EOF -vm.max_map_count=262144 -EOF - case $LINUX_KIND in - ubuntu) systemctl restart systemd-sysctl ;; - *) sysctl -p ;; - esac -} - -function use_cgroups_v1_if_needed() { - case $LINUX_KIND in - fedora) - if [[ -n $(which lsb_release) ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -gt 30 ]]; then - sfRetry 3m 5 "sfYum install -y grubby" || return 1 - grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0" || return 1 - fi - else - if [[ $(echo ${VERSION_ID}) -gt 30 ]]; then - sfRetry 3m 5 "sfYum install -y grubby" || return 1 - grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0" || return 1 - fi - fi - ;; - esac - - return 0 -} - -function fail_fast_unsupported_distros() { - case $LINUX_KIND in - debian) - lsb_release -rs | grep "8." && { - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 201 - } || true - ;; - ubuntu) - if [[ $(lsb_release -rs | cut -d. -f1) -le 17 ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -ne 16 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 201 - fi - fi - ;; - redhat | rhel | centos) - if [[ -n $(which lsb_release) ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -lt 7 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 201 - fi - else - if [[ $(echo ${VERSION_ID}) -lt 7 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $VERSION_ID'!" - fail 201 - fi - fi - ;; - fedora) - if [[ -n $(which lsb_release) ]]; then - if [[ $(lsb_release -rs | cut -d. -f1) -lt 30 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 201 - fi - else - if [[ $(echo ${VERSION_ID}) -lt 30 ]]; then - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $VERSION_ID'!" - fail 201 - fi - fi - ;; - *) - echo "PROVISIONING_ERROR: Unsupported Linux distribution '$LINUX_KIND $(lsb_release -rs)'!" - fail 201 - ;; - esac -} - -function check_network_reachable() { - NETROUNDS=2 - REACHED=0 - TRIED=0 - - for i in $(seq ${NETROUNDS}); do - if which curl; then - TRIED=1 - curl -s -I www.google.com -m 4 | grep "200 OK" && REACHED=1 && break - fi - - if [[ ${TRIED} -eq 1 ]]; then - break - fi - - if which wget; then - TRIED=1 - wget -T 4 -O /dev/null www.google.com &> /dev/null && REACHED=1 && break - fi - - if [[ ${TRIED} -eq 1 ]]; then - break - fi - - ping -n -c1 -w4 -i1 www.google.com && REACHED=1 && break - done - - if [[ ${REACHED} -eq 0 ]]; then - echo "PROVISIONING_ERROR: Unable to reach network" - fail 221 - fi - - return 0 -} - -function check_dns_configuration() { - if [[ -r /etc/resolv.conf ]]; then - echo "Getting DNS using resolv.conf..." - THE_DNS=$(cat /etc/resolv.conf | grep -i '^nameserver' | head -n1 | cut -d ' ' -f2) || true - - if [[ -n ${THE_DNS} ]]; then - timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" || echo "Failure connecting to DNS ${THE_DNS}" - fi - fi - - if which systemd-resolve; then - echo "Getting DNS using systemd-resolve" - THE_DNS=$(systemd-resolve --status | grep "Current DNS" | awk '{print $4}') || true - if [[ -n ${THE_DNS} ]]; then - timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" || echo "Failure connecting to DNS ${THE_DNS}" - fi - fi - - if which resolvectl; then - echo "Getting DNS using resolvectl" - THE_DNS=$(resolvectl | grep "Current DNS" | awk '{print $4}') || true - if [[ -n ${THE_DNS} ]]; then - timeout 2s bash -c "echo > /dev/tcp/${THE_DNS}/53" && echo "DNS ${THE_DNS} up and running" || echo "Failure connecting to DNS ${THE_DNS}" - fi - fi - - timeout 2s bash -c "echo > /dev/tcp/www.google.com/80" && echo "Network OK" && return 0 || echo "Network not reachable" - return 1 -} - -function is_network_reachable() { - NETROUNDS=6 - REACHED=0 - TRIED=0 - - for i in $(seq ${NETROUNDS}); do - if which curl; then - TRIED=1 - curl -s -I www.google.com -m 4 | grep "200 OK" && REACHED=1 && break - fi - - if [[ ${TRIED} -eq 1 ]]; then - continue - fi - - if which wget; then - TRIED=1 - wget -T 4 -O /dev/null www.google.com &> /dev/null && REACHED=1 && break - fi - - if [[ ${TRIED} -eq 1 ]]; then - continue - fi - - ping -n -c1 -w4 -i1 www.google.com && REACHED=1 && break - done - - if [[ ${REACHED} -eq 0 ]]; then - echo "Unable to reach network" - return 1 - fi - - return 0 -} - -function compatible_network() { - # Try installing network-scripts if available - case $LINUX_KIND in - redhat | rhel | centos | fedora) - sfRetry 3m 5 "sfYum install -q -y network-scripts" || true - ;; - *) ;; - esac -} - -function make_ready_for_ansible() { - # Try installing python3 if available, a failure is not considered an error - case $LINUX_KIND in - debian | ubuntu) - sfRetry 3m 5 "sfApt install -y python3" || true - ;; - redhat | rhel | centos | fedora) - sfRetry 3m 5 "sfYum install -q -y python3" || true - ;; - *) ;; - esac -} - -function track_time() { - uptime - last -} - -# ---- Main - -PHASE_DONE=/opt/safescale/var/state/user_data.phase2.done -if [[ -f "$PHASE_DONE" ]]; then - echo "$PHASE_DONE already there." - set +x - exit 0 -fi - -track_time - -collect_original_packages - -fail_fast_unsupported_distros - -configure_locale - -op=1 -ensure_curl_is_installed && op=$? || true -if [[ ${op} -ne 0 ]]; then - echo "Curl not available yet" -else - echo "Curl installed" -fi - -check_dns_configuration || true - -op=1 -is_network_reachable && op=$? || true -in_reach_before_dns=$op - -configure_dns - -op=1 -is_network_reachable && op=$? || true -in_reach_after_dns=$op - -if [[ ${in_reach_after_dns} -eq 1 ]]; then - if [[ ${in_reach_before_dns} -eq 0 ]]; then - echo "PROVISIONING_ERROR: Changing DNS messed up connectivity" && fail 191 - fi -fi - -op=1 -ensure_network_connectivity && op=$? || true -network_connectivity_ok=$op - -op=1 -is_network_reachable && op=$? || true -in_reach_after_gateway_setup=$op - -if [[ ${in_reach_after_gateway_setup} -eq 1 ]]; then - if [[ ${in_reach_after_dns} -eq 0 ]]; then - echo "PROVISIONING_ERROR: Changing Gateway messed up connectivity" && fail 192 - fi -fi - -add_common_repos -add_backport_repos - -early_packages_update - -install_route_if_needed -install_packages - -make_ready_for_ansible - -lspci | grep -i nvidia &> /dev/null && install_drivers_nvidia - -use_cgroups_v1_if_needed || fail 235 - -update_kernel_settings || fail 236 -configure_root_password_if_needed || fail 237 - -identify_nics -configure_network - -is_network_reachable || fail 238 - -collect_installed_packages - -echo -n "0,linux,${LINUX_KIND},${FULL_VERSION_ID},$(hostname),$(date +%Y/%m/%d-%H:%M:%S)" > /opt/safescale/var/state/user_data.phase2.done - -# For compatibility with previous user_data implementation (until v19.03.x)... -mkdir -p /var/tmp || true -ln -s ${SF_VARDIR}/state/user_data.phase2.done /var/tmp/user_data.done || true - -# !!! DON'T REMOVE !!! #insert_tag allows to add something just before exiting, -# but after the template has been realized (cf. libvirt Stack) -#insert_tag - -force_dbus_restart - -track_time - -set +x -exit 0 diff --git a/lib/backend/iaas/userdata/prepare.go b/lib/backend/iaas/userdata/prepare.go index 51bc37d77..c66ffb1c8 100644 --- a/lib/backend/iaas/userdata/prepare.go +++ b/lib/backend/iaas/userdata/prepare.go @@ -1,3 +1,6 @@ +//go:build !debug +// +build !debug + /* * Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu * diff --git a/lib/backend/iaas/userdata/prepare_debug.go b/lib/backend/iaas/userdata/prepare_debug.go new file mode 100644 index 000000000..a34d5e000 --- /dev/null +++ b/lib/backend/iaas/userdata/prepare_debug.go @@ -0,0 +1,370 @@ +//go:build debug +// +build debug + +/* + * Copyright 2018-2023, CS Systemes d'Information, http://csgroup.eu + * + * 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. + */ + +package userdata + +import ( + "bytes" + "embed" + "fmt" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + txttmpl "text/template" + + "github.com/sirupsen/logrus" + + "github.com/CS-SI/SafeScale/v22/lib/backend/iaas/stacks" + "github.com/CS-SI/SafeScale/v22/lib/backend/resources/abstract" + "github.com/CS-SI/SafeScale/v22/lib/system" + "github.com/CS-SI/SafeScale/v22/lib/utils" + "github.com/CS-SI/SafeScale/v22/lib/utils/data/json" + "github.com/CS-SI/SafeScale/v22/lib/utils/fail" + "github.com/CS-SI/SafeScale/v22/lib/utils/template" + "github.com/CS-SI/SafeScale/v22/lib/utils/temporal" +) + +// Content is the structure to apply to userdata.sh template +type Content struct { + // BashLibrary contains the bash library + system.BashLibraryDefinition + + Header string // is the bash header for scripts + Revision string // is the git revision used to build SafeScale + Username string // is the name of the default user (api.DefaultUser) + ExitOnError string // helper to quit script on error + Password string // for the user safescale (for troubleshoot use, usable only in console) + FirstPublicKey string // is the public key used for first connection after Host creation + FirstPrivateKey string // is the private key used for first connection after Host creation + FinalPublicKey string // is the public key used to connect to Host starting phase3 (disabling FirstPublicKey) + FinalPrivateKey string // is the private key used to connect tp Host starting phase3 (disabling FirstPrivateKey) + ConfIF bool // if set to true, configure all interfaces to DHCP + IsGateway bool // if set to true, activate IP forwarding + SSHPort string // Define Gateway SSHport + PublicIP string // contains a public IP bound to the host + AddGateway bool // if set to true, configure default gateway + DNSServers []string // contains the list of DNS servers to use; used only if IsGateway is true + CIDR string // contains the cidr of the network + DefaultRouteIP string // is the IP of the gateway or the VIP if gateway HA is enabled + EndpointIP string // is the IP of the gateway or the VIP if gateway HA is enabled + PrimaryGatewayPrivateIP string // is the private IP of the primary gateway + PrimaryGatewayPublicIP string // is the public IP of the primary gateway + SecondaryGatewayPrivateIP string // is the private IP of the secondary gateway + SecondaryGatewayPublicIP string // is the public IP of the secondary gateway + EmulatedPublicNet string // is a private network which is used to emulate a public one + HostName string // contains the name wanted as host name (default == name of the Cloud resource) + Tags map[Phase]map[string][]string // contains tags and their content(s); a tag is named # in the template + IsPrimaryGateway bool // tells if the host is a primary gateway + GatewayHAKeepalivedPassword string // contains the password to use in keepalived configurations + ProviderName string + BuildSubnetworks bool + Debug bool + WithoutFirewall bool + DefaultFirewall bool + // Dashboard bool // Add kubernetes dashboard +} + +var ( + userdataScriptTemplates = map[Phase]*atomic.Value{ + PHASE1_INIT: nil, + PHASE2_NETWORK_AND_SECURITY: nil, + PHASE3_GATEWAY_HIGH_AVAILABILITY: nil, + PHASE4_SYSTEM_FIXES: nil, + PHASE5_FINAL: nil, + } + userdataScriptTemplatesLock sync.RWMutex + userdataScriptProvider string + userdataScripts = map[Phase]string{ // FIXME: OPP Time to read this shit from a directory + PHASE1_INIT: "newscripts/userdata%s.init.sh", + PHASE2_NETWORK_AND_SECURITY: "newscripts/userdata%s.netsec.sh", + PHASE3_GATEWAY_HIGH_AVAILABILITY: "newscripts/userdata%s.gwha.sh", + PHASE4_SYSTEM_FIXES: "newscripts/userdata%s.sysfix.sh", + PHASE5_FINAL: "newscripts/userdata%s.final.sh", + } +) + +// NewContent ... +func NewContent() *Content { + return &Content{ + Tags: map[Phase]map[string][]string{}, + } +} + +// OK ... +func (ud Content) OK() bool { + result := true + result = result && ud.BashLibraryDefinition.Content != "" + result = result && ud.HostName != "" + return result +} + +// Prepare prepares the initial configuration script executed by cloud compute resource +func (ud *Content) Prepare( + options stacks.ConfigurationOptions, request abstract.HostRequest, cidr string, defaultNetworkCIDR string, + timings temporal.Timings, +) fail.Error { + if ud == nil { + return fail.InvalidInstanceError() + } + + // Generate password for user safescale + var ( + // autoHostNetworkInterfaces bool + useLayer3Networking bool + dnsList []string + operatorUsername string + useNATService bool + ) + if request.Password == "" { + password, err := utils.GeneratePassword(16) + if err != nil { + return fail.Wrap(err, "failed to generate password") + } + request.Password = password + } + + // Determine default route IP + ip := "" + if request.DefaultRouteIP != "" { + ip = request.DefaultRouteIP + } + + // autoHostNetworkInterfaces = options.AutoHostNetworkInterfaces + useLayer3Networking = options.UseLayer3Networking + useNATService = options.UseNATService + operatorUsername = options.OperatorUsername + dnsList = options.DNSList + if len(dnsList) == 0 { + dnsList = []string{"1.1.1.1"} + } + + bashLibraryDefinition, xerr := system.BuildBashLibraryDefinition(timings) + if xerr != nil { + return xerr + } + + exitOnErrorHeader := "" + scriptHeader := "set -u -o pipefail" + if suffixCandidate := os.Getenv("SAFESCALE_SCRIPTS_FAIL_FAST"); suffixCandidate != "" { + if strings.EqualFold("True", strings.TrimSpace(suffixCandidate)) || + strings.EqualFold("1", strings.TrimSpace(suffixCandidate)) { + scriptHeader = "set -Eeuxo pipefail" + exitOnErrorHeader = "echo 'PROVISIONING_ERROR: 222'" + } + } + + if debugFlag := os.Getenv("SAFESCALE_DEBUG"); debugFlag != "" { + ud.Debug = true + } + + if debugFlag := os.Getenv("SAFESCALE_DEBUG"); debugFlag == "NoFirewall" { + ud.WithoutFirewall = true + } + + if debugFlag := os.Getenv("SAFESCALE_DEBUG"); debugFlag == "DefaultFirewall" { + ud.DefaultFirewall = true + } + + ud.BashLibraryDefinition = *bashLibraryDefinition + ud.Header = scriptHeader + ud.Revision = REV + ud.Username = operatorUsername + ud.ExitOnError = exitOnErrorHeader + ud.FinalPublicKey = strings.Trim(request.KeyPair.PublicKey, "\n") + ud.FinalPrivateKey = strings.Trim(request.KeyPair.PrivateKey, "\n") + // ud.ConfIF = !autoHostNetworkInterfaces + ud.IsGateway = request.IsGateway /*&& request.Subnets[0].Name != abstract.SingleHostNetworkName*/ + ud.AddGateway = !request.IsGateway && !request.PublicIP && !useLayer3Networking && ip != "" && !useNATService + ud.DNSServers = dnsList + ud.SSHPort = strconv.Itoa(int(request.SSHPort)) + ud.CIDR = cidr + ud.DefaultRouteIP = ip + ud.Password = request.Password + ud.EmulatedPublicNet = defaultNetworkCIDR + ud.ProviderName = options.ProviderName + ud.BuildSubnetworks = options.BuildSubnets + + if request.HostName != "" { + ud.HostName = request.HostName + } else { + ud.HostName = request.ResourceName + } + + // Generate a keypair for first SSH connection, that will then be replaced by FinalPxxxKey during phase2 + kp, xerr := abstract.NewKeyPair("") + if xerr != nil { + return fail.Wrap(xerr, "failed to create initial Keypair") + } + + ud.FirstPrivateKey = kp.PrivateKey + ud.FirstPublicKey = kp.PublicKey + + return nil +} + +func (ud Content) ToMap() (map[string]interface{}, fail.Error) { + jsoned, err := json.Marshal(ud) + if err != nil { + return nil, fail.Wrap(err, "failed to convert struct to json") + } + var mapped map[string]interface{} + err = json.Unmarshal(jsoned, &mapped) + if err != nil { + return nil, fail.Wrap(err, "failed to convert json string to map") + } + + return mapped, nil +} + +//go:embed newscripts/* +var scripts embed.FS + +// Generate generates the script file corresponding to the phase +func (ud *Content) Generate(phase Phase) ([]byte, fail.Error) { + var ( + result []byte + err error + ) + + userdataScriptTemplatesLock.Lock() + defer userdataScriptTemplatesLock.Unlock() + + anon, ok := userdataScriptTemplates[phase] + if !ok { + return nil, fail.InvalidParameterError("phase '%s' not managed", phase) + } + + var tmpl *txttmpl.Template + if anon != nil { + tmpl, ok = anon.Load().(*txttmpl.Template) + if !ok { + return nil, fail.NewError("error loading template for phase '%s'", phase) + } + } else { + var tmplString []byte + tmplString, err = scripts.ReadFile(fmt.Sprintf(userdataScripts[phase], userdataScriptProvider)) + if err != nil { + return nil, fail.Wrap(err, "error loading script template for phase 'init'") + } + + tmpl, err = template.Parse("userdata."+string(phase), string(tmplString)) + if err != nil { + return nil, fail.Wrap(err, "error parsing script template for phase 'init'") + } + + userdataScriptTemplates[phase] = new(atomic.Value) + userdataScriptTemplates[phase].Store(tmpl) + } + + if tmpl == nil { + return nil, fail.InconsistentError("failed to recover userdata script for phase '%s'", phase) + } + + // Transforms struct content to map using json + mapped, xerr := ud.ToMap() + if xerr != nil { + return nil, xerr + } + + buf := bytes.NewBufferString("") + err = tmpl.Option("missingkey=error").Execute(buf, mapped) + if err != nil { + return nil, fail.ConvertError(err) + } + + result = buf.Bytes() + for tagname, tagcontent := range ud.Tags[phase] { + for _, str := range tagcontent { + bytes.Replace(result, []byte("#"+tagname), []byte(str+"\n\n#"+tagname), 1) + } + } + + if forensics := os.Getenv("SAFESCALE_FORENSICS"); forensics != "" { + _ = os.MkdirAll(utils.AbsPathify(fmt.Sprintf("$HOME/.safescale/forensics/%s", ud.HostName)), 0777) + dumpName := utils.AbsPathify(fmt.Sprintf("$HOME/.safescale/forensics/%s/userdata.%s.sh", ud.HostName, phase)) + err = os.WriteFile(dumpName, result, 0644) + if err != nil { // No need to act on err + logrus.Warnf("[TRACE] Failure writing step info into %s", dumpName) + } + } + + return result, nil +} + +// AddInTag adds some useful code on the end of userdata.netsec.sh just before the end (on the label #insert_tag) +func (ud Content) AddInTag(phase Phase, tagname string, content string) { + if _, ok := ud.Tags[phase]; !ok { + ud.Tags[phase] = map[string][]string{} + } + ud.Tags[phase][tagname] = append(ud.Tags[phase][tagname], content) +} + +// checkScriptFilePresents ... +// returns: +// - nil: files found +// - *fail.ErrNotFound: at least one file with suffix != "" is not found +// - *fail.ErrInconsistent: at least one mandatory file is missing +func checkScriptFilePresents(suffix string) fail.Error { + var ( + missing Phase + problem bool + ) + for k, v := range userdataScripts { + _, err := scripts.ReadFile(fmt.Sprintf(v, suffix)) + problem = problem || (err != nil) + if problem { + missing = k + break + } + } + if problem { + if suffix != "" { + return fail.Wrap(fail.NotFoundError("missing userdata script 'userdata.%s.%s.sh' in binary", suffix, missing), "ignoring script flavor '%s'", suffix) + } + return fail.InconsistentError("missing mandatory userdata script 'userdata.%s.sh' in binary", missing) + } + + return nil +} + +// init checks at start if all needed userdata scripts are present in binary +func init() { + suffixCandidate := os.Getenv("SAFESCALE_SCRIPT_FLAVOR") + xerr := checkScriptFilePresents(suffixCandidate) + if xerr != nil { + switch xerr.(type) { + case *fail.ErrNotFound: + // ignore suffixCandidate but still check "traditional" mandatory files are present + if suffixCandidate != "" { + xerr = checkScriptFilePresents("") + if xerr != nil { + panic(xerr.Error()) + } + } + default: + panic(xerr.Error()) + } + } + + if suffixCandidate != "" { + userdataScriptProvider = suffixCandidate + } +} diff --git a/lib/backend/resources/operations/clustertasks.go b/lib/backend/resources/operations/clustertasks.go index 3adfab88c..565ba0eae 100755 --- a/lib/backend/resources/operations/clustertasks.go +++ b/lib/backend/resources/operations/clustertasks.go @@ -19,6 +19,7 @@ package operations import ( "context" "fmt" + "github.com/CS-SI/SafeScale/v22/lib/backend/resources/enums/clusterflavor" "github.com/sony/gobreaker" "math" "net" @@ -179,6 +180,40 @@ func (instance *Cluster) taskCreateCluster(inctx context.Context, params interfa return nil, xerr } + if req.Flavor == clusterflavor.K8S { + lowerOS := strings.ToLower(req.GatewaysDef.Image) + if strings.Contains(lowerOS, "centos 7") { + return nil, fail.NewError("Sorry, K8s with CentOS 7 not supported") + } + + lowerOS = strings.ToLower(req.MastersDef.Image) + if strings.Contains(lowerOS, "centos 7") { + return nil, fail.NewError("Sorry, K8s with CentOS 7 not supported") + } + + lowerOS = strings.ToLower(req.NodesDef.Image) + if strings.Contains(lowerOS, "centos 7") { + return nil, fail.NewError("Sorry, K8s with CentOS 7 not supported") + } + } + + if req.Flavor != 0 { + lowerOS := strings.ToLower(req.GatewaysDef.Image) + if strings.Contains(lowerOS, "ubuntu 22.04") { + return nil, fail.NewError("Sorry, Ubuntu 22.04 not supported") + } + + lowerOS = strings.ToLower(req.MastersDef.Image) + if strings.Contains(lowerOS, "ubuntu 22.04") { + return nil, fail.NewError("Sorry, Ubuntu 22.04 not supported") + } + + lowerOS = strings.ToLower(req.NodesDef.Image) + if strings.Contains(lowerOS, "ubuntu 22.04") { + return nil, fail.NewError("Sorry, Ubuntu 22.04 not supported") + } + } + var networkInstance resources.Network var subnetInstance resources.Subnet defer func() { diff --git a/lib/backend/resources/operations/featurefile.go b/lib/backend/resources/operations/featurefile.go index 493898e41..681898787 100644 --- a/lib/backend/resources/operations/featurefile.go +++ b/lib/backend/resources/operations/featurefile.go @@ -24,10 +24,8 @@ import ( "github.com/CS-SI/SafeScale/v22/lib/utils/data" "github.com/CS-SI/SafeScale/v22/lib/utils/debug" "github.com/CS-SI/SafeScale/v22/lib/utils/fail" - "github.com/farmergreg/rfsnotify" "github.com/sirupsen/logrus" "github.com/spf13/viper" - "gopkg.in/fsnotify.v1" "io/fs" "os" "path" @@ -36,8 +34,7 @@ import ( ) var ( - featureFileController = make(map[string]interface{}) - featureFileFolders = []string{ + featureFileFolders = []string{ "$HOME/.safescale/features", "$HOME/.config/safescale/features", "/etc/safescale/features", @@ -608,142 +605,3 @@ func setViperConfigPathes(v *viper.Viper) { } } } - -// watchFeatureFileFolders watches folders that may contain Feature Files and react to changes (invalidating cache entry -// already loaded) -func watchFeatureFileFolders(ctx context.Context) error { - watcher, err := rfsnotify.NewWatcher() - if err != nil { - return err - } - defer func() { _ = watcher.Close() }() - - done := make(chan bool) - go func() { - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - onFeatureFileEvent(ctx, watcher, event) - - case err, ok := <-watcher.Errors: - if !ok { - return - } - logrus.WithContext(ctx).Error("Feature File watcher returned an error: ", err) - } - } - }() - - for _, v := range getFeatureFileFolders(false) { - err = addPathToWatch(ctx, watcher, v) - if err != nil { - return err - } - } - - <-done - return nil -} - -func addPathToWatch(ctx context.Context, w *rfsnotify.RWatcher, path string) error { - err := w.AddRecursive(path) - if err != nil { - switch casted := err.(type) { - case *fs.PathError: - switch casted.Err { - case NOTFOUND: - // folder not found, ignore it - debug.IgnoreError2(ctx, err) - return nil - default: - logrus.WithContext(ctx).Error(err) - return err - } - default: - logrus.WithContext(ctx).Error(err) - return err - } - } - - logrus.WithContext(ctx).Debugf("adding monitoring of folder '%s' for Feature file changes", path) - return nil -} - -// onFeatureFileEvent reacts to filesystem change event -func onFeatureFileEvent(ctx context.Context, w *rfsnotify.RWatcher, e fsnotify.Event) { - switch { - case e.Op&fsnotify.Chmod == fsnotify.Chmod: - fallthrough - case e.Op&fsnotify.Remove == fsnotify.Remove: - fallthrough - case e.Op&fsnotify.Rename == fsnotify.Rename: - fallthrough - case e.Op&fsnotify.Write == fsnotify.Write: - relativeName := reduceFilename(e.Name) - stat, err := os.Stat(e.Name) - if err == nil { - if stat.IsDir() { - // If the fs object is a folder, do nothing more - return - } - - // If the fs object is a file and is still readable, do nothing more - if e.Op&fsnotify.Chmod == fsnotify.Chmod { - fd, err := os.Open(e.Name) - if err == nil { - _ = fd.Close() - return - } - } - } - - // invalidate only .yml/.yaml files from cache - extension := path.Ext(relativeName) - if extension != ".yml" && extension != ".yaml" { - return - } - - // From here, we need to invalidate cache entry, either because content has changed or file have been removed/renamed or updated - featureName := strings.TrimPrefix(strings.TrimSuffix(relativeName, extension), "/") - if len(featureName) != len(relativeName) { - delete(featureFileController, featureName) - } - - case e.Op&fsnotify.Create == fsnotify.Create: - // If the object created is a path, add it to RWatcher (if it's a file, nothing more to do, cache miss will - // do the necessary in time - fi, err := os.Stat(e.Name) - if err == nil && fi.IsDir() { - _ = w.AddRecursive(e.Name) - } - } -} - -// reduceFilename removes the absolute part of 'name' corresponding to folder -func reduceFilename(name string) string { - folders := getFeatureFileFolders(false) - last := name - for _, v := range folders { - if strings.HasPrefix(name, v) { - reduced := strings.TrimPrefix(name, v) - if len(reduced) < len(last) { - last = reduced - } - } - } - return strings.TrimLeft(last, "/") -} - -// StartFeatureFileWatcher inits the watcher of Feature File changes -func StartFeatureFileWatcher() { - // Starts go routine watching changes in Feature File folders - go func() { - err := watchFeatureFileFolders(context.Background()) - if err != nil { - logrus.WithContext(context.Background()).Error(err) - } - }() -}