package main import ( "errors" "fmt" "isle/daemon" "isle/jsonutil" "isle/nebula" "net/netip" "os" ) var subCmdNebulaCreateCert = subCmd{ name: "create-cert", descr: "Creates a signed nebula certificate file for an existing host and writes it to stdout", do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) hostName nebula.HostName ip netip.Addr ) hostNameF := flags.VarPF( textUnmarshalerFlag{&hostName}, "hostname", "h", "Name of the host to generate a certificate for", ) pubKeyPath := flags.StringP( "public-key-path", "p", "", `Path to PEM file containing public key which will be embedded in the cert.`, ) flags.Var( textUnmarshalerFlag{&ip}, "ip", "IP address to create a cert for. If this is not given then the IP associated with the host via its `hosts create` call will be used", ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed || *pubKeyPath == "" { return errors.New("--hostname and --pub-key-path are required") } hostPubPEM, err := os.ReadFile(*pubKeyPath) if err != nil { return fmt.Errorf("reading public key from %q: %w", *pubKeyPath, err) } var hostPub nebula.EncryptingPublicKey if err := hostPub.UnmarshalNebulaPEM(hostPubPEM); err != nil { return fmt.Errorf("unmarshaling public key as PEM: %w", err) } var res daemon.CreateNebulaCertificateResult err = subCmdCtx.daemonRCPClient.Call( subCmdCtx.ctx, &res, "CreateNebulaCertificate", daemon.CreateNebulaCertificateRequest{ HostName: hostName, HostEncryptingPublicKey: hostPub, Opts: daemon.CreateNebulaCertificateOpts{ IP: ip, }, }, ) if err != nil { return fmt.Errorf("calling CreateNebulaCertificate: %w", err) } nebulaHostCertPEM, err := res.HostNebulaCertifcate.Unwrap().MarshalToPEM() if err != nil { return fmt.Errorf("marshaling cert to PEM: %w", err) } if _, err := os.Stdout.Write([]byte(nebulaHostCertPEM)); err != nil { return fmt.Errorf("writing to stdout: %w", err) } return nil }, } var subCmdNebulaShow = subCmd{ name: "show", descr: "Writes nebula network information to stdout in JSON format", do: func(subCmdCtx subCmdCtx) error { flags := subCmdCtx.flagSet(false) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } hosts, err := subCmdCtx.getHosts() if err != nil { return fmt.Errorf("getting hosts: %w", err) } var caPublicCreds nebula.CAPublicCredentials err = subCmdCtx.daemonRCPClient.Call( subCmdCtx.ctx, &caPublicCreds, "GetNebulaCAPublicCredentials", nil, ) if err != nil { return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err) } caCert := caPublicCreds.Cert caCertDetails := caCert.Unwrap().Details if len(caCertDetails.Subnets) != 1 { return fmt.Errorf( "malformed ca.crt, contains unexpected subnets %#v", caCertDetails.Subnets, ) } subnet := caCertDetails.Subnets[0] type outLighthouse struct { PublicAddr string IP string } out := struct { CACert nebula.Certificate SubnetCIDR string Lighthouses []outLighthouse }{ CACert: caCert, SubnetCIDR: subnet.String(), } for _, h := range hosts.Hosts { if h.Nebula.PublicAddr == "" { continue } out.Lighthouses = append(out.Lighthouses, outLighthouse{ PublicAddr: h.Nebula.PublicAddr, IP: h.IP().String(), }) } if err := jsonutil.WriteIndented(os.Stdout, out); err != nil { return fmt.Errorf("encoding to stdout: %w", err) } return nil }, } var subCmdNebula = subCmd{ name: "nebula", descr: "Sub-commands related to the nebula VPN", do: func(subCmdCtx subCmdCtx) error { return subCmdCtx.doSubCmd( subCmdNebulaCreateCert, subCmdNebulaShow, ) }, }