| package main | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"crypto/ed25519" | 
 | 	"encoding/pem" | 
 | 	"log" | 
 | 	"net" | 
 | 	"os" | 
 | 	"os/exec" | 
 | 	"path/filepath" | 
 |  | 
 | 	"github.com/spf13/cobra" | 
 | 	"google.golang.org/grpc" | 
 | 	clientauthentication "k8s.io/client-go/pkg/apis/clientauthentication/v1" | 
 | 	"k8s.io/client-go/tools/clientcmd" | 
 | 	clientapi "k8s.io/client-go/tools/clientcmd/api" | 
 |  | 
 | 	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.") | 
 | 	} | 
 | 	clusterEp := flags.clusterEndpoints[0] | 
 |  | 
 | 	ctx := clicontext.WithInterrupt(context.Background()) | 
 | 	ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(flags.configPath, "owner-key.pem")) | 
 | 	if os.IsNotExist(err) { | 
 | 		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.") | 
 | 	} else if err != nil { | 
 | 		log.Fatalf("Failed to load owner private key: %v", err) | 
 | 	} | 
 | 	block, _ := pem.Decode(ownerPrivateKeyPEM) | 
 | 	if block == nil { | 
 | 		log.Fatalf("owner-key.pem contains invalid PEM") | 
 | 	} | 
 | 	if block.Type != ownerKeyType { | 
 | 		log.Fatalf("owner-key.pem contains a PEM block that's not a %v", ownerKeyType) | 
 | 	} | 
 | 	if len(block.Bytes) != ed25519.PrivateKeySize { | 
 | 		log.Fatal("owner-key.pem contains non-Ed25519 key") | 
 | 	} | 
 | 	ownerPrivateKey := ed25519.PrivateKey(block.Bytes) | 
 |  | 
 | 	ephCreds, err := rpc.NewEphemeralCredentials(ownerPrivateKey, nil) | 
 | 	if err != nil { | 
 | 		log.Fatalf("Failed to create ephemeral credentials: %v", err) | 
 | 	} | 
 | 	client, err := grpc.Dial(net.JoinHostPort(clusterEp, node.CuratorServicePort.PortString()), grpc.WithTransportCredentials(ephCreds)) | 
 | 	if err != nil { | 
 | 		log.Fatalf("Failed to create client to given node address: %v", err) | 
 | 	} | 
 | 	defer client.Close() | 
 | 	aaa := apb.NewAAAClient(client) | 
 | 	ownerCert, err := rpc.RetrieveOwnerCertificate(ctx, aaa, ownerPrivateKey) | 
 | 	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. | 
 | 		InsecureSkipTLSVerify: true, | 
 | 		Server:                "https://" + net.JoinHostPort(clusterEp, 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) | 
 | } |