<commit_before>package add

import (
	"fmt"
	"io"

	"time"

	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/args"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/flags"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/with"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/commands/image"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/util"
	"github.com/BytemarkHosting/bytemark-client/lib/brain"
	"github.com/BytemarkHosting/bytemark-client/lib/output"
	"github.com/BytemarkHosting/bytemark-client/lib/output/prettyprint"
	"github.com/BytemarkHosting/bytemark-client/util/log"
	"github.com/urfave/cli"
)

func init() {
	createServerCmd := cli.Command{
		Name:      "server",
		Usage:     `add a new server with bytemark`,
		UsageText: "add server [flags] <name> [<cores> [<memory [<disc specs>]...]]",
		Description: `Adds a Cloud Server with the given specification, defaulting to a basic server with Symbiosis installed and weekly backups of the first disc.

A disc spec looks like the following: label:grade:size
The label and grade fields are optional. If grade is empty, defaults to sata.
If there are two fields, they are assumed to be grade and size.
Multiple --disc flags can be used to add multiple discs

If --backup is set then a backup of the first disk will be taken at the
frequency specified - never, daily, weekly or monthly. This backup will be free if
it's below a certain threshold of size. By default, a backup is taken every week.
This may cost money if your first disk is larger than the default.
See the price list for more details at http://www.bytemark.co.uk/prices

If --hwprofile-locked is set then the cloud server's virtual hardware won't be changed over time.`,
		Flags: append(app.OutputFlags("server", "object"),
			cli.IntFlag{
				Name:  "cores",
				Value: 1,
				Usage: "Number of CPU cores",
			},
			cli.StringFlag{
				Name:  "cdrom",
				Usage: "URL pointing to an ISO which will be attached to the cloud server as a CD",
			},
			cli.GenericFlag{
				Name:  "disc",
				Usage: "One or more disc specifications. Defaults to a single 25GiB sata-grade disc",
				Value: new(util.DiscSpecFlag),
			},
			flags.Force,
			cli.StringFlag{
				Name:  "hwprofile",
				Usage: "The hardware profile to use. Defaults to the current modern profile. See `bytemark profiles` for a list of hardware profiles available.",
			},
			cli.BoolFlag{
				Name:  "hwprofile-locked",
				Usage: "If set, the hardware profile will be 'locked', meaning that when Bytemark updates the hardware profiles your VM will keep its current one.",
			},
			cli.GenericFlag{
				Name:  "ip",
				Value: new(util.IPFlag),
				Usage: "Specify an IPv4 or IPv6 address to use. This will only be useful if you are creating the machine in a private VLAN.",
			},
			cli.GenericFlag{
				Name:  "memory",
				Value: new(util.SizeSpecFlag),
				Usage: "How much memory the server will have available, specified in GiB or with GiB/MiB units. Defaults to 1GiB.",
			},
			cli.GenericFlag{
				Name:  "name",
				Usage: "The new server's name",
				Value: new(app.VirtualMachineNameFlag),
			},
			cli.BoolFlag{
				Name:  "no-image",
				Usage: "Specifies that the server should not be imaged.",
			},
			cli.StringFlag{
				Name:  "backup",
				Usage: "Add a backup schedule for the first disk at the given frequency (daily, weekly, monthly, or never)",
				Value: "weekly",
			},
			cli.BoolFlag{
				Name:  "stopped",
				Usage: "If set, the server will not be started, even to image it.",
			},
			cli.StringFlag{
				Name:  "zone",
				Usage: "Which zone the server will be created in. See `bytemark zones` for the choices.",
			},
		),
		Action: app.Action(args.Optional("name", "cores", "memory", "disc"), with.RequiredFlags("name"), with.Auth, createServer),
	}
	createServerCmd.Flags = append(createServerCmd.Flags, image.ImageInstallFlags...)
	Commands = append(Commands, createServerCmd)
}

// createServer creates a server objec to be created by the brain and sends it.
func createServer(c *app.Context) (err error) {
	name := c.VirtualMachineName("name")
	spec, err := createServerPrepSpec(c)
	if err != nil {
		return
	}

	groupName := name.GroupName()
	err = c.Client().EnsureGroupName(&groupName)
	if err != nil {
		return
	}

	log.Logf("The following server will be created in %s:\r\n", groupName)
	err = spec.PrettyPrint(c.App().Writer, prettyprint.Full)
	if err != nil {
		return err
	}

	// If we're not forcing, prompt. If the prompt comes back false, exit.
	if !c.Bool("force") && !util.PromptYesNo(c.Prompter(), "Are you certain you wish to continue?") {
		log.Error("Exiting.")
		return util.UserRequestedExit{}
	}

	_, err = c.Client().CreateVirtualMachine(groupName, spec)
	if err != nil {
		return err
	}
	vm, err := c.Client().GetVirtualMachine(name)
	if err != nil {
		return
	}
	return c.OutputInDesiredForm(CreatedVirtualMachine{Spec: spec, VirtualMachine: vm})
}

// createServerPrepSpec sets up the server spec by reading in all the flags.
func createServerPrepSpec(c *app.Context) (spec brain.VirtualMachineSpec, err error) {
	noImage := c.Bool("no-image")
	backupFrequency := c.String("backup")

	discs, cores, memory, err := createServerReadArgs(c)
	if err != nil {
		return
	}

	discs, err = createServerPrepDiscs(backupFrequency, discs)
	if err != nil {
		return
	}

	ipspec, err := createServerReadIPs(c)
	if err != nil {
		return
	}

	imageInstall, _, err := image.PrepareImageInstall(c)
	if err != nil {
		return
	}

	stopped := c.Bool("stopped")
	cdrom := c.String("cdrom")

	// if stopped isn't set and a CDROM or image are present, start the server
	autoreboot := !stopped && (!noImage || cdrom != "")

	spec = brain.VirtualMachineSpec{
		VirtualMachine: brain.VirtualMachine{
			Name:                  c.VirtualMachineName("name").VirtualMachine,
			Autoreboot:            autoreboot,
			Cores:                 cores,
			Memory:                memory,
			ZoneName:              c.String("zone"),
			CdromURL:              c.String("cdrom"),
			HardwareProfile:       c.String("hwprofile"),
			HardwareProfileLocked: c.Bool("hwprofile-locked"),
		},
		Discs:   discs,
		IPs:     ipspec,
		Reimage: &imageInstall,
	}
	if noImage {
		spec.Reimage = nil
	}
	return
}

// createServerPrepDiscs checks to see if discs are valid and sets up a backup schedule (if any).
func createServerPrepDiscs(backupFrequency string, discs []brain.Disc) ([]brain.Disc, error) {
	if len(discs) == 0 {
		discs = append(discs, brain.Disc{Size: 25600})
	}

	for i := range discs {
		d, discErr := discs[i].Validate()
		if discErr != nil {
			return discs, discErr
		}
		discs[i] = *d
	}

	interval, err := backupScheduleIntervalFromWords(backupFrequency)
	if err != nil {
		return discs, err
	}

	if interval > 0 {
		if len(discs) > 0 {
			bs := defaultBackupSchedule()
			bs.Interval = interval
			discs[0].BackupSchedules = brain.BackupSchedules{bs}
		}
	}
	return discs, nil
}

// createServerReadArgs sets up the initial defaults, reads in the --disc, --cores and --memory flags
func createServerReadArgs(c *app.Context) (discs []brain.Disc, cores, memory int, err error) {
	discs = c.Discs("disc")
	cores = c.Int("cores")
	memory = c.Size("memory")
	if memory == 0 {
		memory = 1024
	}
	return
}

// createServerReadIPs reads the IP flags and creates an IPSpec
func createServerReadIPs(c *app.Context) (ipspec *brain.IPSpec, err error) {
	ips := c.IPs("ip")

	if len(ips) > 2 {
		err = c.Help("A maximum of one IPv4 and one IPv6 address may be specified")
		return
	}

	if len(ips) > 0 {
		ipspec = &brain.IPSpec{}

		for _, ip := range ips {
			if ip.To4() != nil {
				if ipspec.IPv4 != "" {
					err = c.Help("A maximum of one IPv4 and one IPv6 address may be specified")
					return
				}
				ipspec.IPv4 = ip.To4().String()
			} else {
				if ipspec.IPv6 != "" {
					err = c.Help("A maximum of one IPv4 and one IPv6 address may be specified")
					return

				}
				ipspec.IPv6 = ip.String()
			}
		}
	}
	return
}

// backupScheduleIntervalFromWords deteremines the backup interval
func backupScheduleIntervalFromWords(words string) (freq int, err error) {
	switch words {
	case "daily":
		freq = 86400
	case "weekly":
		freq = 7 * 86400
	case "never":
		// the brain will reject a -1 - so even if the frequency accidentally
		// makes it to the brain the schedule won't be made
		freq = -1
	default:
		err = fmt.Errorf("invalid backup frequency '%s'", words)
	}
	return

}

// defaultBackupSchedule returns a schedule that will backup every week (well - every 604800 seconds)
// starting from midnight tonight.
func defaultBackupSchedule() brain.BackupSchedule {
	tomorrow := time.Now().Add(24 * time.Hour)
	y, m, d := tomorrow.Date()
	midnightTonight := time.Date(y, m, d, 0, 0, 0, 0, time.Local)
	defaultStartDate := midnightTonight.Format("2006-01-02 15:04:05 MST")
	return brain.BackupSchedule{
		StartDate: defaultStartDate,
		Interval:  7 * 86400,
		Capacity:  1,
	}
}

// CreatedVirtualMachine is a struct containing the vm object returned by the VM after creation, and the spec that went into creating it.
// TODO(telyn): move this type into lib/brain?
type CreatedVirtualMachine struct {
	Spec           brain.VirtualMachineSpec `json:"spec"`
	VirtualMachine brain.VirtualMachine     `json:"virtual_machine"`
}

// DefaultFields returns the list of default fields to feed to github.com/BytemarkHosting/row.From for this type.
func (cvm CreatedVirtualMachine) DefaultFields(f output.Format) string {
	return "Spec, VirtualMachine"
}

// PrettyPrint outputs this created virtual machine in a vaguely nice format to the given writer. detail is ignored.
func (cvm CreatedVirtualMachine) PrettyPrint(wr io.Writer, detail prettyprint.DetailLevel) (err error) {
	_, err = fmt.Fprintf(wr, "cloud server created successfully\r\n")
	if err != nil {
		return
	}

	err = cvm.VirtualMachine.PrettyPrint(wr, prettyprint.Full)
	if err != nil {
		return
	}
	if cvm.Spec.Reimage != nil {
		_, err = fmt.Fprintf(wr, "\r\nRoot password: %s\r\n", cvm.Spec.Reimage.RootPassword)
	} else {
		_, err = fmt.Fprintf(wr, "Machine was not imaged\r\n")
	}
	return
}
<commit_msg>in add server, be explict about being able to specify group and account in the server name<commit_after>package add

import (
	"fmt"
	"io"

	"time"

	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/args"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/flags"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/app/with"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/commands/image"
	"github.com/BytemarkHosting/bytemark-client/cmd/bytemark/util"
	"github.com/BytemarkHosting/bytemark-client/lib/brain"
	"github.com/BytemarkHosting/bytemark-client/lib/output"
	"github.com/BytemarkHosting/bytemark-client/lib/output/prettyprint"
	"github.com/BytemarkHosting/bytemark-client/util/log"
	"github.com/urfave/cli"
)

