feat(provider): makes the provider API non stupid

This commit is contained in:
Thomas Maurice 2024-02-12 17:32:18 +01:00
parent 2ae8c6cca8
commit 86df61986e
Signed by: thomas
GPG key ID: 1D577F50583032A6
7 changed files with 288 additions and 176 deletions

View file

@ -8,8 +8,7 @@ import (
"git.maurice.fr/thomas/mailout/pkg/crypto" "git.maurice.fr/thomas/mailout/pkg/crypto"
"git.maurice.fr/thomas/mailout/pkg/database" "git.maurice.fr/thomas/mailout/pkg/database"
"git.maurice.fr/thomas/mailout/pkg/models" "git.maurice.fr/thomas/mailout/pkg/models"
"git.maurice.fr/thomas/mailout/pkg/providers/ovh" provider "git.maurice.fr/thomas/mailout/pkg/providers"
"git.maurice.fr/thomas/mailout/pkg/utils"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -20,11 +19,22 @@ var (
flagDKIMKeyActive bool flagDKIMKeyActive bool
flagSelector string flagSelector string
flagKeyBits int flagKeyBits int
flagProvider string
) )
var DKIMKeyCmd = &cobra.Command{ var DKIMKeyCmd = &cobra.Command{
Use: "dkimkey", Use: "dkimkey",
Short: "manages DKIM keys", 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{ var DKIMKeyAddCmd = &cobra.Command{
@ -228,93 +238,30 @@ var DKIMKeyPublishCmd = &cobra.Command{
"selector": dkimkey.Selector, "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 { 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 { if err != nil {
logger.WithError(err).Fatal("could not determine zone") logger.WithError(err).Fatal("could not publish dkim key")
}
subdomain, err := utils.GetSubdomain(dkimkey.DomainName)
if err != nil {
logger.WithError(err).Fatal("could not determine subdomain")
} }
if subdomain == "" { logger.Info("successfully published record")
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")
}
}, },
} }
@ -344,60 +291,36 @@ var DKIMKeyUnpublishCmd = &cobra.Command{
} }
dkimkey := qRes[0] dkimkey := qRes[0]
logger := logrus.WithFields(logrus.Fields{ logger := logrus.WithFields(logrus.Fields{
"dkimkey": dkimkey.ID, "dkimkey": dkimkey.ID,
"selector": dkimkey.Selector, "selector": dkimkey.Selector,
}) })
ovhClient, err := ovh.NewOVHProvider(cfg) pv := flagProvider
if err != nil { if flagProvider == "" {
logger.WithError(err).Fatal("could not get OVH DNS provider") pv = cfg.DefaultProvider
} if pv == "" {
logrus.Fatal("no provider specified")
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")
} }
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().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().StringVarP(&flagSelector, "selector", "s", "", "force a selector for the key")
DKIMKeyAddCmd.PersistentFlags().IntVarP(&flagKeyBits, "key-bits", "k", 2048, "force a size 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")
} }

View file

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"fmt"
"git.maurice.fr/thomas/mailout/pkg/config" "git.maurice.fr/thomas/mailout/pkg/config"
"github.com/spf13/cobra" "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() { func InitRootCmd() {
InitUserCmd() InitUserCmd()
InitDKIMKeyCmd() InitDKIMKeyCmd()

17
pkg/cmd/version.go Normal file
View file

@ -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)
},
}

191
pkg/providers/ovh.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -1 +1,17 @@
package provider 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,
}
)

7
pkg/version/version.go Normal file
View file

@ -0,0 +1,7 @@
package version
var (
Version = "master"
BuildTime = "now"
Commit = "master"
)