blob: 6102cf1c489c465d9407755ffa5e270ec4440216 [file] [log] [blame]
package main
import (
"context"
"encoding/pem"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication/v1"
"k8s.io/client-go/tools/clientcmd"
clientapi "k8s.io/client-go/tools/clientcmd/api"
"source.monogon.dev/metropolis/cli/metroctl/core"
clicontext "source.monogon.dev/metropolis/cli/pkg/context"
"source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/node/core/rpc"
apb "source.monogon.dev/metropolis/proto/api"
)
var takeownershipCommand = &cobra.Command{
Use: "takeownership",
Short: "Takes ownership of a new Metropolis cluster",
Long: `This takes ownership of a new Metropolis cluster by asking the new
cluster to issue an owner certificate to for the owner key generated by a
previous invocation of metroctl install on this machine. A single cluster
endpoint must be provided with the --endpoints parameter.`,
Args: cobra.ExactArgs(0),
Run: doTakeOwnership,
}
func doTakeOwnership(cmd *cobra.Command, _ []string) {
if len(flags.clusterEndpoints) != 1 {
log.Fatalf("takeownership requires a single cluster endpoint to be provided with the --endpoints parameter.")
}
// Retrieve the cluster owner's private key, and use it to construct
// ephemeral credentials. Then, dial the cluster.
opk, err := getOwnerKey()
if err == noCredentialsError {
log.Fatalf("Owner key does not exist. takeownership needs to be executed on the same system that has previously installed the cluster using metroctl install.")
}
if err != nil {
log.Fatalf("Couldn't get owner's key: %v", err)
}
ctx := clicontext.WithInterrupt(context.Background())
cc, err := core.DialCluster(ctx, opk, nil, flags.proxyAddr, flags.clusterEndpoints, rpcLogger)
if err != nil {
log.Fatalf("While dialing the cluster: %v", err)
}
aaa := apb.NewAAAClient(cc)
ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, opk)
if err != nil {
log.Fatalf("Failed to retrive owner certificate from cluster: %v", err)
}
ownerCertPEM := pem.Block{
Type: "CERTIFICATE",
Bytes: ownerCert.Certificate[0],
}
if err := os.WriteFile(filepath.Join(flags.configPath, "owner.pem"), pem.EncodeToMemory(&ownerCertPEM), 0644); err != nil {
log.Printf("Failed to store retrieved owner certificate: %v", err)
log.Fatalln("Sorry, the cluster has been lost as taking ownership cannot be repeated. Fix the reason the file couldn't be written and reinstall the node.")
}
log.Print("Successfully retrieved owner credentials! You now own this cluster. Setting up kubeconfig now...")
ca := clientcmd.NewDefaultPathOptions()
config, err := ca.GetStartingConfig()
if err != nil {
log.Fatalf("Failed to get initial kubeconfig to add Metropolis cluster: %v", err)
}
// If the user has metroctl in their path, use the metroctl from path as
// a credential plugin. Otherwise use the path to the currently-running
// metroctl.
metroctlPath := "metroctl"
if _, err := exec.LookPath("metroctl"); err != nil {
metroctlPath, err = os.Executable()
if err != nil {
log.Fatalf("Failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %v", err)
}
}
config.AuthInfos["metropolis"] = &clientapi.AuthInfo{
Exec: &clientapi.ExecConfig{
APIVersion: clientauthentication.SchemeGroupVersion.String(),
Command: metroctlPath,
Args: []string{k8scredpluginCmd.Use},
InstallHint: `Authenticating to Metropolis clusters requires metroctl to be present.
Running metroctl takeownership creates this entry and either points to metroctl as a command in
PATH if metroctl is in PATH at that time or to the absolute path to metroctl at that time.
If you moved metroctl afterwards or want to switch to PATH resolution, edit $HOME/.kube/config and
change users.metropolis.exec.command to the required path (or just metroctl if using PATH resolution).`,
InteractiveMode: clientapi.NeverExecInteractiveMode,
},
}
config.Clusters["metropolis"] = &clientapi.Cluster{
// MVP: This is insecure, but making this work would be wasted effort
// as all of it will be replaced by the identity system.
// TODO(issues/144): adjust cluster endpoints once have functioning roles
// implemented.
InsecureSkipTLSVerify: true,
Server: "https://" + net.JoinHostPort(flags.clusterEndpoints[0], node.KubernetesAPIWrappedPort.PortString()),
}
config.Contexts["metropolis"] = &clientapi.Context{
AuthInfo: "metropolis",
Cluster: "metropolis",
Namespace: "default",
}
// Only set us as the current context if no other exists. Changing that
// unprompted would be kind of rude.
if config.CurrentContext == "" {
config.CurrentContext = "metropolis"
}
if err := clientcmd.ModifyConfig(ca, *config, true); err != nil {
log.Fatalf("Failed to modify kubeconfig to add Metropolis cluster: %v", err)
}
log.Println("Success! kubeconfig is set up. You can now run kubectl --context=metropolis ... to access the Kubernetes cluster.")
}
func init() {
rootCmd.AddCommand(takeownershipCommand)
}