package cmd import ( "crypto/rand" "encoding/hex" "fmt" "git.maurice.fr/thomas/mailout/pkg/crypto" "git.maurice.fr/thomas/mailout/pkg/database" "git.maurice.fr/thomas/mailout/pkg/models" provider "git.maurice.fr/thomas/mailout/pkg/providers" "github.com/google/uuid" "github.com/pterm/pterm" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( flagDKIMKeyActive bool flagSelector string flagKeyBits int flagProvider string ) var DKIMKeyCmd = &cobra.Command{ Use: "dkimkey", Short: "manages DKIM keys", PreRunE: func(cmd *cobra.Command, args []string) error { if flagProvider == "" { if cfg.Defaults.Provider == "" { logrus.Fatal("no provider specified and no default provider in config, aborting") } flagProvider = cfg.Defaults.Provider } return nil }, } var DKIMKeyAddCmd = &cobra.Command{ Use: "add [domain]", Short: "adds a DKIM signing key", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } priv, pub, err := crypto.GenerateKeyPair(flagKeyBits) if err != nil { panic(err) } selector := flagSelector if selector == "" { b := make([]byte, 8) _, err := rand.Read(b) if err != nil { logrus.Fatal("entropy generator is kill") } selector = hex.EncodeToString(b) } dkimkey := models.DKIMKey{ DomainName: args[0], PublicKey: pub, PrivateKey: priv, Active: flagUserActive, Selector: selector, Record: fmt.Sprintf("%s._domainkey.%s. IN TXT \"v=DKIM1; k=rsa; p=%s\"", selector, args[0], pub), } err = db.Save(&dkimkey).Error if err != nil { logrus.WithError(err).Fatal("could not create DKIM key") } logrus.Infof("created DKIM key %s", dkimkey.ID) }, } var DKIMKeyListCmd = &cobra.Command{ Use: "list", Short: "list DKIM keys", Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } qRes := make([]models.DKIMKey, 0) err = db.Find(&qRes).Error if err != nil { logrus.WithError(err).Fatal("could not list keys") } tData := pterm.TableData{ {"id", "selector", "domain", "active", "created_at", "updated_at"}, } for _, k := range qRes { tData = append(tData, []string{k.ID.String(), k.Selector, k.DomainName, fmt.Sprintf("%v", k.Active), k.CreatedAt.String(), k.UpdatedAt.String()}) } pterm.DefaultTable.WithHasHeader().WithData(tData).Render() }, } var DKIMKeyRecordCmd = &cobra.Command{ Use: "record", Short: "gives the dns record for a key", Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } qRes := make([]models.DKIMKey, 0) id, err := uuid.Parse(args[0]) if err != nil { logrus.WithError(err).Fatal("invalid UUID") } err = db.Where(models.DKIMKey{ID: id}).Find(&qRes).Error if err != nil { logrus.WithError(err).Fatal("could not search keys") } if len(qRes) == 0 { logrus.WithError(err).Fatal("No such key") } fmt.Println(qRes[0].Record) }, } var DKIMKeyDeleteCmd = &cobra.Command{ Use: "delete", Short: "delete DKIM keys", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } id, err := uuid.Parse(args[0]) if err != nil { logrus.WithError(err).Fatal("invalid UUID") } err = db.Where(&models.DKIMKey{ID: id}).Delete(&models.DKIMKey{}).Error if err != nil { logrus.WithError(err).Fatal("could not delete DKIM key") } logrus.Infof("deleted DKIM key %s if it existed", args[0]) }, } var DKIMKeyActivateCmd = &cobra.Command{ Use: "activate", Short: "activates a DKIM key", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } id, err := uuid.Parse(args[0]) if err != nil { logrus.WithError(err).Fatal("invalid UUID") } err = db.Model(&models.DKIMKey{}).Where(&models.DKIMKey{ID: id}).Update("active", true).Error if err != nil { logrus.WithError(err).Fatal("could not activate DKIM key") } logrus.Infof("activated DKIM key %s", args[0]) }, } var DKIMKeyDeactivateCmd = &cobra.Command{ Use: "deactivate", Short: "deactivates a DKIM key", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } id, err := uuid.Parse(args[0]) if err != nil { logrus.WithError(err).Fatal("invalid UUID") } err = db.Model(&models.DKIMKey{}).Where(&models.DKIMKey{ID: id}).Update("active", false).Error if err != nil { logrus.WithError(err).Fatal("could not deactivate DKIM key") } logrus.Infof("deactivated DKIM key %s", args[0]) }, } var DKIMKeyPublishCmd = &cobra.Command{ Use: "publish", Short: "publishes a DKIM key onto a DNS provider", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } qRes := make([]models.DKIMKey, 0) id, err := uuid.Parse(args[0]) if err != nil { logrus.WithError(err).Fatal("invalid UUID") } err = db.Where(models.DKIMKey{ID: id}).Find(&qRes).Error if err != nil { logrus.WithError(err).Fatal("could not get key") } if len(qRes) == 0 { logrus.WithError(err).Fatal("No such key") } dkimkey := qRes[0] logger := logrus.WithFields(logrus.Fields{ "dkimkey": dkimkey.ID, "selector": dkimkey.Selector, }) pv := flagProvider if flagProvider == "" { pv = cfg.Defaults.Provider if pv == "" { logrus.Fatal("no provider specified") } } pGen, ok := provider.Providers[pv] if !ok { logger.Fatalf("no such provider: %s", flagProvider) } p, err := pGen(cfg) if err != nil { logger.WithError(err).Fatal("could not create provider") } err = p.AddDKIMRecord(&dkimkey) if err != nil { logger.WithError(err).Fatal("could not publish dkim key") } logger.Info("successfully published record") }, } var DKIMKeyUnpublishCmd = &cobra.Command{ Use: "unpublish", Short: "removes a DKIM key onto a DNS provider", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { db, err := database.NewDB(cfg) if err != nil { logrus.WithError(err).Fatal("could not connect to the database") } qRes := make([]models.DKIMKey, 0) id, err := uuid.Parse(args[0]) if err != nil { logrus.WithError(err).Fatal("invalid UUID") } err = db.Where(models.DKIMKey{ID: id}).Find(&qRes).Error if err != nil { logrus.WithError(err).Fatal("could not get key") } if len(qRes) == 0 { logrus.WithError(err).Fatal("No such key") } dkimkey := qRes[0] logger := logrus.WithFields(logrus.Fields{ "dkimkey": dkimkey.ID, "selector": dkimkey.Selector, }) pv := flagProvider if flagProvider == "" { pv = cfg.Defaults.Provider if pv == "" { logrus.Fatal("no provider specified") } } pGen, ok := provider.Providers[pv] if !ok { logger.Fatalf("no such provider: %s", flagProvider) } p, err := pGen(cfg) if err != nil { logger.WithError(err).Fatal("could not create provider") } err = p.DeleteDKIMRecord(&dkimkey) if err != nil { logger.WithError(err).Fatal("could not unpublish dkim key") } logger.Info("successfully unpublished record") }, } func InitDKIMKeyCmd() { DKIMKeyCmd.AddCommand(DKIMKeyAddCmd) DKIMKeyCmd.AddCommand(DKIMKeyListCmd) DKIMKeyCmd.AddCommand(DKIMKeyDeleteCmd) DKIMKeyCmd.AddCommand(DKIMKeyActivateCmd) DKIMKeyCmd.AddCommand(DKIMKeyDeactivateCmd) DKIMKeyCmd.AddCommand(DKIMKeyRecordCmd) DKIMKeyCmd.AddCommand(DKIMKeyPublishCmd) DKIMKeyCmd.AddCommand(DKIMKeyUnpublishCmd) DKIMKeyAddCmd.PersistentFlags().BoolVarP(&flagDKIMKeyActive, "active", "a", true, "whether or not the created key is active") DKIMKeyAddCmd.PersistentFlags().StringVarP(&flagSelector, "selector", "s", "", "force a selector for the key") DKIMKeyAddCmd.PersistentFlags().IntVarP(&flagKeyBits, "key-bits", "k", 2048, "force a size for the key") DKIMKeyPublishCmd.PersistentFlags().StringVarP(&flagProvider, "provider", "p", "", "provider to which publish the change to") DKIMKeyUnpublishCmd.PersistentFlags().StringVarP(&flagProvider, "provider", "p", "", "provider to which unpublish the change from") }