Skip to content

Commit

Permalink
feat: init
Browse files Browse the repository at this point in the history
  • Loading branch information
maxneuvians authored Dec 15, 2023
1 parent 15fed37 commit 1529e3c
Show file tree
Hide file tree
Showing 21 changed files with 1,231 additions and 8 deletions.
21 changes: 21 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
"name": "Go",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/go:1-1.21-bullseye",
"containerEnv": {
"SHELL": "/bin/zsh"
},
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "telnet"
}
},
"extensions": [
"redhat.vscode-yaml",
"github.copilot"
],
"remoteUser": "vscode"
}
17 changes: 17 additions & 0 deletions .github/workflows/ci_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: CI Latest Release
on:
pull_request:
branches:
- main

jobs:
ci-latest-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0

- name: Build a new release based on the code
run: make release-test

- name: Diff binaries
run: diff -u ./release/latest/smtp-proxy-for-notify ./release/latest/smtp-proxy-for-notify-test || exit 1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM alpine:3.6 as alpine

RUN apk add -U --no-cache ca-certificates

FROM scratch

ARG SMTP_TLS_CERT_FILE
ARG SMTP_TLS_KEY_FILE
ARG SMTP_TLS_CERT_FILE_DESTINATION_DIR=/etc/ssl/certs/
ARG SMTP_TLS_KEY_FILE_DESTINATION_DIR=/etc/ssl/private/

# Copy the CA certs from the alpine image
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Copy the TLS cert and key files if they are defined.
# Copying the LICENSE just allows for an optional copy of the certs if they are not defined.
COPY LICENSE ${SMTP_TLS_CERT_FILE}* ${SMTP_TLS_CERT_FILE_DESTINATION_DIR}
COPY LICENSE ${SMTP_TLS_KEY_FILE}* ${SMTP_TLS_KEY_FILE_DESTINATION_DIR}

# Copy the binary
COPY ./release/latest/smtp-proxy-for-notify /smtp-proxy-for-notify

# Run the binary
ENTRYPOINT ["/smtp-proxy-for-notify"]
11 changes: 11 additions & 0 deletions Dockerfile.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM golang:1.21.5-alpine as build

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY *.go ./

RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /smtp-proxy-for-notify
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Canadian Digital Service – Service numérique canadien
Copyright (c) 2023 Canadian Digital Service – Service numérique canadien

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.EXPORT_ALL_VARIABLES:
NOTIFY_HOSTNAME=https://api.staging.notification.cdssandbox.xyz
NOTIFY_APIKEY=gcntfy-test-00000000-0000-4000-8000-000000000000-00000000-0000-4000-8000-0000000000000
NOTIFY_TEMPLATE_ID=00000000-0000-4000-8000-000000000000

SMTP_USE_TLS=true
SMTP_TLS_CERT_FILE=./example_certs/server.crt
SMTP_TLS_KEY_FILE=./example_certs/server.key
SMTP_HOSTNAME=localhost
SMTP_PORT=1025
SMTP_USERNAME=username
SMTP_PASSWORD=longpasswordgo

TEST_SENDER=author@localhost
TEST_RECIPIENT[email protected]

.PHONY: dev generate-keys release release-test script-test-python test

dev:
@echo "Starting dev server..."
@go run .

generate-keys:
@cd example_certs && \
rm -f server.key server.crt && \
echo "Generating keys..." && \
openssl genrsa -out server.key 2048 && \
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 -subj /CN=localhost/O=smtp-proxy-for-notify/C=CA

release:
@mkdir -p release/latest
@docker build -t smtp-proxy-for-notify-build -f Dockerfile.build .
@docker create -ti --name smtp-proxy-for-notify-build smtp-proxy-for-notify-build bash
@docker cp smtp-proxy-for-notify-build:/smtp-proxy-for-notify release/latest/smtp-proxy-for-notify
@docker rm -f smtp-proxy-for-notify-build