func init() {
	createServerCmd := cli.Command{
		Name:      "server",
		Usage:     `add a new server with bytemark`,
		UsageText: "add server [flags] <name> [<cores> [<memory [<disc specs>]...]]",
		Description: `Adds a Cloud Server with the given specification, defaulting to a basic server with Symbiosis installed and weekly backups of the first disc.

The server name can be used to specify which group and account the server should be created in, for example myserver.group1.myaccount.

A disc spec looks like the following: label:grade:size
The label and grade fields are optional. If grade is empty, defaults to sata.
If there are two fields, they are assumed to be grade and size.
Multiple --disc flags can be used to add multiple discs

If --backup is set then a backup of the first disk will be taken at the
frequency specified - never, daily, weekly or monthly. This backup will be free if
it's below a certain threshold of size. By default, a backup is taken every week.
This may cost money if your first disk is larger than the default.
See the price list for more details at http://www.bytemark.co.uk/prices

If --hwprofile-locked is set then the cloud server's virtual hardware won't be changed over time.`,
		Flags: append(app.OutputFlags("server", "object"),
			cli.IntFlag{
				Name:  "cores",
				Value: 1,
				Usage: "Number of CPU cores",
			},
			cli.StringFlag{
				Name:  "cdrom",
				Usage: "URL pointing to an ISO which will be attached to the cloud server as a CD",
			},
			cli.GenericFlag{
				Name:  "disc",
				Usage: "One or more disc specifications. Defaults to a single 25GiB sata-grade disc",
				Value: new(util.DiscSpecFlag),
			},
			flags.Force,
			cli.StringFlag{
				Name:  "hwprofile",
				Usage: "The hardware profile to use. Defaults to the current modern profile. See `bytemark profiles` for a list of hardware profiles available.",
			},
			cli.BoolFlag{
				Name:  "hwprofile-locked",
				Usage: "If set, the hardware profile will be 'locked', meaning that when Bytemark updates the hardware profiles your VM will keep its current one.",
			},
			cli.GenericFlag{
				Name:  "ip",
				Value: new(util.IPFlag),
				Usage: "Specify an IPv4 or IPv6 address to use. This will only be useful if you are creating the machine in a private VLAN.",
			},
			cli.GenericFlag{
				Name:  "memory",
				Value: new(util.SizeSpecFlag),
				Usage: "How much memory the server will have available, specified in GiB or with GiB/MiB units. Defaults to 1GiB.",
			},
			cli.GenericFlag{
				Name:  "name",
				Usage: "The new server's name",
				Value: new(app.VirtualMachineNameFlag),
			},
			cli.BoolFlag{
				Name:  "no-image",
				Usage: "Specifies that the server should not be imaged.",
			},
			cli.StringFlag{
				Name:  "backup",
				Usage: "Add a backup schedule for the first disk at the given frequency (daily, weekly, monthly, or never)",
				Value: "weekly",
			},
			cli.BoolFlag{
				Name:  "stopped",
				Usage: "If set, the server will not be started, even to image it.",
			},
			cli.StringFlag{
				Name:  "zone",
				Usage: "Which zone the server will be created in. See `bytemark zones` for the choices.",
			},
		),
		Action: app.Action(args.Optional("name", "cores", "memory", "disc"), with.RequiredFlags("name"), with.Auth, createServer),
	}
	createServerCmd.Flags = append(createServerCmd.Flags, image.ImageInstallFlags...)
	Commands = append(Commands, createServerCmd)
}

// createServer creates a server objec to be created by the brain and sends it.
func createServer(c *app.Context) (err error) {
	name := c.VirtualMachineName("name")
	spec, err := createServerPrepSpec(c)
	if err != nil {
		return
	}

	groupName := name.GroupName()
	err = c.Client().EnsureGroupName(&groupName)
	if err != nil {
		return
	}

	log.Logf("The following server will be created in %s:\r\n", groupName)
	err = spec.PrettyPrint(c.App().Writer, prettyprint.Full)
	if err != nil {
		return err
	}

	// If we're not forcing, prompt. If the prompt comes back false, exit.
	if !c.Bool("force") && !util.PromptYesNo(c.Prompter(), "Are you certain you wish to continue?") {
		log.Error("Exiting.")
		return util.UserRequestedExit{}
	}

	_, err = c.Client().CreateVirtualMachine(groupName, spec)
	if err != nil {
		return err
	}
	vm, err := c.Client().GetVirtualMachine(name)
	if err != nil {
		return
	}
	return c.OutputInDesiredForm(CreatedVirtualMachine{Spec: spec, VirtualMachine: vm})
}

// createServerPrepSpec sets up the server spec by reading in all the flags.
func createServerPrepSpec(c *app.Context) (spec brain.VirtualMachineSpec, err error) {
	noImage := c.Bool("no-image")
	backupFrequency := c.String("backup")

	discs, cores, memory, err := createServerReadArgs(c)
	if err != nil {
		return
	}

	discs, err = createServerPrepDiscs(backupFrequency, discs)
	if err != nil {
		return
	}

	ipspec, err := createServerReadIPs(c)
	if err != nil {
		return
	}

	imageInstall, _, err := image.PrepareImageInstall(c)
	if err != nil {
		return
	}

	stopped := c.Bool("stopped")
	cdrom := c.String("cdrom")

	// if stopped isn't set and a CDROM or image are present, start the server
	autoreboot := !stopped && (!noImage || cdrom != "")

	spec = brain.VirtualMachineSpec{
		VirtualMachine: brain.VirtualMachine{
			Name:                  c.VirtualMachineName("name").VirtualMachine,
			Autoreboot:            autoreboot,
			Cores:                 cores,
			Memory:                memory,
			ZoneName:              c.String("zone"),
			CdromURL:              c.String("cdrom"),
			HardwareProfile:       c.String("hwprofile"),
			HardwareProfileLocked: c.Bool("hwprofile-locked"),
		},
		Discs:   discs,
		IPs:     ipspec,
		Reimage: &imageInstall,
	}
	if noImage {
		spec.Reimage = nil
	}
	return
}

// createServerPrepDiscs checks to see if discs are valid and sets up a backup schedule (if any).
func createServerPrepDiscs(backupFrequency string, discs []brain.Disc) ([]brain.Disc, error) {
	if len(discs) == 0 {
		discs = append(discs, brain.Disc{Size: 25600})
	}

	for i := range discs {
		d, discErr := discs[i].Validate()
		if discErr != nil {
			return discs, discErr
		}
		discs[i] = *d
	}

	interval, err := backupScheduleIntervalFromWords(backupFrequency)
	if err != nil {
		return discs, err
	}

	if interval > 0 {
		if len(discs) > 0 {
			bs := defaultBackupSchedule()
			bs.Interval = interval
			discs[0].BackupSchedules = brain.BackupSchedules{bs}
		}
	}
	return discs, nil
}

// createServerReadArgs sets up the initial defaults, reads in the --disc, --cores and --memory flags
func createServerReadArgs(c *app.Context) (discs []brain.Disc, cores, memory int, err error) {
	discs = c.Discs("disc")
	cores = c.Int("cores")
	memory = c.Size("memory")
	if memory == 0 {
		memory = 1024
	}
	return
}

// createServerReadIPs reads the IP flags and creates an IPSpec
func createServerReadIPs(c *app.Context) (ipspec *brain.IPSpec, err error) {
	ips := c.IPs("ip")

	if len(ips) > 2 {
		err = c.Help("A maximum of one IPv4 and one IPv6 address may be specified")
		return
	}

	if len(ips) > 0 {
		ipspec = &brain.IPSpec{}

		for _, ip := range ips {
			if ip.To4() != nil {
				if ipspec.IPv4 != "" {
					err = c.Help("A maximum of one IPv4 and one IPv6 address may be specified")
					return
				}
				ipspec.IPv4 = ip.To4().String()
			} else {
				if ipspec.IPv6 != "" {
					err = c.Help("A maximum of one IPv4 and one IPv6 address may be specified")
					return

				}
				ipspec.IPv6 = ip.String()
			}
		}
	}
	return
}

// backupScheduleIntervalFromWords deteremines the backup interval
func backupScheduleIntervalFromWords(words string) (freq int, err error) {
	switch words {
	case "daily":
		freq = 86400
	case "weekly":
		freq = 7 * 86400
	case "never":
		// the brain will reject a -1 - so even if the frequency accidentally
		// makes it to the brain the schedule won't be made
		freq = -1
	default:
		err = fmt.Errorf("invalid backup frequency '%s'", words)
	}
	return

}

// defaultBackupSchedule returns a schedule that will backup every week (well - every 604800 seconds)
// starting from midnight tonight.
func defaultBackupSchedule() brain.BackupSchedule {
	tomorrow := time.Now().Add(24 * time.Hour)
	y, m, d := tomorrow.Date()
	midnightTonight := time.Date(y, m, d, 0, 0, 0, 0, time.Local)
	defaultStartDate := midnightTonight.Format("2006-01-02 15:04:05 MST")
	return brain.BackupSchedule{
		StartDate: defaultStartDate,
		Interval:  7 * 86400,
		Capacity:  1,
	}
}

// CreatedVirtualMachine is a struct containing the vm object returned by the VM after creation, and the spec that went into creating it.
// TODO(telyn): move this type into lib/brain?
type CreatedVirtualMachine struct {
	Spec           brain.VirtualMachineSpec `json:"spec"`
	VirtualMachine brain.VirtualMachine     `json:"virtual_machine"`
}

// DefaultFields returns the list of default fields to feed to github.com/BytemarkHosting/row.From for this type.
func (cvm CreatedVirtualMachine) DefaultFields(f output.Format) string {
	return "Spec, VirtualMachine"
}

