package main import ( "errors" "fmt" "isle/daemon" "isle/jsonutil" "isle/nebula" "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 ) hostNameF := flags.VarPF( textUnmarshalerFlag{&hostName}, "hostname", "h", "Name of the host to generate a certificate for", ) adminPath := flags.StringP( "admin-path", "a", "", `Path to admin.json file. If the given path is "-" then stdin is used.`, ) pubKeyPath := flags.StringP( "public-key-path", "p", "", `Path to PEM file containing public key which will be embedded in the cert.`, ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed || *adminPath == "" || *pubKeyPath == "" { return errors.New("--hostname, --admin-path, and --pub-key-path are required") } adm, err := readAdmin(*adminPath) if err != nil { return fmt.Errorf("reading admin.json with --admin-path of %q: %w", *adminPath, err) } 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{ CASigningPrivateKey: adm.Nebula.CACredentials.SigningPrivateKey, HostName: hostName, HostEncryptingPublicKey: hostPub, }, ) 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, ) }, }