release-test:
@mkdir -p release/latest
@docker build -t smtp-proxy-for-notify-build -f Dockerfile.build .
@docker create -ti --name smtp-proxy-for-notify-build smtp-proxy-for-notify-build bash
@docker cp smtp-proxy-for-notify-build:/smtp-proxy-for-notify release/latest/smtp-proxy-for-notify-test
@docker rm -f smtp-proxy-for-notify-build

script-test-python:
@python3 ./bin/test_send.py

test:
@go test -cover ./...
74 changes: 67 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,70 @@
# Generic Project Template
# SMTP Proxy for Notify

This repository provides some base files for setting up a repository at
CDS. Plan is to create more project template for specific technologies:
This is a simple SMTP proxy for Notify that bridges the gap between the SMTP protocol and the Notify API. The proxy listens for SMTP connections and converts the SMTP message into a Notify API call. The Notify API then sends the message to the recipient. Attachments are supported.

- project-template-terraform
- project-template-python
- project-template-nodejs
## Usage

Note that default community health files are maintained at https://github.com/cds-snc/.github
### Requirements

- A Notify account
- A Notify API key
- A Notify template ID

Your template must have a `subject` and `body` field. For example, the template should look something like this:

![An image showing a Notify template](https://github.com/cds-snc/smtp-proxy-for-notify/assets/867334/a868d28b-f4fb-4069-95ed-6fb11bbf5aae)

### Environment variables

| Variable | Description | Required | Default |
| --- | --- | --- | --- |
| NOTIFY_APIKEY | Your Notify API key | Yes | |
| NOTIFY_HOSTNAME | The hostname of the Notify API | No | https://api.notification.canada.ca |
| NOTIFY_TEMPLATE_ID | Your Notify template ID | Yes | |
| SMTP_TLS_CERT_FILE | Path to your TLS certificate file | No | |
| SMTP_TLS_KEY_FILE | Path to your TLS key file | No | |
| SMTP_USE_TLS | Whether to use TLS or not | No | false |
| SMTP_HOSTNAME | The hostname to listen on | No | localhost |
| SMTP_PORT | The port to listen on | No | 1025 |
| SMTP_USERNAME | The username to use for authentication | Yes |
| SMTP_PASSWORD | The password to use for authentication | Yes |

### Running

#### Locally

You can run the proxy locally using the following command as long as you have all the environment variables set:

```bash
./release/latest/smtp-proxy-for-notify
```

#### Docker

The proxy can also be run using Docker. You can build the image using the Dockerfile in this repository. However, you should provide your own TLS certificate and key files. You can do this by building the image with the `SMTP_TLS_CERT_FILE` and `SMTP_TLS_KEY_FILE` build arguments. For example:

```bash
docker build --build-arg SMTP_TLS_CERT_FILE="example_certs/server.crt" --build-arg=SMTP_TLS_KEY_FILE="example_certs/server.key" -t smtp .
```

The alternative is to mount the certificate and key files into the container at runtime.

To run the container, you can use the following command assuming you built it with the Dockerfile in this repository:

```bash
docker run \
-e NOTIFY_APIKEY=gcntfy-test-00000000-0000-4000-8000-000000000000-00000000-0000-4000-8000-0000000000000 \
-e NOTIFY_TEMPLATE_ID=00000000-0000-4000-8000-0000000000008 \
-e SMTP_TLS_CERT_FILE=/etc/ssl/certs/server.crt \
-e SMTP_TLS_KEY_FILE=/etc/ssl/private/server.key \
-e SMTP_USE_TLS=true \
-e SMTP_HOSTNAME=0.0.0.0 \
-e SMTP_USERNAME=username \
-e SMTP_PASSWORD=longpasswordgo \
-p 1025:1025 \
smtp
```

# License

MIT License
38 changes: 38 additions & 0 deletions bin/test_send.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Send an email to a local smtp server using the smtplib module
import os
import random
import smtplib
import ssl
import string
import email.utils
from email.mime.text import MIMEText
from email.message import EmailMessage

# Generate random string for the attachment
def randomword(length):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(length))

email = EmailMessage()
email['subject'] = "Simple test message"
email.set_content("This is the body of the message.")

attachment_size_in_kb = 1000

