From 86df61986ef05a705fe076759a476c3e8628456c Mon Sep 17 00:00:00 2001 From: Thomas Maurice <thomas@maurice.fr> Date: Mon, 12 Feb 2024 17:32:18 +0100 Subject: [PATCH] feat(provider): makes the provider API non stupid --- pkg/cmd/dkimkey.go | 187 ++++++++++++------------------------- pkg/cmd/root.go | 11 --- pkg/cmd/version.go | 17 ++++ pkg/providers/ovh.go | 191 ++++++++++++++++++++++++++++++++++++++ pkg/providers/ovh/ovh.go | 35 ------- pkg/providers/provider.go | 16 ++++ pkg/version/version.go | 7 ++ 7 files changed, 288 insertions(+), 176 deletions(-) create mode 100644 pkg/cmd/version.go create mode 100644 pkg/providers/ovh.go delete mode 100644 pkg/providers/ovh/ovh.go create mode 100644 pkg/version/version.go diff --git a/pkg/cmd/dkimkey.go b/pkg/cmd/dkimkey.go index 949a7e1..1be46b7 100644 --- a/pkg/cmd/dkimkey.go +++ b/pkg/cmd/dkimkey.go @@ -8,8 +8,7 @@ import ( "git.maurice.fr/thomas/mailout/pkg/crypto" "git.maurice.fr/thomas/mailout/pkg/database" "git.maurice.fr/thomas/mailout/pkg/models" - "git.maurice.fr/thomas/mailout/pkg/providers/ovh" - "git.maurice.fr/thomas/mailout/pkg/utils" + provider "git.maurice.fr/thomas/mailout/pkg/providers" "github.com/google/uuid" "github.com/pterm/pterm" "github.com/sirupsen/logrus" @@ -20,11 +19,22 @@ 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.DefaultProvider == "" { + logrus.Fatal("no provider specified and no default provider in config, aborting") + } + flagProvider = cfg.DefaultProvider + } + + return nil + }, } var DKIMKeyAddCmd = &cobra.Command{ @@ -228,93 +238,30 @@ var DKIMKeyPublishCmd = &cobra.Command{ "selector": dkimkey.Selector, }) - ovhClient, err := ovh.NewOVHProvider(cfg) + pv := flagProvider + if flagProvider == "" { + pv = cfg.DefaultProvider + 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 get OVH DNS provider") + logger.WithError(err).Fatal("could not create provider") } - zone, err := utils.GetZone(dkimkey.DomainName) + err = p.AddDKIMRecord(&dkimkey) if err != nil { - logger.WithError(err).Fatal("could not determine zone") - } - subdomain, err := utils.GetSubdomain(dkimkey.DomainName) - if err != nil { - logger.WithError(err).Fatal("could not determine subdomain") + logger.WithError(err).Fatal("could not publish dkim key") } - if subdomain == "" { - subdomain = zone - } - - logger = logger.WithFields(logrus.Fields{ - "zone": zone, - "subdomain": subdomain, - }) - - dkimSub := fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain) - result := make([]int, 0) - - err = ovhClient.Client.Get(fmt.Sprintf("/domain/zone/%s/record?fieldType=TXT&subDomain=%s", zone, dkimSub), &result) - - if err != nil { - logger.WithError(err).Fatal("could not lookup records") - } - - type createParams struct { - FieldType string `json:"fieldType"` - SubDomain string `json:"subDomain"` - Target string `json:"target"` - TTL int `json:"ttl"` - } - - type updateParams struct { - SubDomain string `json:"subDomain"` - Target string `json:"target"` - TTL int `json:"ttl"` - } - - if len(result) == 0 { - logger.Info("no DKIM records found, creating a new one") - - c := createParams{ - FieldType: "TXT", - SubDomain: fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain), - Target: fmt.Sprintf("\"v=DKIM1; k=rsa; p=%s\"", dkimkey.PublicKey), - TTL: 60, - } - - err = ovhClient.Client.Post(fmt.Sprintf("/domain/zone/%s/record", zone), &c, nil) - if err != nil { - logger.WithError(err).Fatal("could not create new record") - } - logger.Info("created new DKIM record") - - err = ovhClient.Client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) - if err != nil { - logger.WithError(err).Fatal("could not refresh the zone") - } - logger.Info("refreshed zone") - } else if len(result) == 1 { - logger.Info("found one record, updating it") - - u := updateParams{ - SubDomain: fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain), - Target: fmt.Sprintf("\"v=DKIM1; k=foo; p=%s\"", dkimkey.PublicKey), - TTL: 60, - } - - err = ovhClient.Client.Put(fmt.Sprintf("/domain/zone/%s/record/%d", zone, result[0]), &u, nil) - if err != nil { - logger.WithError(err).Fatal("could not update record") - } - logger.Info("updated existing record") - - err = ovhClient.Client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) - if err != nil { - logger.WithError(err).Fatal("could not refresh the zone") - } - logger.Info("refreshed zone") - } + logger.Info("successfully published record") }, } @@ -344,60 +291,36 @@ var DKIMKeyUnpublishCmd = &cobra.Command{ } dkimkey := qRes[0] + logger := logrus.WithFields(logrus.Fields{ "dkimkey": dkimkey.ID, "selector": dkimkey.Selector, }) - ovhClient, err := ovh.NewOVHProvider(cfg) - if err != nil { - logger.WithError(err).Fatal("could not get OVH DNS provider") - } - - zone, err := utils.GetZone(dkimkey.DomainName) - if err != nil { - logger.WithError(err).Fatal("could not determine zone") - } - subdomain, err := utils.GetSubdomain(dkimkey.DomainName) - if err != nil { - logger.WithError(err).Fatal("could not determine subdomain") - } - - if subdomain == "" { - subdomain = zone - } - - logger = logger.WithFields(logrus.Fields{ - "zone": zone, - "subdomain": subdomain, - }) - - dkimSub := fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain) - result := make([]int, 0) - - err = ovhClient.Client.Get(fmt.Sprintf("/domain/zone/%s/record?fieldType=TXT&subDomain=%s", zone, dkimSub), &result) - - if err != nil { - logger.WithError(err).Fatal("could not lookup records") - } - - if len(result) == 0 { - logger.Info("no DKIM records found, no need to do anything") - } else if len(result) == 1 { - logger.Info("found one record, deleting it") - - err = ovhClient.Client.Delete(fmt.Sprintf("/domain/zone/%s/record/%d", zone, result[0]), nil) - if err != nil { - logger.WithError(err).Fatal("could not delete record") + pv := flagProvider + if flagProvider == "" { + pv = cfg.DefaultProvider + if pv == "" { + logrus.Fatal("no provider specified") } - logger.Info("deleted existing record") - - err = ovhClient.Client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) - if err != nil { - logger.WithError(err).Fatal("could not refresh the zone") - } - logger.Info("refreshed zone") } + + 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") }, } @@ -414,4 +337,8 @@ func InitDKIMKeyCmd() { 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") } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index cb7e71e..ce86c25 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "git.maurice.fr/thomas/mailout/pkg/config" "github.com/spf13/cobra" ) @@ -26,15 +24,6 @@ var RootCmd = &cobra.Command{ }, } -var VersionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("v0.0.1") - }, -} - func InitRootCmd() { InitUserCmd() InitDKIMKeyCmd() diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go new file mode 100644 index 0000000..574c987 --- /dev/null +++ b/pkg/cmd/version.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "fmt" + + "git.maurice.fr/thomas/mailout/pkg/version" + "github.com/spf13/cobra" +) + +var VersionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("mailoutctl %s (%s) built on %s\n", version.Version, version.Commit, version.BuildTime) + }, +} diff --git a/pkg/providers/ovh.go b/pkg/providers/ovh.go new file mode 100644 index 0000000..06b00ef --- /dev/null +++ b/pkg/providers/ovh.go @@ -0,0 +1,191 @@ +package provider + +import ( + "fmt" + + "git.maurice.fr/thomas/mailout/pkg/config" + "git.maurice.fr/thomas/mailout/pkg/models" + "git.maurice.fr/thomas/mailout/pkg/utils" + ovhgo "github.com/ovh/go-ovh/ovh" + "github.com/sirupsen/logrus" +) + +type OVHProvider struct { + client *ovhgo.Client +} + +func NewOVHProvider(cfg *config.Config) (Provider, error) { + config := cfg.Providers.OVH + + if config == nil { + return nil, fmt.Errorf("no ovh configuration specified") + } + + c, err := ovhgo.NewClient( + config.Endpoint, + config.ApplicationKey, + config.ApplicationSecret, + config.ConsumerKey, + ) + + if err != nil { + return nil, err + } + + return &OVHProvider{ + client: c, + }, nil +} + +func (p *OVHProvider) AddDKIMRecord(dkimkey *models.DKIMKey) error { + logger := logrus.WithFields(logrus.Fields{ + "dkimkey": dkimkey.ID, + "selector": dkimkey.Selector, + }) + + zone, err := utils.GetZone(dkimkey.DomainName) + if err != nil { + return fmt.Errorf("could not determine zone: %w", err) + } + + subdomain, err := utils.GetSubdomain(dkimkey.DomainName) + if err != nil { + return fmt.Errorf("could not determine subdomain: %w", err) + } + + if subdomain == "" { + subdomain = zone + } + + logger = logger.WithFields(logrus.Fields{ + "zone": zone, + "subdomain": subdomain, + }) + + dkimSub := fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain) + result := make([]int, 0) + + err = p.client.Get(fmt.Sprintf("/domain/zone/%s/record?fieldType=TXT&subDomain=%s", zone, dkimSub), &result) + + if err != nil { + return fmt.Errorf("could not lookup records: %w", err) + } + + type createParams struct { + FieldType string `json:"fieldType"` + SubDomain string `json:"subDomain"` + Target string `json:"target"` + TTL int `json:"ttl"` + } + + type updateParams struct { + SubDomain string `json:"subDomain"` + Target string `json:"target"` + TTL int `json:"ttl"` + } + + if len(result) == 0 { + logger.Info("no DKIM records found, creating a new one") + + c := createParams{ + FieldType: "TXT", + SubDomain: fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain), + Target: fmt.Sprintf("v=DKIM1; k=rsa; p=%s", dkimkey.PublicKey), + TTL: 60, + } + + err = p.client.Post(fmt.Sprintf("/domain/zone/%s/record", zone), &c, nil) + if err != nil { + return fmt.Errorf("could not create new record: %w", err) + } + logger.Info("created new DKIM record") + + err = p.client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) + if err != nil { + return fmt.Errorf("could not refresh the zone: %w", err) + } + logger.Info("refreshed zone") + } else if len(result) == 1 { + logger.Info("found one record, updating it") + + u := updateParams{ + SubDomain: fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain), + Target: fmt.Sprintf("v=DKIM1; k=rsa; p=%s", dkimkey.PublicKey), + TTL: 60, + } + + err = p.client.Put(fmt.Sprintf("/domain/zone/%s/record/%d", zone, result[0]), &u, nil) + if err != nil { + return fmt.Errorf("could not update record: %w", err) + } + logger.Info("updated existing record") + + err = p.client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) + if err != nil { + return fmt.Errorf("could not refresh the zone: %w", err) + } + logger.Info("refreshed zone") + } else { + logrus.Error("more than 1 records matched the query, it is unsafe for me to proceed, check the DNS zone manually") + return fmt.Errorf("more than one record returned for update/creation") + } + + return nil +} + +func (p *OVHProvider) DeleteDKIMRecord(dkimkey *models.DKIMKey) error { + logger := logrus.WithFields(logrus.Fields{ + "dkimkey": dkimkey.ID, + "selector": dkimkey.Selector, + }) + + zone, err := utils.GetZone(dkimkey.DomainName) + if err != nil { + return fmt.Errorf("could not determine zone: %w", err) + } + subdomain, err := utils.GetSubdomain(dkimkey.DomainName) + if err != nil { + return fmt.Errorf("could not determine subdomain: %w", err) + } + + if subdomain == "" { + subdomain = zone + } + + logger = logger.WithFields(logrus.Fields{ + "zone": zone, + "subdomain": subdomain, + }) + + dkimSub := fmt.Sprintf("%s._domainkey.%s", dkimkey.Selector, subdomain) + result := make([]int, 0) + + err = p.client.Get(fmt.Sprintf("/domain/zone/%s/record?fieldType=TXT&subDomain=%s", zone, dkimSub), &result) + + if err != nil { + return fmt.Errorf("could not lookup records: %w", err) + } + + if len(result) == 0 { + logger.Info("no DKIM records found, no need to do anything") + } else if len(result) == 1 { + logger.Info("found one record, deleting it") + + err = p.client.Delete(fmt.Sprintf("/domain/zone/%s/record/%d", zone, result[0]), nil) + if err != nil { + return fmt.Errorf("could not delete record: %w", err) + } + logger.Info("deleted existing record") + + err = p.client.Post(fmt.Sprintf("/domain/zone/%s/refresh", zone), nil, nil) + if err != nil { + return fmt.Errorf("could not refresh the zone: %w", err) + } + logger.Info("refreshed zone") + } else { + logrus.Error("more than 1 records matched the query, it is unsafe for me to proceed, check the DNS zone manually") + return fmt.Errorf("more than one record returned for deletion") + } + + return nil +} diff --git a/pkg/providers/ovh/ovh.go b/pkg/providers/ovh/ovh.go deleted file mode 100644 index 1be1689..0000000 --- a/pkg/providers/ovh/ovh.go +++ /dev/null @@ -1,35 +0,0 @@ -package ovh - -import ( - "fmt" - - "git.maurice.fr/thomas/mailout/pkg/config" - ovhgo "github.com/ovh/go-ovh/ovh" -) - -type OVHProvider struct { - Client *ovhgo.Client -} - -func NewOVHProvider(cfg *config.Config) (*OVHProvider, error) { - config := cfg.Providers.OVH - - if config == nil { - return nil, fmt.Errorf("no ovh configuration specified") - } - - c, err := ovhgo.NewClient( - config.Endpoint, - config.ApplicationKey, - config.ApplicationSecret, - config.ConsumerKey, - ) - - if err != nil { - return nil, err - } - - return &OVHProvider{ - Client: c, - }, nil -} diff --git a/pkg/providers/provider.go b/pkg/providers/provider.go index 4f504f6..07d2760 100644 --- a/pkg/providers/provider.go +++ b/pkg/providers/provider.go @@ -1 +1,17 @@ package provider + +import ( + "git.maurice.fr/thomas/mailout/pkg/config" + "git.maurice.fr/thomas/mailout/pkg/models" +) + +type Provider interface { + AddDKIMRecord(*models.DKIMKey) error + DeleteDKIMRecord(*models.DKIMKey) error +} + +var ( + Providers = map[string]func(*config.Config) (Provider, error){ + "ovh": NewOVHProvider, + } +) diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..6c7b25a --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,7 @@ +package version + +var ( + Version = "master" + BuildTime = "now" + Commit = "master" +)