Skip to content

Commit

Permalink
upstream update
Browse files Browse the repository at this point in the history
  • Loading branch information
bagel-dawg committed Dec 12, 2024
2 parents 8f2195b + f673a06 commit 9f4fc28
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 26 deletions.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
BINARY ?= ses-smtpd-proxy
DOCKER_IMAGE ?= docker.crute.me/ses-email-proxy:latest

$(BINARY): main.go go.sum smtpd/smtpd.go
CGO_ENABLED=0 go build \
-ldflags "-X main.version=$(shell git describe --long --tags --dirty --always)" \
-o $@ $<

.PHONY: docker
docker: $(BINARY)
docker build -t $(DOCKER_IMAGE) .

.PHONY: publish
publish: docker
docker push $(DOCKER_IMAGE)

go.sum: go.mod
go mod tidy

.PHONY: clean
clean:
rm $(BINARY) || true
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ credential using the AWS back-end. It will also renew this credential as
long as possible. This functionality is not enabled by default but can
be enabled with command line flags and environment variables.

The [standard environment variables](
https://developer.hashicorp.com/vault/docs/commands#environme nt-variables)
The [standard environment variables](https://developer.hashicorp.com/vault/docs/commands#environment-variables)
are supported. Minimally ``VAULT_ADDR`` must be specified as a URL to the
Vault server. Additionally, to support
[AppRole](https://developer.hashicorp.com/vault/docs/auth/approle) authentication
Expand Down Expand Up @@ -92,7 +91,7 @@ please:
* Update the readme, if necessary
* Follow the coding style of the current code-base
* Ensure that your code is formatted by gofmt
* Validate that your changes work with Go 1.11+
* Validate that your changes work with Go 1.21+

All code is reviewed before acceptance and changes may be requested to better
follow the conventions of the existing API.
Expand All @@ -101,3 +100,4 @@ follow the conventions of the existing API.
* Mike Crute (@mcrute)
* Thomas Dupas (@thomasdupas)
* Quentin Loos (@Kent1)
* Moriyoshi Koizumi (@moriyoshi)
99 changes: 76 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"code.crute.us/mcrute/ses-smtpd-proxy/smtpd"
"github.com/aws/aws-sdk-go/aws"
Expand All @@ -21,13 +24,13 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
)

var version string

const (
SesSizeLimit = 10000000
DefaultAddr = ":2500"
)

var sesClient *ses.SES

var (
emailSent = promauto.NewCounter(prometheus.CounterOpts{
Namespace: "smtpd",
Expand Down Expand Up @@ -57,9 +60,11 @@ var (
)

type Envelope struct {
from string
rcpts []*string
b bytes.Buffer
from string
client *ses.SES
configSetName *string
rcpts []*string
b bytes.Buffer
}

func (e *Envelope) AddRecipient(rcpt smtpd.MailAddress) error {
Expand Down Expand Up @@ -97,11 +102,12 @@ func (e *Envelope) logMessageSend() {

func (e *Envelope) Close() error {
r := &ses.SendRawEmailInput{
Source: &e.from,
Destinations: e.rcpts,
RawMessage: &ses.RawMessage{Data: e.b.Bytes()},
ConfigurationSetName: e.configSetName,
Source: &e.from,
Destinations: e.rcpts,
RawMessage: &ses.RawMessage{Data: e.b.Bytes()},
}
_, err := sesClient.SendRawEmail(r)
_, err := e.client.SendRawEmail(r)
if err != nil {
log.Printf("ERROR: ses: %v", err)
emailError.With(prometheus.Labels{"type": "ses error"}).Inc()
Expand All @@ -112,7 +118,24 @@ func (e *Envelope) Close() error {
return err
}

func renewSecret(vc *api.Client, s *api.Secret) error {
func logRenewal(renewal *api.RenewOutput) {
canRenew := "renewable"
if !renewal.Secret.Renewable {
canRenew = "not renewable"
}
leaseID := renewal.Secret.LeaseID
if leaseID == "" && renewal.Secret.MountType == "token" {
leaseID = "vault_token"
}
log.Printf("Successfully renewed lease '%s' at %s for %s, %s",
leaseID,
renewal.RenewedAt.Format(time.RFC3339),
time.Duration(renewal.Secret.LeaseDuration)*time.Second,
canRenew,
)
}

func renewSecret(vc *api.Client, s *api.Secret, credentialError chan<- error) error {
w, err := vc.NewLifetimeWatcher(&api.LifetimeWatcherInput{Secret: s})
if err != nil {
return err
Expand All @@ -125,19 +148,19 @@ func renewSecret(vc *api.Client, s *api.Secret) error {
case err := <-w.DoneCh():
if err != nil {
credentialRenewalError.Inc()
log.Fatalf("Error renewing credential: %s", err)
credentialError <- err
}
case renewal := <-w.RenewCh():
credentialRenewalSuccess.Inc()
log.Printf("Successfully renewed: %#v", renewal)
logRenewal(renewal)
}
}
}()

return nil
}

func getVaultSecret(path string) (credentials.Value, error) {
func getVaultSecret(ctx context.Context, path string, credentialError chan<- error) (credentials.Value, error) {
var r credentials.Value

vc, err := api.NewClient(api.DefaultConfig())
Expand All @@ -154,10 +177,10 @@ func getVaultSecret(path string) (credentials.Value, error) {
if err != nil {
return r, fmt.Errorf("unable to initialize AppRole auth method: %w", err)
}
if loginSecret, err := vc.Auth().Login(context.Background(), appRoleAuth); err != nil {
if loginSecret, err := vc.Auth().Login(ctx, appRoleAuth); err != nil {
return r, fmt.Errorf("unable to login to AppRole auth method: %w", err)
} else {
if err := renewSecret(vc, loginSecret); err != nil {
if err := renewSecret(vc, loginSecret, credentialError); err != nil {
return r, err
}
}
Expand All @@ -184,15 +207,15 @@ func getVaultSecret(path string) (credentials.Value, error) {
r.AccessKeyID = keyId.(string)
r.SecretAccessKey = secretKey.(string)

return r, renewSecret(vc, secret)
return r, renewSecret(vc, secret, credentialError)
}

func makeSesClient(enableVault bool, vaultPath string) (*ses.SES, error) {
func makeSesClient(ctx context.Context, enableVault bool, vaultPath string, credentialError chan<- error) (*ses.SES, error) {
var err error
var s *session.Session

if enableVault {
cred, err := getVaultSecret(vaultPath)
cred, err := getVaultSecret(ctx, vaultPath, credentialError)
if err != nil {
return nil, err
}
Expand All @@ -213,14 +236,25 @@ func makeSesClient(enableVault bool, vaultPath string) (*ses.SES, error) {
func main() {
var err error

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer cancel()

disablePrometheus := flag.Bool("disable-prometheus", false, "Disables prometheus metrics server")
prometheusBind := flag.String("prometheus-bind", ":2501", "Address/port on which to bind Prometheus server")
enableVault := flag.Bool("enable-vault", false, "Enable fetching AWS IAM credentials from a Vault server")
vaultPath := flag.String("vault-path", "", "Full path to Vault credential (ex: \"aws/creds/my-mail-user\")")
showVersion := flag.Bool("version", false, "Show program version")
configurationSetName := flag.String("configuration-set-name", "", "Configuration set name with which SendRawEmail will be invoked")

flag.Parse()

sesClient, err = makeSesClient(*enableVault, *vaultPath)
if *showVersion {
fmt.Printf("ses-smtp-proxy version %s\n", version)
return
}

credentialError := make(chan error, 2)
sesClient, err := makeSesClient(ctx, *enableVault, *vaultPath, credentialError)
if err != nil {
log.Fatalf("Error creating AWS session: %s", err)
}
Expand All @@ -239,15 +273,34 @@ func main() {
go ps.ListenAndServe()
}

if *configurationSetName == "" {
configurationSetName = nil
}

s := &smtpd.Server{
Addr: addr,
OnNewMail: func(c smtpd.Connection, from smtpd.MailAddress) (smtpd.Envelope, error) {
return &Envelope{from: from.Email()}, nil
return &Envelope{
from: from.Email(),
client: sesClient,
configSetName: configurationSetName,
}, nil
},
}

log.Printf("ListenAndServe on %s", addr)
if err := s.ListenAndServe(); err != nil {
log.Fatalf("ListenAndServe: %v", err)
go func() {
log.Printf("ListenAndServe on %s", addr)
if err := s.ListenAndServe(); err != nil {
log.Printf("Error in ListenAndServe: %v", err)
}
}()

select {
case <-ctx.Done():
log.Printf("SIGTERM/SIGINT received, shutting down")
os.Exit(0)
case err := <-credentialError:
log.Fatalf("Error renewing credential: %s", err)
os.Exit(1)
}
}

0 comments on commit 9f4fc28

Please sign in to comment.