#for i in range(5):
# email.add_attachment(
# randomword(attachment_size_in_kb * 1024).encode('utf-8'),
# filename="attachment-{}.txt".format(i),
# maintype="application",
# subtype="txt"
# )

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
server = smtplib.SMTP_SSL("0.0.0.0", 1025)
server.ehlo() # send the extended hello to our server
server.set_debuglevel(True) # show communication with the server
try:
server.login(os.environ["SMTP_USERNAME"], os.environ["SMTP_PASSWORD"])
server.sendmail(os.environ["TEST_SENDER"], [os.environ["TEST_RECIPIENT"]], email.as_string())
finally:
server.quit()
103 changes: 103 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"errors"
"regexp"

"github.com/spf13/viper"
)

type Config struct {
// Notify settings
Notify struct {
ApiKey string
Hostname string
TemplateId string
}

// SMTP settings
Smtp struct {
// Hostname to listen on
Hostname string
Port int

// Username and password for authentication
Username string
Password string

// Start TLS
UseTLS bool
TlsCertFile string
TlsKeyFile string
}
}

func initConfig() (*Config, error) {
viper.AutomaticEnv()

var configuration Config
var err error

// Set default values
viper.SetDefault("LogLevel", "info")
viper.SetDefault("Notify_ApiKey", "")
viper.SetDefault("Notify_Hostname", "https://api.notification.canada.ca")
viper.SetDefault("Notify_Template_Id", "")
viper.SetDefault("Smtp_Hostname", "localhost")
viper.SetDefault("Smtp_Port", 1025)
viper.SetDefault("Smtp_Username", "")
viper.SetDefault("Smtp_Password", "")
viper.SetDefault("Smtp_Use_tls", false)
viper.SetDefault("Smtp_tls_cert_file", "")
viper.SetDefault("Smtp_tls_key_file", "")

configuration.Notify.ApiKey = viper.GetString("Notify_ApiKey")
configuration.Notify.Hostname = viper.GetString("Notify_Hostname")
configuration.Notify.TemplateId = viper.GetString("Notify_Template_Id")
configuration.Smtp.Hostname = viper.GetString("Smtp_Hostname")
configuration.Smtp.Port = viper.GetInt("Smtp_Port")
configuration.Smtp.Username = viper.GetString("Smtp_Username")
configuration.Smtp.Password = viper.GetString("Smtp_Password")
configuration.Smtp.UseTLS = viper.GetBool("Smtp_Use_TLS")
configuration.Smtp.TlsCertFile = viper.GetString("Smtp_tls_cert_file")
configuration.Smtp.TlsKeyFile = viper.GetString("Smtp_tls_key_file")

// Validate username is no less than three characters
if len(configuration.Smtp.Username) < 3 {
err := errors.New("username must be at least three characters")
return &configuration, err
}

// Validate password is no less than fourteen characters
if len(configuration.Smtp.Password) < 14 {
err := errors.New("password must be at least fourteen characters")
return &configuration, err
}

// Validate API key starts with gcntfy and is not less than 81 characters
if len(configuration.Notify.ApiKey) < 81 || configuration.Notify.ApiKey[:6] != "gcntfy" {
err := errors.New("API key must start with gcntfy and be at least 81 characters")
return &configuration, err
}

// Validate Notify Template ID matches a UUIDv4 using regex
r, _ := regexp.Compile(`^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-4[0-9a-fA-F]{3}\-[89abAB][0-9a-fA-F]{3}\-[0-9a-fA-F]{12}$`)
if !r.MatchString(configuration.Notify.TemplateId) {
err := errors.New("notify Template ID must be a UUIDv4")
return &configuration, err
}

// If TLS is enabled, validate the certificate and key file paths
if configuration.Smtp.UseTLS {
if configuration.Smtp.TlsCertFile == "" {
err := errors.New("TLS certificate file path must be specified")
return &configuration, err
}
if configuration.Smtp.TlsKeyFile == "" {
err := errors.New("TLS key file path must be specified")
return &configuration, err
}
}

return &configuration, err
}
Loading

0 comments on commit 1529e3c

Please sign in to comment.