Skip to content

Commit

Permalink
Merge pull request #157 from Plaenkler/add-secret-encryption
Browse files Browse the repository at this point in the history
[ADD] Encryption of sensitive data
  • Loading branch information
Plaenkler authored Sep 3, 2023
2 parents aec1ba7 + 61b1af2 commit eef29d6
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 42 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ The user-friendly interface allows for straightforward secure setup and manageme

- [x] Simple & User friendly UI
- [x] Secure authentication with TOTP
- [x] Available as Docker Container
- [x] Encryption of sensitive data
- [x] Scheduled update service
- [x] Supports multiple IP resolvers
- [ ] Deploy as Windows Service
- [x] Available as Docker Container

## 🏷️ Supported providers

Expand Down Expand Up @@ -126,10 +127,16 @@ Changes to the interval take effect immediately. The program must be restarted f
A config.yaml file is provided to store all settings. In the absence of this file, the program generates one. Users have the option to directly modify settings within this file. It is important to note that changes made here will only take effect upon restarting the program. Default settings within the file are as follows:

```yaml
# How often the IP address is checked in seconds
Interval: 600
# Enable TOTP authentication
TOTP: false
# Port for the web interface
Port: 80
# Custom IP resolver returns IPv4 address in plain text
Resolver: ""
# Random key for symmetrical encryption
Cryptor: 29atdqljyqUVXNAuXltyng==
```
**3. Environment Variables**
Expand All @@ -141,4 +148,5 @@ DDNS_INTERVAL=600
DDNS_TOTP=false
DDNS_PORT=80
DDNS_RESOLVER=ipv4.example.com
DDNS_CRYPTOR=29atdqljyqUVXNAuXltyng==
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/plaenkler/ddns-updater

go 1.21
go 1.20

require (
github.com/aliyun/alibaba-cloud-sdk-go v1.62.514
Expand Down
61 changes: 61 additions & 0 deletions pkg/cipher/cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cipher

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)

func Encrypt(key string, plaintext string) (string, error) {
encrypter, err := aes.NewCipher([]byte(key))
if err != nil {
return "", fmt.Errorf("[cipher-Encrypt-1] encryption failed: %s", err)
}
gcm, err := cipher.NewGCM(encrypter)
if err != nil {
return "", fmt.Errorf("[cipher-Encrypt-2] encryption failed: %s", err)
}
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return "", fmt.Errorf("[cipher-Encrypt-3] encryption failed: %s", err)
}
return base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, []byte(plaintext), nil)), nil
}

func Decrypt(key string, ciphertext string) ([]byte, error) {
c, err := aes.NewCipher([]byte(key))
if err != nil {
return nil, fmt.Errorf("[cipher-Decrypt-1] decryption failed: %s", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, fmt.Errorf("[cipher-Decrypt-2] decryption failed: %s", err)
}
cipherBytes, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return nil, fmt.Errorf("[cipher-Decrypt-3] decryption failed: %s", err)
}
nonceSize := gcm.NonceSize()
if len(cipherBytes) < nonceSize {
return nil, fmt.Errorf("[cipher-Decrypt-4] decryption failed: %s", err)
}
nonce, cipherBytes := cipherBytes[:nonceSize], cipherBytes[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, cipherBytes, nil)
if err != nil {
return nil, fmt.Errorf("[cipher-Decrypt-5] decryption failed: %s", err)
}
return plaintext, nil
}

func GenerateRandomKey(length int) (string, error) {
randomBytes := make([]byte, length)
_, err := rand.Read(randomBytes)
if err != nil {
return "", fmt.Errorf("[cipher-GenerateRandomKey-1] generating random key failed: %s", err)
}
return base64.URLEncoding.EncodeToString(randomBytes), nil
}
13 changes: 12 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"
"sync"

"github.com/plaenkler/ddns-updater/pkg/cipher"
log "github.com/plaenkler/ddns-updater/pkg/logging"
"gopkg.in/yaml.v3"
)
Expand All @@ -17,6 +18,7 @@ type Config struct {
UseTOTP bool `yaml:"TOTP"`
Port uint64 `yaml:"Port"`
Resolver string `yaml:"Resolver"`
Cryptor string `yaml:"Cryptor"`
}

