Skip to content

Commit

Permalink
Add option to disallow unencrypted secrets
Browse files Browse the repository at this point in the history
Signed-off-by: Knut Ahlers <knut@ahlers.me>
  • Loading branch information
Luzifer committed Nov 8, 2023
1 parent e5bcfaa commit e8003bb
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 14 deletions.
58 changes: 44 additions & 14 deletions api.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package main

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
Expand All @@ -16,11 +20,12 @@ import (
)

const (
errorReasonInvalidJSON = "invalid_json"
errorReasonSecretMissing = "secret_missing"
errorReasonSecretSize = "secret_size"
errorReasonStorageError = "storage_error"
errorReasonSecretNotFound = "secret_not_found"
errorReasonInvalidJSON = "invalid_json"
errorReasonSecretMissing = "secret_missing"
errorReasonSecretSize = "secret_size"
errorReasonStorageError = "storage_error"
errorReasonSecretNotFound = "secret_not_found"
errorReasonSecretUnencrypted = "unencrypted_secret" //#nosec:G101 // That's no secret.
)

type apiServer struct {
Expand All @@ -40,6 +45,8 @@ type apiRequest struct {
Secret string `json:"secret"`
}

var opensslEncHeader = []byte("Salted__")

func newAPI(s storage.Storage, c *metrics.Collector) *apiServer {
return &apiServer{
collector: c,
Expand Down Expand Up @@ -93,15 +100,9 @@ func (a apiServer) handleCreate(res http.ResponseWriter, r *http.Request) {
secret = r.FormValue("secret")
}

if secret == "" {
a.collector.CountSecretCreateError(errorReasonSecretMissing)
a.errorResponse(res, http.StatusBadRequest, errors.New("secret missing"), "")
return
}

if cust.MaxSecretSize > 0 && len(secret) > int(cust.MaxSecretSize) {
a.collector.CountSecretCreateError(errorReasonSecretSize)
a.errorResponse(res, http.StatusBadRequest, errors.New("secret size exceeds maximum"), "")
if reason, err := a.sanityCheckSecret(secret); err != nil {
a.collector.CountSecretCreateError(reason)
a.errorResponse(res, http.StatusBadRequest, err, "sanity-checking secret input")
return
}

Expand Down Expand Up @@ -182,3 +183,32 @@ func (apiServer) jsonResponse(res http.ResponseWriter, status int, response any)
http.Error(res, `{"error":"could not encode response"}`, http.StatusInternalServerError)
}
}

func (a apiServer) sanityCheckSecret(secret string) (reason string, err error) {
if secret == "" {
return errorReasonSecretMissing, errors.New("secret missing")
}

if cust.MaxSecretSize > 0 && len(secret) > int(cust.MaxSecretSize) {
return errorReasonSecretSize, errors.New("secret size exceeds maximum")
}

if err = a.secretContainsCryptoHeader(secret); err != nil && cust.RejectUnencryptedSecrets {
return errorReasonSecretUnencrypted, fmt.Errorf("checking secret encryption: %w", err)
}

return "", nil
}

func (apiServer) secretContainsCryptoHeader(secret string) (err error) {
header := make([]byte, len(opensslEncHeader))
if _, err = io.ReadFull(base64.NewDecoder(base64.StdEncoding, strings.NewReader(secret)), header); err != nil {
return fmt.Errorf("reading header: %w", err)
}

if !bytes.Equal(header, opensslEncHeader) {
return fmt.Errorf("header does not match")
}

return nil
}
2 changes: 2 additions & 0 deletions pkg/customization/customize.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type (
MetricsAllowedSubnets []string `json:"-" yaml:"metricsAllowedSubnets"`
OverlayFSPath string `json:"-" yaml:"overlayFSPath"`
UseFormalLanguage bool `json:"-" yaml:"useFormalLanguage"`

RejectUnencryptedSecrets bool `json:"-" yaml:"rejectUnencryptedSecrets"`
}
)

Expand Down

0 comments on commit e8003bb

Please sign in to comment.