// PrettyPrint outputs this created virtual machine in a vaguely nice format to the given writer. detail is ignored.
func (cvm CreatedVirtualMachine) PrettyPrint(wr io.Writer, detail prettyprint.DetailLevel) (err error) {
	_, err = fmt.Fprintf(wr, "cloud server created successfully\r\n")
	if err != nil {
		return
	}

	err = cvm.VirtualMachine.PrettyPrint(wr, prettyprint.Full)
	if err != nil {
		return
	}
	if cvm.Spec.Reimage != nil {
		_, err = fmt.Fprintf(wr, "\r\nRoot password: %s\r\n", cvm.Spec.Reimage.RootPassword)
	} else {
		_, err = fmt.Fprintf(wr, "Machine was not imaged\r\n")
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package master

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"strings"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
	"k8s.io/kubernetes/cmd/kubeadm/app/images"
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/unversioned"
	api "k8s.io/kubernetes/pkg/api/v1"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/util/intstr"
)

// Static pod definitions in golang form are included below so that `kubeadm init` can get going.

const (
	DefaultClusterName     = "kubernetes"
	DefaultCloudConfigPath = "/etc/kubernetes/cloud-config.json"

	etcd                  = "etcd"
	apiServer             = "apiserver"
	controllerManager     = "controller-manager"
	scheduler             = "scheduler"
	proxy                 = "proxy"
	kubeAPIServer         = "kube-apiserver"
	kubeControllerManager = "kube-controller-manager"
	kubeScheduler         = "kube-scheduler"
	kubeProxy             = "kube-proxy"
	pkiDir                = "/etc/kubernetes/pki"
)

// WriteStaticPodManifests builds manifest objects based on user provided configuration and then dumps it to disk
// where kubelet will pick and schedule them.
func WriteStaticPodManifests(s *kubeadmapi.KubeadmConfig) error {
	// Prepare static pod specs
	staticPodSpecs := map[string]api.Pod{
		kubeAPIServer: componentPod(api.Container{
			Name:          kubeAPIServer,
			Image:         images.GetCoreImage(images.KubeAPIServerImage, s, s.EnvParams["hyperkube_image"]),
			Command:       getComponentCommand(apiServer, s),
			VolumeMounts:  []api.VolumeMount{certsVolumeMount(), k8sVolumeMount()},
			LivenessProbe: componentProbe(8080, "/healthz"),
			Resources:     componentResources("250m"),
		}, certsVolume(s), k8sVolume(s)),
		kubeControllerManager: componentPod(api.Container{
			Name:          kubeControllerManager,
			Image:         images.GetCoreImage(images.KubeControllerManagerImage, s, s.EnvParams["hyperkube_image"]),
			Command:       getComponentCommand(controllerManager, s),
			VolumeMounts:  []api.VolumeMount{k8sVolumeMount()},
			LivenessProbe: componentProbe(10252, "/healthz"),
			Resources:     componentResources("200m"),
		}, k8sVolume(s)),
		kubeScheduler: componentPod(api.Container{
			Name:          kubeScheduler,
			Image:         images.GetCoreImage(images.KubeSchedulerImage, s, s.EnvParams["hyperkube_image"]),
			Command:       getComponentCommand(scheduler, s),
			LivenessProbe: componentProbe(10251, "/healthz"),
			Resources:     componentResources("100m"),
		}),
	}

	// Add etcd static pod spec only if external etcd is not configured
	if len(s.InitFlags.API.Etcd.ExternalEndpoints) == 0 {
		staticPodSpecs[etcd] = componentPod(api.Container{
			Name: etcd,
			Command: []string{
				"etcd",
				"--listen-client-urls=http://127.0.0.1:2379",
				"--advertise-client-urls=http://127.0.0.1:2379",
				"--data-dir=/var/etcd/data",
			},
			VolumeMounts:  []api.VolumeMount{certsVolumeMount(), etcdVolumeMount(), k8sVolumeMount()},
			Image:         images.GetCoreImage(images.KubeEtcdImage, s, s.EnvParams["etcd_image"]),
			LivenessProbe: componentProbe(2379, "/health"),
			Resources:     componentResources("200m"),
			SecurityContext: &api.SecurityContext{
				SELinuxOptions: &api.SELinuxOptions{
					// TODO: This implies our etcd container is not being restricted by
					// SELinux. This is not optimal and would be nice to adjust in future
					// so it can create and write /var/lib/etcd, but for now this avoids
					// recommending setenforce 0 system-wide.
					Type: "unconfined_t",
				},
			},
		}, certsVolume(s), etcdVolume(s), k8sVolume(s))
	}

	manifestsPath := path.Join(s.EnvParams["kubernetes_dir"], "manifests")
	if err := os.MkdirAll(manifestsPath, 0700); err != nil {
		return fmt.Errorf("<master/manifests> failed to create directory %q [%v]", manifestsPath, err)
	}
	for name, spec := range staticPodSpecs {
		filename := path.Join(manifestsPath, name+".json")
		serialized, err := json.MarshalIndent(spec, "", "  ")
		if err != nil {
			return fmt.Errorf("<master/manifests> failed to marshall manifest for %q to JSON [%v]", name, err)
		}
		if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), filename); err != nil {
			return fmt.Errorf("<master/manifests> failed to create static pod manifest file for %q (%q) [%v]", name, filename, err)
		}
	}
	return nil
}

// etcdVolume exposes a path on the host in order to guarantee data survival during reboot.
func etcdVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
	return api.Volume{
		Name: "etcd",
		VolumeSource: api.VolumeSource{
			HostPath: &api.HostPathVolumeSource{Path: s.EnvParams["host_etcd_path"]},
		},
	}
}

func etcdVolumeMount() api.VolumeMount {
	return api.VolumeMount{
		Name:      "etcd",
		MountPath: "/var/etcd",
	}
}

// certsVolume exposes host SSL certificates to pod containers.
func certsVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
	return api.Volume{
		Name: "certs",
		VolumeSource: api.VolumeSource{
			// TODO(phase1+) make path configurable
			HostPath: &api.HostPathVolumeSource{Path: "/etc/ssl/certs"},
		},
	}
}

func certsVolumeMount() api.VolumeMount {
	return api.VolumeMount{
		Name:      "certs",
		MountPath: "/etc/ssl/certs",
	}
}

func k8sVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
	return api.Volume{
		Name: "pki",
		VolumeSource: api.VolumeSource{
			HostPath: &api.HostPathVolumeSource{Path: s.EnvParams["kubernetes_dir"]},
		},
	}
}

func k8sVolumeMount() api.VolumeMount {
	return api.VolumeMount{
		Name:      "pki",
		MountPath: "/etc/kubernetes/",
		ReadOnly:  true,
	}
}

func componentResources(cpu string) api.ResourceRequirements {
	return api.ResourceRequirements{
		Requests: api.ResourceList{
			api.ResourceName(api.ResourceCPU): resource.MustParse(cpu),
		},
	}
}

func componentProbe(port int, path string) *api.Probe {
	return &api.Probe{
		Handler: api.Handler{
			HTTPGet: &api.HTTPGetAction{
				Host: "127.0.0.1",
				Path: path,
				Port: intstr.FromInt(port),
			},
		},
		InitialDelaySeconds: 15,
		TimeoutSeconds:      15,
	}
}

func componentPod(container api.Container, volumes ...api.Volume) api.Pod {
	return api.Pod{
		TypeMeta: unversioned.TypeMeta{
			APIVersion: "v1",
			Kind:       "Pod",
		},
		ObjectMeta: api.ObjectMeta{
			Name:      container.Name,
			Namespace: "kube-system",
			Labels:    map[string]string{"component": container.Name, "tier": "control-plane"},
		},
		Spec: api.PodSpec{
			Containers:  []api.Container{container},
			HostNetwork: true,
			Volumes:     volumes,
		},
	}
}

func getComponentCommand(component string, s *kubeadmapi.KubeadmConfig) (command []string) {
	baseFlags := map[string][]string{
		apiServer: {
			"--insecure-bind-address=127.0.0.1",
			"--etcd-servers=http://127.0.0.1:2379",
			"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
			"--service-cluster-ip-range=" + s.InitFlags.Services.CIDR.String(),
			"--service-account-key-file=" + pkiDir + "/apiserver-key.pem",
			"--client-ca-file=" + pkiDir + "/ca.pem",
			"--tls-cert-file=" + pkiDir + "/apiserver.pem",
			"--tls-private-key-file=" + pkiDir + "/apiserver-key.pem",
			"--token-auth-file=" + pkiDir + "/tokens.csv",
			"--secure-port=443",
			"--allow-privileged",
		},
		controllerManager: {
			"--address=127.0.0.1",
			"--leader-elect",
			"--master=127.0.0.1:8080",
			"--cluster-name=" + DefaultClusterName,
			"--root-ca-file=" + pkiDir + "/ca.pem",
			"--service-account-private-key-file=" + pkiDir + "/apiserver-key.pem",
			"--cluster-signing-cert-file=" + pkiDir + "/ca.pem",
			"--cluster-signing-key-file=" + pkiDir + "/ca-key.pem",
			"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap",
		},
		scheduler: {
			"--address=127.0.0.1",
			"--leader-elect",
			"--master=127.0.0.1:8080",
		},
		proxy: {},
	}

	if s.EnvParams["hyperkube_image"] != "" {
		command = []string{"/hyperkube", component}
	} else {
		command = []string{"/usr/local/bin/kube-" + component}
	}

	command = append(command, s.EnvParams["component_loglevel"])
	command = append(command, baseFlags[component]...)

	if component == apiServer {
		// Check if the user decided to use an external etcd cluster
		if len(s.InitFlags.API.Etcd.ExternalEndpoints) > 0 {
			command = append(command, fmt.Sprintf("--etcd-servers=%s", strings.Join(s.InitFlags.API.Etcd.ExternalEndpoints, ",")))
		} else {
			command = append(command, "--etcd-servers=http://127.0.0.1:2379")
		}

		// Is etcd secured?
		if s.InitFlags.API.Etcd.ExternalCAFile != "" {
			command = append(command, fmt.Sprintf("--etcd-cafile=%s", s.InitFlags.API.Etcd.ExternalCAFile))
		}
		if s.InitFlags.API.Etcd.ExternalCertFile != "" && s.InitFlags.API.Etcd.ExternalKeyFile != "" {
			etcdClientFileArg := fmt.Sprintf("--etcd-certfile=%s", s.InitFlags.API.Etcd.ExternalCertFile)
			etcdKeyFileArg := fmt.Sprintf("--etcd-keyfile=%s", s.InitFlags.API.Etcd.ExternalKeyFile)
			command = append(command, etcdClientFileArg, etcdKeyFileArg)
		}
	}

	if component == controllerManager {
		if s.InitFlags.CloudProvider != "" {
			command = append(command, "--cloud-provider="+s.InitFlags.CloudProvider)

			// Only append the --cloud-config option if there's a such file
			// TODO(phase1+) this won't work unless it's in one of the few directories we bind-mount
			if _, err := os.Stat(DefaultCloudConfigPath); err == nil {
				command = append(command, "--cloud-config="+DefaultCloudConfigPath)
			}
		}

		if s.InitFlags.PodNetwork.CIDR.IP != nil {
			// Let the controller-manager allocate Node CIDRs for the Pod network.
			// Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
			command = append(command, "--allocate-node-cidrs=true", "--cluster-cidr="+s.InitFlags.PodNetwork.CIDR.String())
		}
	}

	return
}
<commit_msg>Set FailureTreshold to 8 for kubeadm components in order to not restart unless really necessary<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package master

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path"
	"strings"

	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
	"k8s.io/kubernetes/cmd/kubeadm/app/images"
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/unversioned"
	api "k8s.io/kubernetes/pkg/api/v1"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/util/intstr"
)

// Static pod definitions in golang form are included below so that `kubeadm init` can get going.

const (
	DefaultClusterName     = "kubernetes"
	DefaultCloudConfigPath = "/etc/kubernetes/cloud-config.json"

	etcd                  = "etcd"
	apiServer             = "apiserver"
	controllerManager     = "controller-manager"
	scheduler             = "scheduler"
	proxy                 = "proxy"
	kubeAPIServer         = "kube-apiserver"
	kubeControllerManager = "kube-controller-manager"
	kubeScheduler         = "kube-scheduler"
	kubeProxy             = "kube-proxy"
	pkiDir                = "/etc/kubernetes/pki"
)