const (
Expand Down Expand Up @@ -64,13 +66,18 @@ func load() error {
}

func create() error {
cryptor, err := cipher.GenerateRandomKey(16)
if err != nil {
return err
}
config := Config{
Interval: 600,
UseTOTP: false,
Port: 80,
Resolver: "",
Cryptor: cryptor,
}
err := os.MkdirAll(filepath.Dir(pathToConfig), dirPerm)
err = os.MkdirAll(filepath.Dir(pathToConfig), dirPerm)
if err != nil {
return err
}
Expand Down Expand Up @@ -115,6 +122,10 @@ func loadFromEnv() error {
if resolver != "" {
config.Resolver = resolver
}
cryptor, ok := os.LookupEnv("DDNS_CRYPTOR")
if ok && cryptor != "" {
config.Cryptor = cryptor
}
return nil
}

Expand Down
16 changes: 11 additions & 5 deletions pkg/ddns/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"sync"
"time"

"github.com/plaenkler/ddns-updater/pkg/cipher"
"github.com/plaenkler/ddns-updater/pkg/config"
"github.com/plaenkler/ddns-updater/pkg/database"
"github.com/plaenkler/ddns-updater/pkg/database/model"
Expand Down Expand Up @@ -82,22 +83,27 @@ func updateDDNSEntries(db *gorm.DB, jobs []model.SyncJob, a model.IPAddress) {
log.Errorf("[ddns-updateDDNSEntries-1] no updater found for job %v", job.ID)
continue
}
params, err := cipher.Decrypt(config.Get().Cryptor, job.Params)
if err != nil {
log.Errorf("[ddns-updateDDNSEntries-2] failed to decrypt job params for job %v: %s", job.ID, err)
continue
}
request := updater.Request
err := json.Unmarshal([]byte(job.Params), request)
err = json.Unmarshal(params, request)
if err != nil {
log.Errorf("[ddns-updateDDNSEntries-2] failed to unmarshal job params for job %v: %s", job.ID, err)
log.Errorf("[ddns-updateDDNSEntries-3] failed to unmarshal job params for job %v: %s", job.ID, err)
continue
}
err = updater.Updater(request, a.Address)
if err != nil {
log.Errorf("[ddns-updateDDNSEntries-3] failed to update DDNS entry for job %v: %s", job.ID, err)
log.Errorf("[ddns-updateDDNSEntries-4] failed to update DDNS entry for job %v: %s", job.ID, err)
continue
}
err = db.Model(&job).Update("ip_address_id", a.ID).Error
if err != nil {
log.Errorf("[ddns-updateDDNSEntries-4] failed to update IP address for job %v: %s", job.ID, err)
log.Errorf("[ddns-updateDDNSEntries-5] failed to update IP address for job %v: %s", job.ID, err)
}
log.Infof("[ddns-updateDDNSEntries-5] updated DDNS entry for ID: %v Provider: %s Params: %+v", job.ID, job.Provider, job.Params)
log.Infof("[ddns-updateDDNSEntries-6] updated DDNS entry for ID: %v Provider: %s Params: %+v", job.ID, job.Provider, job.Params)
}
}

Expand Down
28 changes: 21 additions & 7 deletions pkg/server/routes/api/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"net/http"
"strconv"

"github.com/plaenkler/ddns-updater/pkg/cipher"
"github.com/plaenkler/ddns-updater/pkg/config"
"github.com/plaenkler/ddns-updater/pkg/database"
"github.com/plaenkler/ddns-updater/pkg/database/model"
"github.com/plaenkler/ddns-updater/pkg/ddns"
Expand Down Expand Up @@ -34,19 +36,25 @@ func CreateJob(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
encParams, err := cipher.Encrypt(config.Get().Cryptor, params)
if err != nil {
log.Errorf("[api-CreateJob-4] could not encrypt params: %s", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
job := model.SyncJob{
Provider: provider,
Params: params,
Params: encParams,
}
db := database.GetDatabase()
if db == nil {
log.Errorf("[api-CreateJob-4] could not get database connection")
log.Errorf("[api-CreateJob-5] could not get database connection")
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
err = db.Create(&job).Error
if err != nil {
log.Errorf("[api-CreateJob-5] could not create job: %s", err)
log.Errorf("[api-CreateJob-6] could not create job: %s", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
Expand Down Expand Up @@ -82,27 +90,33 @@ func UpdateJob(w http.ResponseWriter, r *http.Request) {
log.Errorf("[api-UpdateJob-4] could not unmarshal params: %s", err)
return
}
encParams, err := cipher.Encrypt(config.Get().Cryptor, params)
if err != nil {
http.Error(w, "Could not encrypt params", http.StatusInternalServerError)
log.Errorf("[api-UpdateJob-5] could not encrypt params: %s", err)
return
}
job := model.SyncJob{
Model: gorm.Model{
ID: uint(id),
},
Provider: provider,
Params: params,
Params: encParams,
}
db := database.GetDatabase()
if db == nil {
http.Error(w, "Could not get database connection", http.StatusInternalServerError)
log.Errorf("[api-UpdateJob-5] could not get database connection")
log.Errorf("[api-UpdateJob-6] could not get database connection")
return
}
err = db.Save(&job).Error
if err != nil {
http.Error(w, "Could not update job", http.StatusInternalServerError)
log.Errorf("[api-UpdateJob-6] could not update job: %s", err)
log.Errorf("[api-UpdateJob-7] could not update job: %s", err)
return
}
http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
log.Infof("[api-UpdateJob-7] updated job with ID %d", job.ID)
log.Infof("[api-UpdateJob-8] updated job with ID %d", job.ID)
}

func DeleteJob(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading

0 comments on commit eef29d6

Please sign in to comment.