mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-08 16:06:47 +00:00
This patch fixes a vulnerability in the secrets API, however it is important to stress that the user must be authenticated as the admin user on the REST API before they can attempt this. Reported by Appsecco via email. @lucasroesler, Appsecco and myself believe this to be of low severity. The fix prevents directory traversal characters from being used in secret names. If a secret name such as: ../../root/.ssh/authorized_keys were to be used, an attacker could remove the value and write their own. Tested with unit tests and tests are now made to run via the CI and a new Makefile target. Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
123 lines
2.8 KiB
Go
123 lines
2.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/openfaas/faas-provider/types"
|
|
)
|
|
|
|
const secretFilePermission = 0644
|
|
|
|
func MakeSecretHandler(c *containerd.Client, mountPath string) func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := os.MkdirAll(mountPath, secretFilePermission)
|
|
if err != nil {
|
|
log.Printf("Creating path: %s, error: %s\n", mountPath, err)
|
|
}
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Body != nil {
|
|
defer r.Body.Close()
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
listSecrets(c, w, r, mountPath)
|
|
case http.MethodPost:
|
|
createSecret(c, w, r, mountPath)
|
|
case http.MethodPut:
|
|
createSecret(c, w, r, mountPath)
|
|
case http.MethodDelete:
|
|
deleteSecret(c, w, r, mountPath)
|
|
default:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func listSecrets(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
|
files, err := ioutil.ReadDir(mountPath)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
secrets := []types.Secret{}
|
|
for _, f := range files {
|
|
secrets = append(secrets, types.Secret{Name: f.Name()})
|
|
}
|
|
|
|
bytesOut, _ := json.Marshal(secrets)
|
|
w.Write(bytesOut)
|
|
}
|
|
|
|
func createSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
|
secret, err := parseSecret(r)
|
|
if err != nil {
|
|
log.Printf("[secret] error %s", err.Error())
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
err = ioutil.WriteFile(path.Join(mountPath, secret.Name), []byte(secret.Value), secretFilePermission)
|
|
|
|
if err != nil {
|
|
log.Printf("[secret] error %s", err.Error())
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
|
secret, err := parseSecret(r)
|
|
if err != nil {
|
|
log.Printf("[secret] error %s", err.Error())
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
err = os.Remove(path.Join(mountPath, secret.Name))
|
|
|
|
if err != nil {
|
|
log.Printf("[secret] error %s", err.Error())
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func parseSecret(r *http.Request) (types.Secret, error) {
|
|
secret := types.Secret{}
|
|
bytesOut, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
return secret, err
|
|
}
|
|
|
|
err = json.Unmarshal(bytesOut, &secret)
|
|
if err != nil {
|
|
return secret, err
|
|
}
|
|
|
|
if isTraversal(secret.Name) {
|
|
return secret, fmt.Errorf(traverseErrorSt)
|
|
}
|
|
|
|
return secret, err
|
|
}
|
|
|
|
const traverseErrorSt = "directory traversal found in name"
|
|
|
|
func isTraversal(name string) bool {
|
|
return strings.Contains(name, fmt.Sprintf("%s", string(os.PathSeparator))) ||
|
|
strings.Contains(name, "..")
|
|
}
|