package handlers import ( "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "strings" "testing" "github.com/openfaas/faas-provider/types" "github.com/openfaas/faasd/pkg" provider "github.com/openfaas/faasd/pkg/provider" ) func Test_parseSecret(t *testing.T) { cases := []struct { name string payload string expError string expSecret types.Secret }{ { name: "no error when name is valid without extention and with no traversal", payload: `{"name": "authorized_keys", "value": "foo"}`, expSecret: types.Secret{Name: "authorized_keys", Value: "foo"}, }, { name: "no error when name is valid and parses RawValue correctly", payload: `{"name": "authorized_keys", "rawValue": "YmFy"}`, expSecret: types.Secret{Name: "authorized_keys", RawValue: []byte("bar")}, }, { name: "no error when name is valid with dot and with no traversal", payload: `{"name": "authorized.keys", "value": "foo"}`, expSecret: types.Secret{Name: "authorized.keys", Value: "foo"}, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { reader := strings.NewReader(tc.payload) r := httptest.NewRequest(http.MethodPost, "/", reader) secret, err := parseSecret(r) if err != nil && tc.expError == "" { t.Fatalf("unexpected error: %s", err) return } if tc.expError != "" { if err == nil { t.Fatalf("expected error: %s, got nil", tc.expError) } if err.Error() != tc.expError { t.Fatalf("expected error: %s, got: %s", tc.expError, err) } return } if !reflect.DeepEqual(secret, tc.expSecret) { t.Fatalf("expected secret: %+v, got: %+v", tc.expSecret, secret) } }) } } func TestSecretCreation(t *testing.T) { mountPath, err := os.MkdirTemp("", "test_secret_creation") if err != nil { t.Fatalf("unexpected error while creating temp directory: %s", err) } defer os.RemoveAll(mountPath) handler := MakeSecretHandler(nil, mountPath) cases := []struct { name string verb string payload string status int secretPath string secret string err string }{ { name: "returns error when the name contains a traversal", verb: http.MethodPost, payload: `{"name": "/root/.ssh/authorized_keys", "value": "foo"}`, status: http.StatusBadRequest, err: "directory traversal found in name\n", }, { name: "returns error when the name contains a traversal", verb: http.MethodPost, payload: `{"name": "..", "value": "foo"}`, status: http.StatusBadRequest, err: "directory traversal found in name\n", }, { name: "empty request returns a validation error", verb: http.MethodPost, payload: `{}`, status: http.StatusBadRequest, err: "non-empty name is required\n", }, { name: "can create secret from string", verb: http.MethodPost, payload: `{"name": "foo", "value": "bar"}`, status: http.StatusOK, secretPath: "/openfaas-fn/foo", secret: "bar", }, { name: "can create secret from raw value", verb: http.MethodPost, payload: `{"name": "foo", "rawValue": "YmFy"}`, status: http.StatusOK, secretPath: "/openfaas-fn/foo", secret: "bar", }, { name: "can create secret in non-default namespace from raw value", verb: http.MethodPost, payload: `{"name": "pity", "rawValue": "dGhlIGZvbw==", "namespace": "a-team"}`, status: http.StatusOK, secretPath: "/a-team/pity", secret: "the foo", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(tc.verb, "http://example.com/foo", strings.NewReader(tc.payload)) w := httptest.NewRecorder() handler(w, req) resp := w.Result() if resp.StatusCode != tc.status { t.Logf("response body: %s", w.Body.String()) t.Fatalf("expected status: %d, got: %d", tc.status, resp.StatusCode) } if resp.StatusCode != http.StatusOK && w.Body.String() != tc.err { t.Fatalf("expected error message: %q, got %q", tc.err, w.Body.String()) } if tc.secretPath != "" { data, err := os.ReadFile(filepath.Join(mountPath, tc.secretPath)) if err != nil { t.Fatalf("can not read the secret from disk: %s", err) } if string(data) != tc.secret { t.Fatalf("expected secret value: %s, got %s", tc.secret, string(data)) } } }) } } func TestListSecrets(t *testing.T) { mountPath, err := os.MkdirTemp("", "test_secret_creation") if err != nil { t.Fatalf("unexpected error while creating temp directory: %s", err) } defer os.RemoveAll(mountPath) cases := []struct { name string verb string namespace string labels map[string]string status int secretPath string secret string err string expected []types.Secret }{ { name: "Get empty secret list for default namespace having no secret", verb: http.MethodGet, status: http.StatusOK, secretPath: "/test-fn/foo", secret: "bar", expected: make([]types.Secret, 0), }, { name: "Get empty secret list for non-default namespace having no secret", verb: http.MethodGet, status: http.StatusOK, secretPath: "/test-fn/foo", secret: "bar", expected: make([]types.Secret, 0), namespace: "other-ns", labels: map[string]string{ pkg.NamespaceLabel: "true", }, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { labelStore := provider.NewFakeLabeller(tc.labels) handler := MakeSecretHandler(labelStore, mountPath) path := "http://example.com/foo" if len(tc.namespace) > 0 { path = path + fmt.Sprintf("?namespace=%s", tc.namespace) } req := httptest.NewRequest(tc.verb, path, nil) w := httptest.NewRecorder() handler(w, req) resp := w.Result() if resp.StatusCode != tc.status { t.Fatalf("want status: %d, but got: %d", tc.status, resp.StatusCode) } if resp.StatusCode != http.StatusOK && w.Body.String() != tc.err { t.Fatalf("want error message: %q, but got %q", tc.err, w.Body.String()) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("can't read response of list %v", err) } var res []types.Secret err = json.Unmarshal(body, &res) if err != nil { t.Fatalf("unable to unmarshal %q, error: %v", string(body), err) } if !reflect.DeepEqual(res, tc.expected) { t.Fatalf("want response: %v, but got: %v", tc.expected, res) } }) } }