// WriteStaticPodManifests builds manifest objects based on user provided configuration and then dumps it to disk
// where kubelet will pick and schedule them.
func WriteStaticPodManifests(s *kubeadmapi.KubeadmConfig) error {
	// Prepare static pod specs
	staticPodSpecs := map[string]api.Pod{
		kubeAPIServer: componentPod(api.Container{
			Name:          kubeAPIServer,
			Image:         images.GetCoreImage(images.KubeAPIServerImage, s, s.EnvParams["hyperkube_image"]),
			Command:       getComponentCommand(apiServer, s),
			VolumeMounts:  []api.VolumeMount{certsVolumeMount(), k8sVolumeMount()},
			LivenessProbe: componentProbe(8080, "/healthz"),
			Resources:     componentResources("250m"),
		}, certsVolume(s), k8sVolume(s)),
		kubeControllerManager: componentPod(api.Container{
			Name:          kubeControllerManager,
			Image:         images.GetCoreImage(images.KubeControllerManagerImage, s, s.EnvParams["hyperkube_image"]),
			Command:       getComponentCommand(controllerManager, s),
			VolumeMounts:  []api.VolumeMount{k8sVolumeMount()},
			LivenessProbe: componentProbe(10252, "/healthz"),
			Resources:     componentResources("200m"),
		}, k8sVolume(s)),
		kubeScheduler: componentPod(api.Container{
			Name:          kubeScheduler,
			Image:         images.GetCoreImage(images.KubeSchedulerImage, s, s.EnvParams["hyperkube_image"]),
			Command:       getComponentCommand(scheduler, s),
			LivenessProbe: componentProbe(10251, "/healthz"),
			Resources:     componentResources("100m"),
		}),
	}

	// Add etcd static pod spec only if external etcd is not configured
	if len(s.InitFlags.API.Etcd.ExternalEndpoints) == 0 {
		staticPodSpecs[etcd] = componentPod(api.Container{
			Name: etcd,
			Command: []string{
				"etcd",
				"--listen-client-urls=http://127.0.0.1:2379",
				"--advertise-client-urls=http://127.0.0.1:2379",
				"--data-dir=/var/etcd/data",
			},
			VolumeMounts:  []api.VolumeMount{certsVolumeMount(), etcdVolumeMount(), k8sVolumeMount()},
			Image:         images.GetCoreImage(images.KubeEtcdImage, s, s.EnvParams["etcd_image"]),
			LivenessProbe: componentProbe(2379, "/health"),
			Resources:     componentResources("200m"),
			SecurityContext: &api.SecurityContext{
				SELinuxOptions: &api.SELinuxOptions{
					// TODO: This implies our etcd container is not being restricted by
					// SELinux. This is not optimal and would be nice to adjust in future
					// so it can create and write /var/lib/etcd, but for now this avoids
					// recommending setenforce 0 system-wide.
					Type: "unconfined_t",
				},
			},
		}, certsVolume(s), etcdVolume(s), k8sVolume(s))
	}

	manifestsPath := path.Join(s.EnvParams["kubernetes_dir"], "manifests")
	if err := os.MkdirAll(manifestsPath, 0700); err != nil {
		return fmt.Errorf("<master/manifests> failed to create directory %q [%v]", manifestsPath, err)
	}
	for name, spec := range staticPodSpecs {
		filename := path.Join(manifestsPath, name+".json")
		serialized, err := json.MarshalIndent(spec, "", "  ")
		if err != nil {
			return fmt.Errorf("<master/manifests> failed to marshall manifest for %q to JSON [%v]", name, err)
		}
		if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), filename); err != nil {
			return fmt.Errorf("<master/manifests> failed to create static pod manifest file for %q (%q) [%v]", name, filename, err)
		}
	}
	return nil
}

// etcdVolume exposes a path on the host in order to guarantee data survival during reboot.
func etcdVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
	return api.Volume{
		Name: "etcd",
		VolumeSource: api.VolumeSource{
			HostPath: &api.HostPathVolumeSource{Path: s.EnvParams["host_etcd_path"]},
		},
	}
}

func etcdVolumeMount() api.VolumeMount {
	return api.VolumeMount{
		Name:      "etcd",
		MountPath: "/var/etcd",
	}
}

// certsVolume exposes host SSL certificates to pod containers.
func certsVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
	return api.Volume{
		Name: "certs",
		VolumeSource: api.VolumeSource{
			// TODO(phase1+) make path configurable
			HostPath: &api.HostPathVolumeSource{Path: "/etc/ssl/certs"},
		},
	}
}

func certsVolumeMount() api.VolumeMount {
	return api.VolumeMount{
		Name:      "certs",
		MountPath: "/etc/ssl/certs",
	}
}

func k8sVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
	return api.Volume{
		Name: "pki",
		VolumeSource: api.VolumeSource{
			HostPath: &api.HostPathVolumeSource{Path: s.EnvParams["kubernetes_dir"]},
		},
	}
}

func k8sVolumeMount() api.VolumeMount {
	return api.VolumeMount{
		Name:      "pki",
		MountPath: "/etc/kubernetes/",
		ReadOnly:  true,
	}
}

func componentResources(cpu string) api.ResourceRequirements {
	return api.ResourceRequirements{
		Requests: api.ResourceList{
			api.ResourceName(api.ResourceCPU): resource.MustParse(cpu),
		},
	}
}

func componentProbe(port int, path string) *api.Probe {
	return &api.Probe{
		Handler: api.Handler{
			HTTPGet: &api.HTTPGetAction{
				Host: "127.0.0.1",
				Path: path,
				Port: intstr.FromInt(port),
			},
		},
		InitialDelaySeconds: 15,
		TimeoutSeconds:      15,
		FailureThreshold:    8,
	}
}

func componentPod(container api.Container, volumes ...api.Volume) api.Pod {
	return api.Pod{
		TypeMeta: unversioned.TypeMeta{
			APIVersion: "v1",
			Kind:       "Pod",
		},
		ObjectMeta: api.ObjectMeta{
			Name:      container.Name,
			Namespace: "kube-system",
			Labels:    map[string]string{"component": container.Name, "tier": "control-plane"},
		},
		Spec: api.PodSpec{
			Containers:  []api.Container{container},
			HostNetwork: true,
			Volumes:     volumes,
		},
	}
}

func getComponentCommand(component string, s *kubeadmapi.KubeadmConfig) (command []string) {
	baseFlags := map[string][]string{
		apiServer: {
			"--insecure-bind-address=127.0.0.1",
			"--etcd-servers=http://127.0.0.1:2379",
			"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
			"--service-cluster-ip-range=" + s.InitFlags.Services.CIDR.String(),
			"--service-account-key-file=" + pkiDir + "/apiserver-key.pem",
			"--client-ca-file=" + pkiDir + "/ca.pem",
			"--tls-cert-file=" + pkiDir + "/apiserver.pem",
			"--tls-private-key-file=" + pkiDir + "/apiserver-key.pem",
			"--token-auth-file=" + pkiDir + "/tokens.csv",
			"--secure-port=443",
			"--allow-privileged",
		},
		controllerManager: {
			"--address=127.0.0.1",
			"--leader-elect",
			"--master=127.0.0.1:8080",
			"--cluster-name=" + DefaultClusterName,
			"--root-ca-file=" + pkiDir + "/ca.pem",
			"--service-account-private-key-file=" + pkiDir + "/apiserver-key.pem",
			"--cluster-signing-cert-file=" + pkiDir + "/ca.pem",
			"--cluster-signing-key-file=" + pkiDir + "/ca-key.pem",
			"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap",
		},
		scheduler: {
			"--address=127.0.0.1",
			"--leader-elect",
			"--master=127.0.0.1:8080",
		},
		proxy: {},
	}

	if s.EnvParams["hyperkube_image"] != "" {
		command = []string{"/hyperkube", component}
	} else {
		command = []string{"/usr/local/bin/kube-" + component}
	}

	command = append(command, s.EnvParams["component_loglevel"])
	command = append(command, baseFlags[component]...)

	if component == apiServer {
		// Check if the user decided to use an external etcd cluster
		if len(s.InitFlags.API.Etcd.ExternalEndpoints) > 0 {
			command = append(command, fmt.Sprintf("--etcd-servers=%s", strings.Join(s.InitFlags.API.Etcd.ExternalEndpoints, ",")))
		} else {
			command = append(command, "--etcd-servers=http://127.0.0.1:2379")
		}

		// Is etcd secured?
		if s.InitFlags.API.Etcd.ExternalCAFile != "" {
			command = append(command, fmt.Sprintf("--etcd-cafile=%s", s.InitFlags.API.Etcd.ExternalCAFile))
		}
		if s.InitFlags.API.Etcd.ExternalCertFile != "" && s.InitFlags.API.Etcd.ExternalKeyFile != "" {
			etcdClientFileArg := fmt.Sprintf("--etcd-certfile=%s", s.InitFlags.API.Etcd.ExternalCertFile)
			etcdKeyFileArg := fmt.Sprintf("--etcd-keyfile=%s", s.InitFlags.API.Etcd.ExternalKeyFile)
			command = append(command, etcdClientFileArg, etcdKeyFileArg)
		}
	}

	if component == controllerManager {
		if s.InitFlags.CloudProvider != "" {
			command = append(command, "--cloud-provider="+s.InitFlags.CloudProvider)

			// Only append the --cloud-config option if there's a such file
			// TODO(phase1+) this won't work unless it's in one of the few directories we bind-mount
			if _, err := os.Stat(DefaultCloudConfigPath); err == nil {
				command = append(command, "--cloud-config="+DefaultCloudConfigPath)
			}
		}

		if s.InitFlags.PodNetwork.CIDR.IP != nil {
			// Let the controller-manager allocate Node CIDRs for the Pod network.
			// Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
			command = append(command, "--allocate-node-cidrs=true", "--cluster-cidr="+s.InitFlags.PodNetwork.CIDR.String())
		}
	}

	return
}
<|endoftext|>


<commit_before>// Copyright 2020 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The disk_unlock command is used to unlock a disk drive as follows:
// 1. Via BMC, read a 32-byte secret seed known as the Host Secret Seed (HSS)
//    using the OpenBMC IPMI blob transfer protocol
// 2. Compute a password as follows:
//	We get the deterministically computed 32-byte HDKF-SHA256 using:
//	- salt: "SKM PROD_V2 ACCESS"
//	- hss: 32-byte HSS
//	- device identity: strings formed by concatenating the assembly serial
//	  number, the _ character, and the assembly part number.
// 3. Unlock the drive with the given password
// 4. Update the partition table for the disk
package main

import (
	"crypto/sha256"
	"flag"
	"fmt"
	"io"
	"log"
	"path/filepath"
	"strings"

	"github.com/u-root/u-root/pkg/ipmi"
	"github.com/u-root/u-root/pkg/ipmi/blobs"
	"github.com/u-root/u-root/pkg/mount/block"
	"github.com/u-root/u-root/pkg/mount/scuzz"
	"golang.org/x/crypto/hkdf"
)

const (
	hostSecretSeedLen = 32

	passwordSalt = "SKM PROD_V2 ACCESS"
)

var (
	disk               = flag.String("disk", "/dev/sda", "The disk to be unlocked")
	verbose            = flag.Bool("d", false, "print debug output")
	verboseNoSanitize  = flag.Bool("dangerously-disable-sanitize", false, "Print sensitive information - this should only be used for testing!")
	noRereadPartitions = flag.Bool("no-reread-partitions", false, "Only attempt to unlock the disk, don't re-read the partition table.")
)

func verboseLog(msg string) {
	if *verbose {
		log.Print(msg)
	}
}

// readHssBlob reads a host secret seed from the given blob id.
func readHssBlob(id string, h *blobs.BlobHandler) (data []uint8, rerr error) {
	sessionID, err := h.BlobOpen(id, blobs.BMC_BLOB_OPEN_FLAG_READ)
	if err != nil {
		return nil, fmt.Errorf("IPMI BlobOpen for %s failed: %v", id, err)
	}
	defer func() {
		// If the function returned successfully but failed to close the blob,
		// return an error.
		if err := h.BlobClose(sessionID); err != nil && rerr == nil {
			rerr = fmt.Errorf("IPMI BlobClose %s failed: %v", id, err)
		}
	}()

	data, err = h.BlobRead(sessionID, 0, hostSecretSeedLen)
	if err != nil {
		return nil, fmt.Errorf("IPMI BlobRead %s failed: %v", id, err)
	}

	if len(data) != hostSecretSeedLen {
		return nil, fmt.Errorf("HSS size incorrect: got %d for %s", len(data), id)
	}

	return data, nil
}

