Andrey Smirnov b2b86a622e fix: remove 'token creds' from maintenance service
This fixes the reverse Go dependency from `pkg/machinery` to `talos`
package.

Add a check to `Dockerfile` to prevent `pkg/machinery/go.mod` getting
out of sync, this should prevent problems in the future.

Fix potential security issue in `token` authorizer to deny requests
without grpc metadata.

In provisioner, add support for launching nodes without the config
(config is not delivered to the provisioned nodes).

Breaking change in `pkg/provision`: now `NodeRequest.Type` should be set
to the node type (as config can be missing now).

In `talosctl cluster create` add a flag to skip providing config to the
nodes so that they enter maintenance mode, while the generated configs
are written down to disk (so they can be tweaked and applied easily).

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2020-11-09 14:10:32 -08:00

211 lines
5.8 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package firecracker
import (
"fmt"
"io"
"io/ioutil"
"math"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
firecracker "github.com/firecracker-microvm/firecracker-go-sdk"
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
multierror "github.com/hashicorp/go-multierror"
"github.com/talos-systems/go-procfs/procfs"
"k8s.io/apimachinery/pkg/util/json"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/provision"
"github.com/talos-systems/talos/pkg/provision/providers/vm"
)
func (p *provisioner) createNodes(state *vm.State, clusterReq provision.ClusterRequest, nodeReqs []provision.NodeRequest, opts *provision.Options) ([]provision.NodeInfo, error) {
errCh := make(chan error)
nodeCh := make(chan provision.NodeInfo, len(nodeReqs))
for _, nodeReq := range nodeReqs {
go func(nodeReq provision.NodeRequest) {
nodeInfo, err := p.createNode(state, clusterReq, nodeReq, opts)
if err == nil {
nodeCh <- nodeInfo
}
errCh <- err
}(nodeReq)
}
var multiErr *multierror.Error
for range nodeReqs {
multiErr = multierror.Append(multiErr, <-errCh)
}
close(nodeCh)
nodesInfo := make([]provision.NodeInfo, 0, len(nodeReqs))
for nodeInfo := range nodeCh {
nodesInfo = append(nodesInfo, nodeInfo)
}
return nodesInfo, multiErr.ErrorOrNil()
}
//nolint:gocyclo
func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRequest, nodeReq provision.NodeRequest, opts *provision.Options) (provision.NodeInfo, error) {
socketPath := state.GetRelativePath(fmt.Sprintf("%s.sock", nodeReq.Name))
pidPath := state.GetRelativePath(fmt.Sprintf("%s.pid", nodeReq.Name))
vcpuCount := int64(math.RoundToEven(float64(nodeReq.NanoCPUs) / 1000 / 1000 / 1000))
if vcpuCount < 2 {
vcpuCount = 1
}
memSize := nodeReq.Memory / 1024 / 1024
diskPaths, err := p.CreateDisks(state, nodeReq)
if err != nil {
return provision.NodeInfo{}, err
}
cmdline := procfs.NewDefaultCmdline()
// required to get kernel console
cmdline.Append("console", "ttyS0")
// reboot configuration
cmdline.Append("reboot", "k")
cmdline.Append("panic", "1")
// disable stuff we don't need
cmdline.Append("pci", "off")
cmdline.Append("acpi", "off")
cmdline.Append("i8042.noaux", "")
// Talos config
cmdline.Append("talos.platform", "metal")
cmdline.Append("talos.hostname", nodeReq.Name)
var nodeConfig string
if nodeReq.Config != nil {
cmdline.Append("talos.config", "{TALOS_CONFIG_URL}") // to be patched by launcher
nodeConfig, err = nodeReq.Config.String()
if err != nil {
return provision.NodeInfo{}, err
}
}
ones, _ := clusterReq.Network.CIDR.Mask.Size()
drives := make([]models.Drive, len(diskPaths))
for i, disk := range diskPaths {
drives[i] = models.Drive{
DriveID: firecracker.String("disk"),
IsRootDevice: firecracker.Bool(false),
IsReadOnly: firecracker.Bool(false),
PathOnHost: firecracker.String(disk),
}
}
cfg := firecracker.Config{
SocketPath: socketPath,
KernelImagePath: strings.ReplaceAll(clusterReq.KernelPath, constants.ArchVariable, opts.TargetArch),
KernelArgs: cmdline.String(),
InitrdPath: strings.ReplaceAll(clusterReq.InitramfsPath, constants.ArchVariable, opts.TargetArch),
ForwardSignals: []os.Signal{}, // don't forward any signals
MachineCfg: models.MachineConfiguration{
HtEnabled: firecracker.Bool(false),
VcpuCount: firecracker.Int64(vcpuCount),
MemSizeMib: firecracker.Int64(memSize),
},
NetworkInterfaces: firecracker.NetworkInterfaces{
firecracker.NetworkInterface{
CNIConfiguration: &firecracker.CNIConfiguration{
BinPath: clusterReq.Network.CNI.BinPath,
ConfDir: clusterReq.Network.CNI.ConfDir,
CacheDir: clusterReq.Network.CNI.CacheDir,
NetworkConfig: state.VMCNIConfig,
Args: [][2]string{
{"IP", fmt.Sprintf("%s/%d", nodeReq.IP, ones)},
{"GATEWAY", clusterReq.Network.GatewayAddr.String()},
},
IfName: "veth0",
VMIfName: "eth0",
},
},
},
Drives: drives,
}
logFile, err := os.OpenFile(state.GetRelativePath(fmt.Sprintf("%s.log", nodeReq.Name)), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o666)
if err != nil {
return provision.NodeInfo{}, err
}
defer logFile.Close() //nolint: errcheck
launchConfig := LaunchConfig{
FirecrackerConfig: cfg,
Config: nodeConfig,
GatewayAddr: clusterReq.Network.GatewayAddr,
BootloaderEmulation: opts.BootloaderEnabled,
}
launchConfigFile, err := os.Create(state.GetRelativePath(fmt.Sprintf("%s.config", nodeReq.Name)))
if err != nil {
return provision.NodeInfo{}, err
}
if err = json.NewEncoder(launchConfigFile).Encode(&launchConfig); err != nil {
return provision.NodeInfo{}, err
}
if _, err = launchConfigFile.Seek(0, io.SeekStart); err != nil {
return provision.NodeInfo{}, err
}
defer launchConfigFile.Close() //nolint: errcheck
cmd := exec.Command(clusterReq.SelfExecutable, "firecracker-launch")
cmd.Stdout = logFile
cmd.Stderr = logFile
cmd.Stdin = launchConfigFile
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true, // daemonize
}
if err = cmd.Start(); err != nil {
return provision.NodeInfo{}, err
}
if err = ioutil.WriteFile(pidPath, []byte(strconv.Itoa(cmd.Process.Pid)), os.ModePerm); err != nil {
return provision.NodeInfo{}, fmt.Errorf("error writing PID file: %w", err)
}
// no need to wait here, as cmd has all the Stdin/out/err via *os.File
nodeInfo := provision.NodeInfo{
ID: pidPath,
Name: nodeReq.Name,
Type: nodeReq.Type,
NanoCPUs: nodeReq.NanoCPUs,
Memory: nodeReq.Memory,
DiskSize: nodeReq.Disks[0].Size,
PrivateIP: nodeReq.IP,
}
return nodeInfo, nil
}