mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-21 22:33:27 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
35e017b526 | |||
e54da61283 | |||
84353d0cae | |||
e33a60862d | |||
7b67ff22e6 | |||
19abc9f7b9 | |||
480f566819 |
@ -10,6 +10,7 @@ addons:
|
|||||||
- runc
|
- runc
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- make test
|
||||||
- make dist
|
- make dist
|
||||||
- make prepare-test
|
- make prepare-test
|
||||||
- make test-e2e
|
- make test-e2e
|
||||||
|
4
Makefile
4
Makefile
@ -11,6 +11,10 @@ all: local
|
|||||||
local:
|
local:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -o bin/faasd
|
CGO_ENABLED=0 GOOS=linux go build -o bin/faasd
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
CGO_ENABLED=0 GOOS=linux go test -ldflags $(LDFLAGS) ./...
|
||||||
|
|
||||||
.PHONY: dist
|
.PHONY: dist
|
||||||
dist:
|
dist:
|
||||||
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
[](https://travis-ci.com/openfaas/faasd)
|
[](https://travis-ci.com/openfaas/faasd)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://www.openfaas.com)
|
[](https://www.openfaas.com)
|
||||||
|

|
||||||
|
|
||||||
faasd is the same OpenFaaS experience and ecosystem, but without Kubernetes. Functions and microservices can be deployed anywhere with reduced overheads whilst retaining the portability of containers and cloud-native tooling.
|
faasd is the same OpenFaaS experience and ecosystem, but without Kubernetes. Functions and microservices can be deployed anywhere with reduced overheads whilst retaining the portability of containers and cloud-native tooling.
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ runcmd:
|
|||||||
- curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.8.5/cni-plugins-linux-amd64-v0.8.5.tgz | tar -xz -C /opt/cni/bin
|
- curl -sSL https://github.com/containernetworking/plugins/releases/download/v0.8.5/cni-plugins-linux-amd64-v0.8.5.tgz | tar -xz -C /opt/cni/bin
|
||||||
- mkdir -p /go/src/github.com/openfaas/
|
- mkdir -p /go/src/github.com/openfaas/
|
||||||
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
- cd /go/src/github.com/openfaas/ && git clone https://github.com/openfaas/faasd
|
||||||
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.7.7/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
- curl -fSLs "https://github.com/openfaas/faasd/releases/download/0.8.1/faasd" --output "/usr/local/bin/faasd" && chmod a+x "/usr/local/bin/faasd"
|
||||||
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
- cd /go/src/github.com/openfaas/faasd/ && /usr/local/bin/faasd install
|
||||||
- systemctl status -l containerd --no-pager
|
- systemctl status -l containerd --no-pager
|
||||||
- journalctl -u faasd-provider --no-pager
|
- journalctl -u faasd-provider --no-pager
|
||||||
|
@ -49,7 +49,7 @@ func (r *requester) Query(ctx context.Context, req logs.Request) (<-chan logs.Me
|
|||||||
// call start and get the stdout prior to streaming so that we can return a meaningful
|
// call start and get the stdout prior to streaming so that we can return a meaningful
|
||||||
// error for as long as possible. If the cmd starts correctly, we are highly likely to
|
// error for as long as possible. If the cmd starts correctly, we are highly likely to
|
||||||
// succeed anyway
|
// succeed anyway
|
||||||
msgs := make(chan logs.Message, 100)
|
msgs := make(chan logs.Message)
|
||||||
go streamLogs(ctx, cmd, stdout, msgs)
|
go streamLogs(ctx, cmd, stdout, msgs)
|
||||||
go logErrOut(stderr)
|
go logErrOut(stderr)
|
||||||
|
|
||||||
@ -62,7 +62,6 @@ func (r *requester) Query(ctx context.Context, req logs.Request) (<-chan logs.Me
|
|||||||
// --output=json \
|
// --output=json \
|
||||||
// --since=<timestamp> \
|
// --since=<timestamp> \
|
||||||
// <--follow> \
|
// <--follow> \
|
||||||
// --output-fields=SYSLOG_IDENTIFIER,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP
|
|
||||||
func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
||||||
// // set the cursor position based on req, default to 5m
|
// // set the cursor position based on req, default to 5m
|
||||||
since := time.Now().Add(-5 * time.Minute)
|
since := time.Now().Add(-5 * time.Minute)
|
||||||
@ -102,10 +101,6 @@ func buildCmd(ctx context.Context, req logs.Request) *exec.Cmd {
|
|||||||
// the loop is based on the Decoder example in the docs
|
// the loop is based on the Decoder example in the docs
|
||||||
// https://golang.org/pkg/encoding/json/#Decoder.Decode
|
// https://golang.org/pkg/encoding/json/#Decoder.Decode
|
||||||
func streamLogs(ctx context.Context, cmd *exec.Cmd, out io.ReadCloser, msgs chan logs.Message) {
|
func streamLogs(ctx context.Context, cmd *exec.Cmd, out io.ReadCloser, msgs chan logs.Message) {
|
||||||
// without this sleep the channel seems to get stuck. This results in either no log messages
|
|
||||||
// being read by the Handler _or_ the messages are read but only flushed when the request
|
|
||||||
// timesout
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
log.Println("starting journal stream using ", cmd.String())
|
log.Println("starting journal stream using ", cmd.String())
|
||||||
|
|
||||||
// will ensure `out` is closed and all related resources cleaned up
|
// will ensure `out` is closed and all related resources cleaned up
|
||||||
|
@ -29,19 +29,19 @@ func Test_parseEntry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if entry.Name != expectedEntry.Name {
|
if entry.Name != expectedEntry.Name {
|
||||||
t.Fatalf("expected Name %s, got %s", expectedEntry.Name, entry.Name)
|
t.Fatalf("want Name: %q, got %q", expectedEntry.Name, entry.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Namespace != expectedEntry.Namespace {
|
if entry.Namespace != expectedEntry.Namespace {
|
||||||
t.Fatalf("expected Namespace %s, got %s", expectedEntry.Namespace, entry.Namespace)
|
t.Fatalf("want Namespace: %q, got %q", expectedEntry.Namespace, entry.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Timestamp != expectedEntry.Timestamp {
|
if entry.Timestamp != expectedEntry.Timestamp {
|
||||||
t.Fatalf("expected Timestamp %s, got %s", expectedEntry.Timestamp, entry.Timestamp)
|
t.Fatalf("want Timestamp: %q, got %q", expectedEntry.Timestamp, entry.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Text != expectedEntry.Text {
|
if entry.Text != expectedEntry.Text {
|
||||||
t.Fatalf("expected Text %s, got %s", expectedEntry.Text, entry.Text)
|
t.Fatalf("want Text: %q, got %q", expectedEntry.Text, entry.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,17 +57,17 @@ func Test_buildCmd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectedArgs := fmt.Sprintf(
|
expectedArgs := fmt.Sprintf(
|
||||||
"--utc --no-pager --output=json --output-fields=SYSLOG_IDENTIFIER,MESSAGE,_PID,_SOURCE_REALTIME_TIMESTAMP --identifier=spacetwo:loggyfunc --since=%s --follow --lines=5",
|
"--utc --no-pager --output=json --identifier=spacetwo:loggyfunc --since=%s --follow --lines=5",
|
||||||
now.UTC().Format("2006-01-02 15:04:05"),
|
now.UTC().Format("2006-01-02 15:04:05"),
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd := buildCmd(ctx, req).String()
|
cmd := buildCmd(ctx, req).String()
|
||||||
|
wantCmd := "journalctl"
|
||||||
if !strings.Contains(cmd, "journalctl") {
|
if !strings.Contains(cmd, wantCmd) {
|
||||||
t.Fatalf("expected journalctl cmd, got cmd %s", cmd)
|
t.Fatalf("cmd want: %q, got: %q", wantCmd, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(cmd, expectedArgs) {
|
if !strings.HasSuffix(cmd, expectedArgs) {
|
||||||
t.Fatalf("expected arg %s,\ngot cmd %s", expectedArgs, cmd)
|
t.Fatalf("arg want: %q\ngot: %q", expectedArgs, cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,13 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/openfaas/faas-provider/types"
|
"github.com/openfaas/faas-provider/types"
|
||||||
@ -76,17 +78,6 @@ func createSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
return secret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request, mountPath string) {
|
||||||
secret, err := parseSecret(r)
|
secret, err := parseSecret(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -103,3 +94,29 @@ func deleteSecret(c *containerd.Client, w http.ResponseWriter, r *http.Request,
|
|||||||
return
|
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, "..")
|
||||||
|
}
|
||||||
|
63
pkg/provider/handlers/secret_test.go
Normal file
63
pkg/provider/handlers/secret_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/openfaas/faas-provider/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseSecretValidName(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: "authorized_keys"}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("secret name is valid with no traversal characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSecretValidNameWithDot(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: "authorized.keys"}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("secret name is valid with no traversal characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSecretWithTraversalWithSlash(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: "/root/.ssh/authorized_keys"}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("secret name should fail due to path traversal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSecretWithTraversalWithDoubleDot(t *testing.T) {
|
||||||
|
|
||||||
|
s := types.Secret{Name: ".."}
|
||||||
|
body, _ := json.Marshal(s)
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", reader)
|
||||||
|
_, err := parseSecret(r)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("secret name should fail due to path traversal")
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user