// getAllHss reads all host secret seeds over IPMI.
func getAllHss() ([][]uint8, error) {
	i, err := ipmi.Open(0)
	if err != nil {
		return nil, err
	}
	h := blobs.NewBlobHandler(i)

	blobCount, err := h.BlobGetCount()
	if err != nil {
		return nil, fmt.Errorf("failed to get blob count: %v", err)
	}

	hssList := [][]uint8{}
	skmSubstr := "/skm/hss/"

	// Read from all */skm/hss/* blobs.
	for j := 0; j < blobCount; j++ {
		id, err := h.BlobEnumerate(j)
		if err != nil {
			return nil, fmt.Errorf("failed to enumerate blob %d: %v", j, err)
		}

		if !strings.Contains(id, skmSubstr) {
			continue
		}

		hss, err := readHssBlob(id, h)
		if err != nil {
			log.Printf("failed to read HSS of id %s: %v", id, err)
		} else {
			msg := fmt.Sprintf("HSS Entry: Id=%s", id)
			if *verboseNoSanitize {
				msg = msg + fmt.Sprintf(",Seed=%x", hss)
			}
			verboseLog(msg)
			hssList = append(hssList, hss)
		}
	}

	return hssList, nil
}

// Compute the password deterministically as the 32-byte HDKF-SHA256 of the
// HSS plus the device identity.
func genPassword(hss []byte, info *scuzz.Info) ([]byte, error) {
	hash := sha256.New
	devID := fmt.Sprintf("%s_%s", info.Serial, info.Model)

	r := hkdf.New(hash, hss, ([]byte)(passwordSalt), ([]byte)(devID))
	key := make([]byte, 32)

	if _, err := io.ReadFull(r, key); err != nil {
		return nil, err
	}
	return key, nil
}

func main() {
	// Obtain 32 byte Host Secret Seed (HSS) from IPMI.
	hssList, err := getAllHss()
	if err != nil {
		log.Fatalf("error getting HSS: %v", err)
	}

	if len(hssList) == 0 {
		log.Fatalf("no HSS found - can't unlock disk.")
	}

	verboseLog(fmt.Sprintf("Found %d Host Secret Seeds.", len(hssList)))

	// Open the disk. Read its identity, and use it to unlock the disk.
	sgdisk, err := scuzz.NewSGDisk(*disk)
	if err != nil {
		log.Fatalf("failed to open disk %v: %v", *disk, err)
	}

	info, err := sgdisk.Identify()
	if err != nil {
		log.Fatalf("failed to read disk %v identity: %v", *disk, err)
	}

	verboseLog(fmt.Sprintf("Disk info for %s: %s", *disk, info.String()))

	// Try using each HSS to unlock the disk - only 1 should work.
	unlocked := false
	for i, hss := range hssList {
		key, err := genPassword(hss, info)
		if err != nil {
			log.Printf("Couldn't generate password with HSS %d: %v", i, err)
			continue
		}

		if err := sgdisk.Unlock((string)(key), false); err != nil {
			log.Printf("Couldn't unlock disk with HSS %d: %v", i, err)
		} else {
			unlocked = true
			break
		}
	}

	if unlocked {
		log.Printf("Successfully unlocked disk %s.", *disk)
	} else {
		log.Fatalf("Failed to unlock disk %s with any HSS.", *disk)
	}

	if *noRereadPartitions {
		return
	}

	// Update partitions on the on the disk.
	diskdev, err := block.Device(*disk)
	if err != nil {
		log.Fatalf("Could not find %s: %v", *disk, err)
	}

	if err := diskdev.ReadPartitionTable(); err != nil {
		log.Fatalf("Could not re-read partition table: %v", err)
	}

	glob := filepath.Join("/sys/class/block", diskdev.Name+"*")
	parts, err := filepath.Glob(glob)
	if err != nil {
		log.Fatalf("Could not find disk partitions: %v", err)
	}

	verboseLog(fmt.Sprintf("Found these %s unlocked partitions: %v", *disk, parts))

}
<commit_msg>Add flag.Parse() to disk_unlock<commit_after>// Copyright 2020 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The disk_unlock command is used to unlock a disk drive as follows:
// 1. Via BMC, read a 32-byte secret seed known as the Host Secret Seed (HSS)
//    using the OpenBMC IPMI blob transfer protocol
// 2. Compute a password as follows:
//	We get the deterministically computed 32-byte HDKF-SHA256 using:
//	- salt: "SKM PROD_V2 ACCESS"
//	- hss: 32-byte HSS
//	- device identity: strings formed by concatenating the assembly serial
//	  number, the _ character, and the assembly part number.
// 3. Unlock the drive with the given password
// 4. Update the partition table for the disk
package main

import (
	"crypto/sha256"
	"flag"
	"fmt"
	"io"
	"log"
	"path/filepath"
	"strings"

	"github.com/u-root/u-root/pkg/ipmi"
	"github.com/u-root/u-root/pkg/ipmi/blobs"
	"github.com/u-root/u-root/pkg/mount/block"
	"github.com/u-root/u-root/pkg/mount/scuzz"
	"golang.org/x/crypto/hkdf"
)

const (
	hostSecretSeedLen = 32

	passwordSalt = "SKM PROD_V2 ACCESS"
)

var (
	disk               = flag.String("disk", "/dev/sda", "The disk to be unlocked")
	verbose            = flag.Bool("d", false, "print debug output")
	verboseNoSanitize  = flag.Bool("dangerously-disable-sanitize", false, "Print sensitive information - this should only be used for testing!")
	noRereadPartitions = flag.Bool("no-reread-partitions", false, "Only attempt to unlock the disk, don't re-read the partition table.")
)

func verboseLog(msg string) {
	if *verbose {
		log.Print(msg)
	}
}

// readHssBlob reads a host secret seed from the given blob id.
func readHssBlob(id string, h *blobs.BlobHandler) (data []uint8, rerr error) {
	sessionID, err := h.BlobOpen(id, blobs.BMC_BLOB_OPEN_FLAG_READ)
	if err != nil {
		return nil, fmt.Errorf("IPMI BlobOpen for %s failed: %v", id, err)
	}
	defer func() {
		// If the function returned successfully but failed to close the blob,
		// return an error.
		if err := h.BlobClose(sessionID); err != nil && rerr == nil {
			rerr = fmt.Errorf("IPMI BlobClose %s failed: %v", id, err)
		}
	}()

	data, err = h.BlobRead(sessionID, 0, hostSecretSeedLen)
	if err != nil {
		return nil, fmt.Errorf("IPMI BlobRead %s failed: %v", id, err)
	}

	if len(data) != hostSecretSeedLen {
		return nil, fmt.Errorf("HSS size incorrect: got %d for %s", len(data), id)
	}

	return data, nil
}

// getAllHss reads all host secret seeds over IPMI.
func getAllHss() ([][]uint8, error) {
	i, err := ipmi.Open(0)
	if err != nil {
		return nil, err
	}
	h := blobs.NewBlobHandler(i)

	blobCount, err := h.BlobGetCount()
	if err != nil {
		return nil, fmt.Errorf("failed to get blob count: %v", err)
	}

	hssList := [][]uint8{}
	skmSubstr := "/skm/hss/"

	// Read from all */skm/hss/* blobs.
	for j := 0; j < blobCount; j++ {
		id, err := h.BlobEnumerate(j)
		if err != nil {
			return nil, fmt.Errorf("failed to enumerate blob %d: %v", j, err)
		}

		if !strings.Contains(id, skmSubstr) {
			continue
		}

		hss, err := readHssBlob(id, h)
		if err != nil {
			log.Printf("failed to read HSS of id %s: %v", id, err)
		} else {
			msg := fmt.Sprintf("HSS Entry: Id=%s", id)
			if *verboseNoSanitize {
				msg = msg + fmt.Sprintf(",Seed=%x", hss)
			}
			verboseLog(msg)
			hssList = append(hssList, hss)
		}
	}

	return hssList, nil
}

// Compute the password deterministically as the 32-byte HDKF-SHA256 of the
// HSS plus the device identity.
func genPassword(hss []byte, info *scuzz.Info) ([]byte, error) {
	hash := sha256.New
	devID := fmt.Sprintf("%s_%s", info.Serial, info.Model)

	r := hkdf.New(hash, hss, ([]byte)(passwordSalt), ([]byte)(devID))
	key := make([]byte, 32)

	if _, err := io.ReadFull(r, key); err != nil {
		return nil, err
	}
	return key, nil
}

func main() {
	flag.Parse()

	// Obtain 32 byte Host Secret Seed (HSS) from IPMI.
	hssList, err := getAllHss()
	if err != nil {
		log.Fatalf("error getting HSS: %v", err)
	}

	if len(hssList) == 0 {
		log.Fatalf("no HSS found - can't unlock disk.")
	}

	verboseLog(fmt.Sprintf("Found %d Host Secret Seeds.", len(hssList)))

	// Open the disk. Read its identity, and use it to unlock the disk.
	sgdisk, err := scuzz.NewSGDisk(*disk)
	if err != nil {
		log.Fatalf("failed to open disk %v: %v", *disk, err)
	}

	info, err := sgdisk.Identify()
	if err != nil {
		log.Fatalf("failed to read disk %v identity: %v", *disk, err)
	}

	verboseLog(fmt.Sprintf("Disk info for %s: %s", *disk, info.String()))

	// Try using each HSS to unlock the disk - only 1 should work.
	unlocked := false
	for i, hss := range hssList {
		key, err := genPassword(hss, info)
		if err != nil {
			log.Printf("Couldn't generate password with HSS %d: %v", i, err)
			continue
		}

		if err := sgdisk.Unlock((string)(key), false); err != nil {
			log.Printf("Couldn't unlock disk with HSS %d: %v", i, err)
		} else {
			unlocked = true
			break
		}
	}

	if unlocked {
		log.Printf("Successfully unlocked disk %s.", *disk)
	} else {
		log.Fatalf("Failed to unlock disk %s with any HSS.", *disk)
	}

	if *noRereadPartitions {
		return
	}

	// Update partitions on the on the disk.
	diskdev, err := block.Device(*disk)
	if err != nil {
		log.Fatalf("Could not find %s: %v", *disk, err)
	}

	if err := diskdev.ReadPartitionTable(); err != nil {
		log.Fatalf("Could not re-read partition table: %v", err)
	}

	glob := filepath.Join("/sys/class/block", diskdev.Name+"*")
	parts, err := filepath.Glob(glob)
	if err != nil {
		log.Fatalf("Could not find disk partitions: %v", err)
	}

	verboseLog(fmt.Sprintf("Found these %s unlocked partitions: %v", *disk, parts))

}
<|endoftext|>


<commit_before>// Based on code from the Go standard library.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the ORIGINAL_LICENSE file.

package math

