multus-cni/cmd/config-generation/kubeconfig.go
Miguel Duarte Barroso 8ba2accb9f
Replace entrypoint script with initcontainers (#718)
* build: install the multus binary in an init container

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: generate kubeconfig via go

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: generate multus cni configuration via golang

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: provide a docker img for daemon based deployments

We will have 2 different images (only on amd64 archs):
- legacy entrypoint script based
- daemonized process

The `image-build` docker action is updated, to build these 2 images.

There will be 2 different deployment specs, along with e2e test
lanes, one for each of the aforementioned alternatives.

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: delegate CNI config watch loop via golang

For the thick-plugin alternative, provide the watch loop for
configuration regeneration via a golang binary.

Over time, this binary is expected to run the control loop to watch
out for pod updates.

To enable current multus users to chose when they upgrade to this new
deployment setup, these changes are provided in separate multus images,
having a different yaml spec files. Both of these alternatives are
tested e2e, since a new lane is introduced.

The following libraries are introduced, along with the motivation for
adding them:
- dproxy: allows traversing the default network configuration arbitrarily,
  similar to what an X path / JSON path tool provides.
  Repo is available at [0].
- fsnotify: watch for changes in the default CNI configuration file.
  Repo is available at [1].

The config map providing the default network CNI configuration is not
copied over, since originally, the user was not required to install a
default network CNI plugin first, but, nowadays, this is a required
step of multus.

As such, it is no longer required to provide a default CNI
configuration.

[0] - https://github.com/koron/go-dproxy
[1] - https://github.com/fsnotify/fsnotify

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* run gofmt

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* refactor: make the builder pattern more idiomatic to golang

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>

* build: update github actions to release new imgs

Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-10-27 08:42:37 -04:00

148 lines
4.6 KiB
Go

// Copyright (c) 2021 Multus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package main
import (
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
const userRWPermission = 0600
const (
cniConfigDirVarName = "cni-config-dir"
k8sCAFilePathVarName = "kube-ca-file"
k8sServiceHostVarName = "k8s-service-host"
k8sServicePortVarName = "k8s-service-port"
serviceAccountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
skipTLSVerifyVarName = "skip-tls-verify"
)
const (
defaultCniConfigDir = "/host/etc/cni/net.d"
defaultK8sCAFilePath = ""
defaultK8sServiceHost = ""
defaultK8sServicePort = 0
defaultSkipTLSValue = false
)
func main() {
k8sServiceHost := flag.String(k8sServiceHostVarName, defaultK8sServiceHost, "Cluster IP of the kubernetes service")
k8sServicePort := flag.Int(k8sServicePortVarName, defaultK8sServicePort, "Port of the kubernetes service")
skipTLSVerify := flag.Bool(skipTLSVerifyVarName, defaultSkipTLSValue, "Should TLS verification be skipped")
kubeCAFilePath := flag.String(k8sCAFilePathVarName, defaultK8sCAFilePath, "Override the default kubernetes CA file path")
cniConfigDir := flag.String(cniConfigDirVarName, defaultCniConfigDir, "CNI config dir")
flag.Parse()
if *k8sServiceHost == defaultK8sServiceHost {
logInvalidArg("must provide the k8s service cluster port")
}
if *k8sServicePort == defaultK8sServicePort {
logInvalidArg("must provide the k8s service cluster port")
}
if *kubeCAFilePath == defaultK8sServiceHost {
*kubeCAFilePath = serviceAccountPath + "/ca.crt"
}
tlsCfg := "insecure-skip-tls-verify: true"
if !*skipTLSVerify {
kubeCAFileContents, err := k8sCAFileContentsBase64(*kubeCAFilePath)
if err != nil {
logError("failed grabbing CA file: %w", err)
}
tlsCfg = "certificate-authority-data: " + kubeCAFileContents
}
multusConfigDir := *cniConfigDir + "/multus.d/"
if err := prepareCNIConfigDir(multusConfigDir); err != nil {
logError("failed to create CNI config dir: %w", err)
}
kubeConfigFilePath := *cniConfigDir + "/multus.d/multus.kubeconfig"
serviceAccountToken, err := k8sKubeConfigToken(serviceAccountPath + "/token")
if err != nil {
logError("failed grabbing k8s token: %w", err)
}
if err := writeKubeConfig(kubeConfigFilePath, "https", *k8sServiceHost, *k8sServicePort, tlsCfg, serviceAccountToken); err != nil {
logError("failed generating kubeconfig: %w", err)
}
}
func k8sCAFileContentsBase64(pathCAFile string) (string, error) {
data, err := ioutil.ReadFile(pathCAFile)
if err != nil {
return "", fmt.Errorf("failed reading file %s: %w", pathCAFile, err)
}
return strings.Trim(base64.StdEncoding.EncodeToString(data), "\n"), nil
}
func k8sKubeConfigToken(tokenPath string) (string, error) {
data, err := ioutil.ReadFile(tokenPath)
if err != nil {
return "", fmt.Errorf("failed reading file %s: %w", tokenPath, err)
}
return string(data), nil
}
func writeKubeConfig(outputPath string, protocol string, k8sServiceIP string, k8sServicePort int, tlsConfig string, serviceAccountToken string) error {
kubeConfigTemplate := `
# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
cluster:
server: %s://[%s]:%d
%s
users:
- name: multus
user:
token: "%s"
contexts:
- name: multus-context
context:
cluster: local
user: multus
current-context: multus-context
`
kubeconfig := fmt.Sprintf(kubeConfigTemplate, protocol, k8sServiceIP, k8sServicePort, tlsConfig, serviceAccountToken)
logInfo("Generated KubeConfig: \n%s", kubeconfig)
return ioutil.WriteFile(outputPath, []byte(kubeconfig), userRWPermission)
}
func prepareCNIConfigDir(cniConfigDirPath string) error {
return os.MkdirAll(cniConfigDirPath, userRWPermission)
}
func logInvalidArg(format string, values ...interface{}) {
log.Printf("ERROR: %s", fmt.Errorf(format, values...).Error())
flag.PrintDefaults()
os.Exit(1)
}
func logError(format string, values ...interface{}) {
log.Printf("ERROR: %s", fmt.Errorf(format, values...).Error())
os.Exit(1)
}
func logInfo(format string, values ...interface{}) {
log.Printf("INFO: %s", fmt.Sprintf(format, values...))
}