// Mathematical constants.
// Reference: http://oeis.org/Axxxxxx
const (
	E   = float32(2.71828182845904523536028747135266249775724709369995957496696763) // A001113
	Pi  = float32(3.14159265358979323846264338327950288419716939937510582097494459) // A000796
	Phi = float32(1.61803398874989484820458683436563811772030917980576286213544862) // A001622

	Sqrt2   = float32(1.41421356237309504880168872420969807856967187537694807317667974) // A002193
	SqrtE   = float32(1.64872127070012814684865078781416357165377610071014801157507931) // A019774
	SqrtPi  = float32(1.77245385090551602729816748334114518279754945612238712821380779) // A002161
	SqrtPhi = float32(1.27201964951406896425242246173749149171560804184009624861664038) // A139339

	Ln2    = float32(0.693147180559945309417232121458176568075500134360255254120680009) // A002162
	Log2E  = float32(1 / Ln2)
	Ln10   = float32(2.30258509299404568401799145468436420760110148862877297603332790) // A002392
	Log10E = float32(1 / Ln10)
)

// Floating-point limit values.
// Max is the largest finite value representable by the type.
// SmallestNonzero is the smallest positive, non-zero value representable by the type.
const (
	MaxFloat32             = float32(3.40282346638528859811704183484516925440e+38)  // 2**127 * (2**24 - 1) / 2**23
	SmallestNormalFloat32  = float32(1.17549435082229e-38)                          // 1 / 2**(127 - 1)
	SmallestNonzeroFloat32 = float32(1.401298464324817070923729583289916131280e-45) // 1 / 2**(127 - 1 + 23)

	MaxFloat64             = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52
	SmallestNormalFloat64  = 2.2250738585072014e-308                        // 1 / 2**(1023 - 1)
	SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52)
)

// Integer limit values.
const (
	MaxInt8   = 1<<7 - 1
	MinInt8   = -1 << 7
	MaxInt16  = 1<<15 - 1
	MinInt16  = -1 << 15
	MaxInt32  = 1<<31 - 1
	MinInt32  = -1 << 31
	MaxInt64  = 1<<63 - 1
	MinInt64  = -1 << 63
	MaxUint8  = 1<<8 - 1
	MaxUint16 = 1<<16 - 1
	MaxUint32 = 1<<32 - 1
	MaxUint64 = 1<<64 - 1
)
<commit_msg>Edded Espilon constants.<commit_after>// Based on code from the Go standard library.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the ORIGINAL_LICENSE file.

package math

// Mathematical constants.
// Reference: http://oeis.org/Axxxxxx
const (
	E   = float32(2.71828182845904523536028747135266249775724709369995957496696763) // A001113
	Pi  = float32(3.14159265358979323846264338327950288419716939937510582097494459) // A000796
	Phi = float32(1.61803398874989484820458683436563811772030917980576286213544862) // A001622

	Sqrt2   = float32(1.41421356237309504880168872420969807856967187537694807317667974) // A002193
	SqrtE   = float32(1.64872127070012814684865078781416357165377610071014801157507931) // A019774
	SqrtPi  = float32(1.77245385090551602729816748334114518279754945612238712821380779) // A002161
	SqrtPhi = float32(1.27201964951406896425242246173749149171560804184009624861664038) // A139339

	Ln2    = float32(0.693147180559945309417232121458176568075500134360255254120680009) // A002162
	Log2E  = float32(1 / Ln2)
	Ln10   = float32(2.30258509299404568401799145468436420760110148862877297603332790) // A002392
	Log10E = float32(1 / Ln10)
)

// Floating-point limit values.
// Max is the largest finite value representable by the type.
// SmallestNormal is the smallest normal value representable by the type.
// Epsilon is the smallest value that, when added to one, yields a result different from one.
// SmallestNonzero is the smallest positive, non-zero value representable by the type.
const (
	MaxFloat32             = float32(3.40282346638528859811704183484516925440e+38)  // 2**127 * (2**24 - 1) / 2**23
	SmallestNormalFloat32  = float32(1.17549435082229e-38)                          // 1 / 2**(127 - 1)
	EpsilonFloat32         = float32(1.19209290e-07)                                // 1 / 2**23
	SmallestNonzeroFloat32 = float32(1.401298464324817070923729583289916131280e-45) // 1 / 2**(127 - 1 + 23)

	MaxFloat64             = 1.797693134862315708145274237317043567981e+308 // 2**1023 * (2**53 - 1) / 2**52
	SmallestNormalFloat64  = 2.2250738585072014e-308                        // 1 / 2**(1023 - 1)
	EpsilonFloat64         = 2.2204460492503131e-16                         // 1 / 2**52
	SmallestNonzeroFloat64 = 4.940656458412465441765687928682213723651e-324 // 1 / 2**(1023 - 1 + 52)
)

// Integer limit values.
const (
	MaxInt8   = 1<<7 - 1
	MinInt8   = -1 << 7
	MaxInt16  = 1<<15 - 1
	MinInt16  = -1 << 15
	MaxInt32  = 1<<31 - 1
	MinInt32  = -1 << 31
	MaxInt64  = 1<<63 - 1
	MinInt64  = -1 << 63
	MaxUint8  = 1<<8 - 1
	MaxUint16 = 1<<16 - 1
	MaxUint32 = 1<<32 - 1
	MaxUint64 = 1<<64 - 1
)
<|endoftext|>


<commit_before>package maxsat

import (
	"fmt"

	"github.com/crillab/gophersat/solver"
)

// A Model associates variable names with a binding.
type Model map[string]bool

// A Problem is a set of constraints.
type Problem struct {
	solver       *solver.Solver
	intVars      map[string]int // for each var, its integer counterpart
	varInts      []string       // for each int value, the associated variable
	blockWeights map[int]int    // for each blocking literal, the weight of the associated constraint
	maxWeight    int            // sum of all blockWeights
}

// New returns a new problem associated with the given constraints.
func New(constrs ...Constr) *Problem {
	pb := &Problem{intVars: make(map[string]int), blockWeights: make(map[int]int)}
	clauses := make([]solver.PBConstr, len(constrs))
	for i, constr := range constrs {
		lits := make([]int, len(constr.Lits))
		for j, lit := range constr.Lits {
			v := lit.Var
			if _, ok := pb.intVars[v]; !ok {
				pb.varInts = append(pb.varInts, v)
				pb.intVars[v] = len(pb.varInts)
			}
			lits[j] = pb.intVars[v]
			if lit.Negated {
				lits[j] = -lits[j]
			}
		}
		var coeffs []int
		if len(constr.Coeffs) != 0 {
			coeffs = make([]int, len(constr.Coeffs))
			copy(coeffs, constr.Coeffs)
		}
		if constr.Weight != 0 { // Soft constraint: add blocking literal
			pb.varInts = append(pb.varInts, "") // Create new blocking lit
			bl := len(pb.varInts)
			pb.blockWeights[bl] = constr.Weight
			pb.maxWeight += constr.Weight
			lits = append(lits, bl)
			if coeffs != nil { // If this is a clause, there is no explicit coeff
				// TODO: deal with card constraints: AtLeast !=1 but coeffs == nil!
				coeffs = append(coeffs, constr.AtLeast)
			}
		}
		clauses[i] = solver.GtEq(lits, coeffs, constr.AtLeast)
	}
	optLits := make([]solver.Lit, 0, len(pb.blockWeights))
	optWeights := make([]int, 0, len(pb.blockWeights))
	for v, w := range pb.blockWeights {
		optLits = append(optLits, solver.IntToLit(int32(v)))
		optWeights = append(optWeights, w)
	}
	prob := solver.ParsePBConstrs(clauses)
	prob.SetCostFunc(optLits, optWeights)
	pb.solver = solver.New(prob)
	return pb
}

// SetVerbose makes the underlying solver verbose, or not.
func (pb *Problem) SetVerbose(verbose bool) {
	pb.solver.Verbose = verbose
}

// Output output the problem to stdout in the OPB format.
func (pb *Problem) Output() {
	fmt.Println(pb.solver.PBString())
}

// Solve returns an optimal Model for the problem and the associated cost.
// If the model is nil, the problem was not satisfiable (i.e hard clauses could not be satisfied).
func (pb *Problem) Solve() (Model, int) {
	cost := pb.solver.Minimize()
	if cost == -1 {
		return nil, -1
	}
	res := make(Model)
	for i, binding := range pb.solver.Model() {
		name := pb.varInts[i]
		if name != "" { // Ignore blocking lits
			res[name] = binding
		}
	}
	return res, cost
}
<commit_msg>adding Solver() method to access underlying solver.Solver<commit_after>package maxsat

import (
	"fmt"

	"github.com/crillab/gophersat/solver"
)

// A Model associates variable names with a binding.
type Model map[string]bool

// A Problem is a set of constraints.
type Problem struct {
	solver       *solver.Solver
	intVars      map[string]int // for each var, its integer counterpart
	varInts      []string       // for each int value, the associated variable
	blockWeights map[int]int    // for each blocking literal, the weight of the associated constraint
	maxWeight    int            // sum of all blockWeights
}

// New returns a new problem associated with the given constraints.
func New(constrs ...Constr) *Problem {
	pb := &Problem{intVars: make(map[string]int), blockWeights: make(map[int]int)}
	clauses := make([]solver.PBConstr, len(constrs))
	for i, constr := range constrs {
		lits := make([]int, len(constr.Lits))
		for j, lit := range constr.Lits {
			v := lit.Var
			if _, ok := pb.intVars[v]; !ok {
				pb.varInts = append(pb.varInts, v)
				pb.intVars[v] = len(pb.varInts)
			}
			lits[j] = pb.intVars[v]
			if lit.Negated {
				lits[j] = -lits[j]
			}
		}
		var coeffs []int
		if len(constr.Coeffs) != 0 {
			coeffs = make([]int, len(constr.Coeffs))
			copy(coeffs, constr.Coeffs)
		}
		if constr.Weight != 0 { // Soft constraint: add blocking literal
			pb.varInts = append(pb.varInts, "") // Create new blocking lit
			bl := len(pb.varInts)
			pb.blockWeights[bl] = constr.Weight
			pb.maxWeight += constr.Weight
			lits = append(lits, bl)
			if coeffs != nil { // If this is a clause, there is no explicit coeff
				// TODO: deal with card constraints: AtLeast !=1 but coeffs == nil!
				coeffs = append(coeffs, constr.AtLeast)
			}
		}
		clauses[i] = solver.GtEq(lits, coeffs, constr.AtLeast)
	}
	optLits := make([]solver.Lit, 0, len(pb.blockWeights))
	optWeights := make([]int, 0, len(pb.blockWeights))
	for v, w := range pb.blockWeights {
		optLits = append(optLits, solver.IntToLit(int32(v)))
		optWeights = append(optWeights, w)
	}
	prob := solver.ParsePBConstrs(clauses)
	prob.SetCostFunc(optLits, optWeights)
	pb.solver = solver.New(prob)
	return pb
}

// SetVerbose makes the underlying solver verbose, or not.
func (pb *Problem) SetVerbose(verbose bool) {
	pb.solver.Verbose = verbose
}

// Output output the problem to stdout in the OPB format.
func (pb *Problem) Output() {
	fmt.Println(pb.solver.PBString())
}

// Solver gives access to the solver.Solver used to solve the MAXSAT problem.
// Unless you have specific needs, youè will usually not need to call this method,
// and rather want to call pb.Solve() instead.
func (pb *Problem) Solver() *solver.Solver {
	return pb.solver
}

// Solve returns an optimal Model for the problem and the associated cost.
// If the model is nil, the problem was not satisfiable (i.e hard clauses could not be satisfied).
func (pb *Problem) Solve() (Model, int) {
	cost := pb.solver.Minimize()
	if cost == -1 {
		return nil, -1
	}
	res := make(Model)
	for i, binding := range pb.solver.Model() {
		name := pb.varInts[i]
		if name != "" { // Ignore blocking lits
			res[name] = binding
		}
	}
	return res, cost
}
<|endoftext|>


<commit_before>package main

import (
	"time"

	"github.com/pkg/errors"
)

// authBackend is an interface for adding and finding HAWK users and
// their permissions
type authBackend interface {
	addAuth(*authorization) error
	addMonitoringAuth(string) error
	getAuthByID(id string) (authorization, error)
	getAuths() map[string]authorization
}

// inMemoryBackend is an authBackend that loads a config and stores
// that auth info in memory
type inMemoryBackend struct {
	auths       map[string]authorization
	signerIndex map[string]int
}

// newInMemoryAuthBackend returns an empty inMemoryBackend
func newInMemoryAuthBackend() (backend *inMemoryBackend) {
	return &inMemoryBackend{
		auths:       make(map[string]authorization),
		signerIndex: make(map[string]int),
	}
}

// addAuth adds an authorization to the auth map or errors
func (b *inMemoryBackend) addAuth(auth *authorization) (err error) {
	_, getAuthErr := b.getAuthByID(auth.ID)
	switch getAuthErr {
	case nil:
		return errors.Errorf("authorization id '%s' already defined, duplicates are not permitted", auth.ID)
	case ErrAuthNotFound:
		// this is what we want
	default:
		return errors.Wrapf(getAuthErr, "error finding auth with id '%s'", auth.ID)
	}
	if auth.HawkTimestampValidity != "" {
		auth.hawkMaxTimestampSkew, err = time.ParseDuration(auth.HawkTimestampValidity)
		if err != nil {
			return err
		}
	} else {
		auth.hawkMaxTimestampSkew = time.Minute
	}
	b.auths[auth.ID] = *auth
	return nil
}

// getAuthByID returns an authorization if it exists or nil. Call
// addAuthorizations and addMonitoring first
func (b *inMemoryBackend) getAuthByID(id string) (authorization, error) {
	if auth, ok := b.auths[id]; ok {
		return auth, nil
	}
	return authorization{}, ErrAuthNotFound
}

// getAuths returns enabled authorizations
func (b *inMemoryBackend) getAuths() map[string]authorization {
	return b.auths
}

// addMonitoringAuth adds an authorization to enable the
// tools/autograph-monitor
func (b *inMemoryBackend) addMonitoringAuth(monitorKey string) error {
	_, err := b.getAuthByID(monitorAuthID)
	switch err {
	case ErrAuthNotFound:
	case nil:
		return errors.Errorf("user 'monitor' is reserved for monitoring, duplication is not permitted")
	default:
		return errors.Errorf("error fetching 'monitor' auth: %q", err)
	}
	return b.addAuth(&authorization{
		ID:                    monitorAuthID,
		Key:                   monitorKey,
		HawkTimestampValidity: "1m",
		hawkMaxTimestampSkew:  time.Minute,
	})
}
<commit_msg>add getSignerID to in-memory backend<commit_after>package main

import (
	"time"

	"github.com/pkg/errors"
)

// authBackend is an interface for adding and finding HAWK users and
// their permissions
type authBackend interface {
	addAuth(*authorization) error
	addMonitoringAuth(string) error
	getAuthByID(id string) (authorization, error)
	getAuths() map[string]authorization
	getSignerID(userid, keyid string) (int, error)
}

// inMemoryBackend is an authBackend that loads a config and stores
// that auth info in memory
type inMemoryBackend struct {
	auths       map[string]authorization
	signerIndex map[string]int
}

// newInMemoryAuthBackend returns an empty inMemoryBackend
func newInMemoryAuthBackend() (backend *inMemoryBackend) {
	return &inMemoryBackend{
		auths:       make(map[string]authorization),
		signerIndex: make(map[string]int),
	}
}

// addAuth adds an authorization to the auth map or errors
func (b *inMemoryBackend) addAuth(auth *authorization) (err error) {
	_, getAuthErr := b.getAuthByID(auth.ID)
	switch getAuthErr {
	case nil:
		return errors.Errorf("authorization id '%s' already defined, duplicates are not permitted", auth.ID)
	case ErrAuthNotFound:
		// this is what we want
	default:
		return errors.Wrapf(getAuthErr, "error finding auth with id '%s'", auth.ID)
	}
	if auth.HawkTimestampValidity != "" {
		auth.hawkMaxTimestampSkew, err = time.ParseDuration(auth.HawkTimestampValidity)
		if err != nil {
			return err
		}
	} else {
		auth.hawkMaxTimestampSkew = time.Minute
	}
	b.auths[auth.ID] = *auth
	return nil
}

// getAuthByID returns an authorization if it exists or nil. Call
// addAuthorizations and addMonitoring first
func (b *inMemoryBackend) getAuthByID(id string) (authorization, error) {
	if auth, ok := b.auths[id]; ok {
		return auth, nil
	}
	return authorization{}, ErrAuthNotFound
}

// getAuths returns enabled authorizations
func (b *inMemoryBackend) getAuths() map[string]authorization {
	return b.auths
}

// addMonitoringAuth adds an authorization to enable the
// tools/autograph-monitor
func (b *inMemoryBackend) addMonitoringAuth(monitorKey string) error {
	_, err := b.getAuthByID(monitorAuthID)
	switch err {
	case ErrAuthNotFound:
	case nil:
		return errors.Errorf("user 'monitor' is reserved for monitoring, duplication is not permitted")
	default:
		return errors.Errorf("error fetching 'monitor' auth: %q", err)
	}
	return b.addAuth(&authorization{
		ID:                    monitorAuthID,
		Key:                   monitorKey,
		HawkTimestampValidity: "1m",
		hawkMaxTimestampSkew:  time.Minute,
	})
}

// getSignerId returns the signer identifier for the user. If a keyid
// is specified, the corresponding signer is returned. If no signer is
// found, an error is returned and the signer identifier is set to -1.
func (b *inMemoryBackend) getSignerID(userid, keyid string) (int, error) {
	tag := userid + "+" + keyid
	if _, ok := b.signerIndex[tag]; !ok {
		if keyid == "" {
			return -1, errors.Errorf("%q does not have a default signing key", userid)
		}
		return -1, errors.Errorf("%s is not authorized to sign with key ID %s", userid, keyid)
	}
	return b.signerIndex[tag], nil
}
<|endoftext|>


<commit_before>package agent

import (
	"fmt"
	"github.com/hashicorp/serf/serf"
	"io/ioutil"
	"net"
	"testing"
)

const eventScript = `#!/bin/sh
RESULT_FILE="%s"
echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
echo $SERF_TAG_DC >> ${RESULT_FILE}
echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
while read line; do
	printf "${line}\n" >>${RESULT_FILE}
done
`

const userEventScript = `#!/bin/sh
RESULT_FILE="%s"
echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
echo $SERF_TAG_DC >> ${RESULT_FILE}
echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
echo $SERF_EVENT $SERF_USER_LTIME "$@" >>${RESULT_FILE}
while read line; do
	printf "${line}\n" >>${RESULT_FILE}
done
`

// testEventScript creates an event script that can be used with the
// agent. It returns the path to the event script itself and a path to
// the file that will contain the events that that script receives.
func testEventScript(t *testing.T, script string) (string, string) {
	scriptFile, err := ioutil.TempFile("", "serf")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer scriptFile.Close()

	if err := scriptFile.Chmod(0755); err != nil {
		t.Fatalf("err: %s", err)
	}

	resultFile, err := ioutil.TempFile("", "serf-result")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer resultFile.Close()

	_, err = scriptFile.Write([]byte(
		fmt.Sprintf(script, resultFile.Name())))
	if err != nil {
		t.Fatalf("err: %s")
	}

	return scriptFile.Name(), resultFile.Name()
}

func TestScriptEventHandler(t *testing.T) {
	script, results := testEventScript(t, eventScript)

	h := &ScriptEventHandler{
		Self: serf.Member{
			Name: "ourname",
			Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
		},
		Scripts: []EventScript{
			{
				EventFilter: EventFilter{
					Event: "*",
				},
				Script: script,
			},
		},
	}

	event := serf.MemberEvent{
		Type: serf.EventMemberJoin,
		Members: []serf.Member{
			{
				Name: "foo",
				Addr: net.ParseIP("1.2.3.4"),
				Tags: map[string]string{"role": "bar", "foo": "bar"},
			},
		},
	}

	h.HandleEvent(event)

	result, err := ioutil.ReadFile(results)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	expected1 := "ourname ourrole\neast-aws\nmember-join\nfoo\t1.2.3.4\tbar\trole=bar,foo=bar\n"
	expected2 := "ourname ourrole\neast-aws\nmember-join\nfoo\t1.2.3.4\tbar\tfoo=bar,role=bar\n"
	if string(result) != expected1 && string(result) != expected2 {
		t.Fatalf("bad: %#v. Expected: %#v or %v", string(result), expected1, expected2)
	}
}

func TestScriptUserEventHandler(t *testing.T) {
	script, results := testEventScript(t, userEventScript)

	h := &ScriptEventHandler{
		Self: serf.Member{
			Name: "ourname",
			Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
		},
		Scripts: []EventScript{
			{
				EventFilter: EventFilter{
					Event: "*",
				},
				Script: script,
			},
		},
	}

	userEvent := serf.UserEvent{
		LTime:    1,
		Name:     "baz",
		Payload:  []byte("foobar"),
		Coalesce: true,
	}

	h.HandleEvent(userEvent)

	result, err := ioutil.ReadFile(results)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	expected := "ourname ourrole\neast-aws\nuser baz\nuser 1\nfoobar\n"
	if string(result) != expected {
		t.Fatalf("bad: %#v. Expected: %#v", string(result), expected)
	}
}

func TestEventScriptInvoke(t *testing.T) {
	testCases := []struct {
		script EventScript
		event  serf.Event
		invoke bool
	}{
		{
			EventScript{EventFilter{"*", ""}, "script.sh"},
			serf.MemberEvent{},
			true,
		},
		{
			EventScript{EventFilter{"user", ""}, "script.sh"},
			serf.MemberEvent{},
			false,
		},
		{
			EventScript{EventFilter{"user", "deploy"}, "script.sh"},
			serf.UserEvent{Name: "deploy"},
			true,
		},
		{
			EventScript{EventFilter{"user", "deploy"}, "script.sh"},
			serf.UserEvent{Name: "restart"},
			false,
		},
		{
			EventScript{EventFilter{"member-join", ""}, "script.sh"},
			serf.MemberEvent{Type: serf.EventMemberJoin},
			true,
		},
		{
			EventScript{EventFilter{"member-join", ""}, "script.sh"},
			serf.MemberEvent{Type: serf.EventMemberLeave},
			false,
		},
	}

	for _, tc := range testCases {
		result := tc.script.Invoke(tc.event)
		if result != tc.invoke {
			t.Errorf("bad: %#v", tc)
		}
	}
}

func TestEventScriptValid(t *testing.T) {
	testCases := []struct {
		Event string
		Valid bool
	}{
		{"member-join", true},
		{"member-leave", true},
		{"member-failed", true},
		{"member-update", true},
		{"user", true},
		{"User", false},
		{"member", false},
		{"*", true},
	}

	for _, tc := range testCases {
		script := EventScript{EventFilter: EventFilter{Event: tc.Event}}
		if script.Valid() != tc.Valid {
			t.Errorf("bad: %#v", tc)
		}
	}
}

func TestParseEventScript(t *testing.T) {
	testCases := []struct {
		v       string
		err     bool
		results []EventScript
	}{
		{
			"script.sh",
			false,
			[]EventScript{{EventFilter{"*", ""}, "script.sh"}},
		},

		{
			"member-join=script.sh",
			false,
			[]EventScript{{EventFilter{"member-join", ""}, "script.sh"}},
		},

		{
			"foo,bar=script.sh",
			false,
			[]EventScript{
				{EventFilter{"foo", ""}, "script.sh"},
				{EventFilter{"bar", ""}, "script.sh"},
			},
		},

		{
			"user:deploy=script.sh",
			false,
			[]EventScript{{EventFilter{"user", "deploy"}, "script.sh"}},
		},

		{
			"foo,user:blah,bar=script.sh",
			false,
			[]EventScript{
				{EventFilter{"foo", ""}, "script.sh"},
				{EventFilter{"user", "blah"}, "script.sh"},
				{EventFilter{"bar", ""}, "script.sh"},
			},
		},
	}

	for _, tc := range testCases {
		results := ParseEventScript(tc.v)
		if results == nil {
			t.Errorf("result should not be nil")
			continue
		}

		if len(results) != len(tc.results) {
			t.Errorf("bad: %#v", results)
			continue
		}

		for i, r := range results {
			expected := tc.results[i]

			if r.Event != expected.Event {
				t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
			}

			if r.UserEvent != expected.UserEvent {
				t.Errorf("User events not equal: %s %s", r.UserEvent, expected.UserEvent)
			}

			if r.Script != expected.Script {
				t.Errorf("Scripts not equal: %s %s", r.Script, expected.Script)
			}
		}
	}
}

func TestParseEventFilter(t *testing.T) {
	testCases := []struct {
		v       string
		results []EventFilter
	}{
		{
			"",
			[]EventFilter{EventFilter{"*", ""}},
		},

		{
			"member-join",
			[]EventFilter{EventFilter{"member-join", ""}},
		},

		{
			"foo,bar",
			[]EventFilter{
				EventFilter{"foo", ""},
				EventFilter{"bar", ""},
			},
		},

		{
			"user:deploy",
			[]EventFilter{EventFilter{"user", "deploy"}},
		},

		{
			"foo,user:blah,bar",
			[]EventFilter{
				EventFilter{"foo", ""},
				EventFilter{"user", "blah"},
				EventFilter{"bar", ""},
			},
		},
	}

	for _, tc := range testCases {
		results := ParseEventFilter(tc.v)
		if results == nil {
			t.Errorf("result should not be nil")
			continue
		}

		if len(results) != len(tc.results) {
			t.Errorf("bad: %#v", results)
			continue
		}

		for i, r := range results {
			expected := tc.results[i]

			if r.Event != expected.Event {
				t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
			}

			if r.UserEvent != expected.UserEvent {
				t.Errorf("User events not equal: %s %s", r.UserEvent, expected.UserEvent)
			}
		}
	}
}
<commit_msg>Make sure that event handlers pass through environment variables.<commit_after>package agent

import (
	"fmt"
	"github.com/hashicorp/serf/serf"
	"io/ioutil"
	"net"
	"os"
	"testing"
)

const eventScript = `#!/bin/sh
RESULT_FILE="%s"
echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
echo $SERF_TAG_DC >> ${RESULT_FILE}
echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
echo $os_env_var >> ${RESULT_FILE}
while read line; do
	printf "${line}\n" >>${RESULT_FILE}
done
`

const userEventScript = `#!/bin/sh
RESULT_FILE="%s"
echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
echo $SERF_TAG_DC >> ${RESULT_FILE}
echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
echo $SERF_EVENT $SERF_USER_LTIME "$@" >>${RESULT_FILE}
while read line; do
	printf "${line}\n" >>${RESULT_FILE}
done
`

// testEventScript creates an event script that can be used with the
// agent. It returns the path to the event script itself and a path to
// the file that will contain the events that that script receives.
func testEventScript(t *testing.T, script string) (string, string) {
	scriptFile, err := ioutil.TempFile("", "serf")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer scriptFile.Close()

	if err := scriptFile.Chmod(0755); err != nil {
		t.Fatalf("err: %s", err)
	}

	resultFile, err := ioutil.TempFile("", "serf-result")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer resultFile.Close()

	_, err = scriptFile.Write([]byte(
		fmt.Sprintf(script, resultFile.Name())))
	if err != nil {
		t.Fatalf("err: %s")
	}

	return scriptFile.Name(), resultFile.Name()
}

func TestScriptEventHandler(t *testing.T) {
	os.Setenv("os_env_var", "os-env-foo")

	script, results := testEventScript(t, eventScript)

	h := &ScriptEventHandler{
		Self: serf.Member{
			Name: "ourname",
			Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
		},
		Scripts: []EventScript{
			{
				EventFilter: EventFilter{
					Event: "*",
				},
				Script: script,
			},
		},
	}

	event := serf.MemberEvent{
		Type: serf.EventMemberJoin,
		Members: []serf.Member{
			{
				Name: "foo",
				Addr: net.ParseIP("1.2.3.4"),
				Tags: map[string]string{"role": "bar", "foo": "bar"},
			},
		},
	}

	h.HandleEvent(event)

	result, err := ioutil.ReadFile(results)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	expected1 := "ourname ourrole\neast-aws\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\trole=bar,foo=bar\n"
	expected2 := "ourname ourrole\neast-aws\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\tfoo=bar,role=bar\n"
	if string(result) != expected1 && string(result) != expected2 {
		t.Fatalf("bad: %#v. Expected: %#v or %v", string(result), expected1, expected2)
	}
}

func TestScriptUserEventHandler(t *testing.T) {
	script, results := testEventScript(t, userEventScript)

	h := &ScriptEventHandler{
		Self: serf.Member{
			Name: "ourname",
			Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
		},
		Scripts: []EventScript{
			{
				EventFilter: EventFilter{
					Event: "*",
				},
				Script: script,
			},
		},
	}

	userEvent := serf.UserEvent{
		LTime:    1,
		Name:     "baz",
		Payload:  []byte("foobar"),
		Coalesce: true,
	}

	h.HandleEvent(userEvent)

	result, err := ioutil.ReadFile(results)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	expected := "ourname ourrole\neast-aws\nuser baz\nuser 1\nfoobar\n"
	if string(result) != expected {
		t.Fatalf("bad: %#v. Expected: %#v", string(result), expected)
	}
}

func TestEventScriptInvoke(t *testing.T) {
	testCases := []struct {
		script EventScript
		event  serf.Event
		invoke bool
	}{
		{
			EventScript{EventFilter{"*", ""}, "script.sh"},
			serf.MemberEvent{},
			true,
		},
		{
			EventScript{EventFilter{"user", ""}, "script.sh"},
			serf.MemberEvent{},
			false,
		},
		{
			EventScript{EventFilter{"user", "deploy"}, "script.sh"},
			serf.UserEvent{Name: "deploy"},
			true,
		},
		{
			EventScript{EventFilter{"user", "deploy"}, "script.sh"},
			serf.UserEvent{Name: "restart"},
			false,
		},
		{
			EventScript{EventFilter{"member-join", ""}, "script.sh"},
			serf.MemberEvent{Type: serf.EventMemberJoin},
			true,
		},
		{
			EventScript{EventFilter{"member-join", ""}, "script.sh"},
			serf.MemberEvent{Type: serf.EventMemberLeave},
			false,
		},
	}

	for _, tc := range testCases {
		result := tc.script.Invoke(tc.event)
		if result != tc.invoke {
			t.Errorf("bad: %#v", tc)
		}
	}
}

func TestEventScriptValid(t *testing.T) {
	testCases := []struct {
		Event string
		Valid bool
	}{
		{"member-join", true},
		{"member-leave", true},
		{"member-failed", true},
		{"member-update", true},
		{"user", true},
		{"User", false},
		{"member", false},
		{"*", true},
	}

	for _, tc := range testCases {
		script := EventScript{EventFilter: EventFilter{Event: tc.Event}}
		if script.Valid() != tc.Valid {
			t.Errorf("bad: %#v", tc)
		}
	}
}

func TestParseEventScript(t *testing.T) {
	testCases := []struct {
		v       string
		err     bool
		results []EventScript
	}{
		{
			"script.sh",
			false,
			[]EventScript{{EventFilter{"*", ""}, "script.sh"}},
		},

		{
			"member-join=script.sh",
			false,
			[]EventScript{{EventFilter{"member-join", ""}, "script.sh"}},
		},

		{
			"foo,bar=script.sh",
			false,
			[]EventScript{
				{EventFilter{"foo", ""}, "script.sh"},
				{EventFilter{"bar", ""}, "script.sh"},
			},
		},

		{
			"user:deploy=script.sh",
			false,
			[]EventScript{{EventFilter{"user", "deploy"}, "script.sh"}},
		},

		{
			"foo,user:blah,bar=script.sh",
			false,
			[]EventScript{
				{EventFilter{"foo", ""}, "script.sh"},
				{EventFilter{"user", "blah"}, "script.sh"},
				{EventFilter{"bar", ""}, "script.sh"},
			},
		},
	}

	for _, tc := range testCases {
		results := ParseEventScript(tc.v)
		if results == nil {
			t.Errorf("result should not be nil")
			continue
		}

		if len(results) != len(tc.results) {
			t.Errorf("bad: %#v", results)
			continue
		}

		for i, r := range results {
			expected := tc.results[i]

			if r.Event != expected.Event {
				t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
			}

			if r.UserEvent != expected.UserEvent {
				t.Errorf("User events not equal: %s %s", r.UserEvent, expected.UserEvent)
			}

			if r.Script != expected.Script {
				t.Errorf("Scripts not equal: %s %s", r.Script, expected.Script)
			}
		}
	}
}

func TestParseEventFilter(t *testing.T) {
	testCases := []struct {
		v       string
		results []EventFilter
	}{
		{
			"",
			[]EventFilter{EventFilter{"*", ""}},
		},

		{
			"member-join",
			[]EventFilter{EventFilter{"member-join", ""}},
		},

		{
			"foo,bar",
			[]EventFilter{
				EventFilter{"foo", ""},
				EventFilter{"bar", ""},
			},
		},

		{
			"user:deploy",
			[]EventFilter{EventFilter{"user", "deploy"}},
		},

		{
			"foo,user:blah,bar",
			[]EventFilter{
				EventFilter{"foo", ""},
				EventFilter{"user", "blah"},
				EventFilter{"bar", ""},
			},
		},
	}

	for _, tc := range testCases {
		results := ParseEventFilter(tc.v)
		if results == nil {
			t.Errorf("result should not be nil")
			continue
		}

		if len(results) != len(tc.results) {
			t.Errorf("bad: %#v", results)
			continue
		}

		for i, r := range results {
			expected := tc.results[i]

			if r.Event != expected.Event {
				t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
			}

			if r.UserEvent != expected.UserEvent {
				t.Errorf("User events not equal: %s %s", r.UserEvent, expected.UserEvent)
			}
		}
	}
}
<|endoftext|>