Files
kopia/internal/server/api_sources_test.go
Jarek Kowalski 3d58566644 fix(security): prevent cross-site request forgery in the UI website (#1653)
* fix(security): prevent cross-site request forgery in the UI website

This fixes a [cross-site request forgery (CSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
vulnerability in self-hosted UI for Kopia server.

The vulnerability allows potential attacker to make unauthorized API
calls against a running Kopia server. It requires an attacker to trick
the user into visiting a malicious website while also logged into a
Kopia website.

The vulnerability only affected self-hosted Kopia servers with UI. The
following configurations were not vulnerable:

* Kopia Repository Server without UI
* KopiaUI (desktop app)
* command-line usage of `kopia`

All users are strongly recommended to upgrade at the earliest
convenience.

* pr feedback
2022-01-13 11:31:51 -08:00

153 lines
4.8 KiB
Go

package server_test
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/kopia/kopia/internal/apiclient"
"github.com/kopia/kopia/internal/clock"
"github.com/kopia/kopia/internal/repotesting"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/internal/testutil"
"github.com/kopia/kopia/internal/uitask"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
)
func TestSnapshotCounters(t *testing.T) {
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
srvInfo := startServer(t, env, false)
cli, err := apiclient.NewKopiaAPIClient(apiclient.Options{
BaseURL: srvInfo.BaseURL,
TrustedServerCertificateFingerprint: srvInfo.TrustedServerCertificateFingerprint,
Username: testUIUsername,
Password: testUIPassword,
})
require.NoError(t, err)
require.NoError(t, cli.FetchCSRFTokenForTesting(ctx))
dir := testutil.TempDirectory(t)
si := localSource(env, dir)
mustCreateSource(t, cli, dir, &policy.Policy{})
require.Len(t, mustListSources(t, cli, &snapshot.SourceInfo{}), 1)
mustSetPolicy(t, cli, si, &policy.Policy{
FilesPolicy: policy.FilesPolicy{
IgnoreRules: []string{"*.i"},
},
})
require.NoError(t, os.WriteFile(filepath.Join(dir, "file-a"), []byte{1, 2}, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "file-b"), []byte{1, 2, 3}, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "file-c"), []byte{1, 2, 3, 4}, 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "file2.i"), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0o644))
require.NoError(t, os.MkdirAll(filepath.Join(dir, "dir.i"), 0o755))
eti, err := serverapi.Estimate(ctx, cli, &serverapi.EstimateRequest{
Root: dir,
})
require.NoError(t, err)
et := waitForTask(t, cli, eti.TaskID, 15*time.Second)
require.Equal(t, et.Counters["Bytes"], uitask.BytesCounter(9))
require.Equal(t, et.Counters["Directories"], uitask.SimpleCounter(1))
require.Equal(t, et.Counters["Files"], uitask.SimpleCounter(3))
require.Equal(t, et.Counters["Excluded Directories"], uitask.SimpleCounter(1))
require.Equal(t, et.Counters["Excluded Files"], uitask.SimpleCounter(1))
uresp, err := serverapi.UploadSnapshots(ctx, cli, &si)
require.True(t, uresp.Sources[si.String()].Success)
require.NoError(t, err)
// wait until new task for the upload is created
deadline := clock.Now().Add(10 * time.Second)
for mustGetLatestTask(t, cli).TaskID == et.TaskID && clock.Now().Before(deadline) {
time.Sleep(100 * time.Microsecond)
}
ut := waitForTask(t, cli, mustGetLatestTask(t, cli).TaskID, 15*time.Second)
t.Logf("got latest task: %v", ut)
allTasks := mustListTasks(t, cli)
for tid, tsk := range allTasks {
t.Logf("allTasks[%v] = %v", tid, tsk)
}
require.Equal(t, ut.Counters["Hashed Files"], uitask.SimpleCounter(3))
require.Equal(t, ut.Counters["Hashed Bytes"], uitask.BytesCounter(9))
require.Equal(t, ut.Counters["Excluded Directories"], uitask.SimpleCounter(1))
require.Equal(t, ut.Counters["Excluded Files"], uitask.SimpleCounter(1))
require.Equal(t, ut.Counters["Processed Files"], uitask.SimpleCounter(3))
}
func TestSourceRefreshesAfterPolicy(t *testing.T) {
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
srvInfo := startServer(t, env, false)
_ = ctx
cli, err := apiclient.NewKopiaAPIClient(apiclient.Options{
BaseURL: srvInfo.BaseURL,
TrustedServerCertificateFingerprint: srvInfo.TrustedServerCertificateFingerprint,
Username: testUIUsername,
Password: testUIPassword,
})
require.NoError(t, err)
require.NoError(t, cli.FetchCSRFTokenForTesting(ctx))
dir := testutil.TempDirectory(t)
si := localSource(env, dir)
currentHour := clock.Now().Hour()
mustCreateSource(t, cli, dir, &policy.Policy{
SchedulingPolicy: policy.SchedulingPolicy{
TimesOfDay: []policy.TimeOfDay{
{Hour: (currentHour + 2) % 24, Minute: 33},
},
},
})
sources := mustListSources(t, cli, &snapshot.SourceInfo{})
require.Len(t, sources, 1)
require.NotNil(t, sources[0].NextSnapshotTime)
require.Equal(t, 33, sources[0].NextSnapshotTime.Minute())
mustSetPolicy(t, cli, si, &policy.Policy{
SchedulingPolicy: policy.SchedulingPolicy{
TimesOfDay: []policy.TimeOfDay{
{Hour: (currentHour + 2) % 24, Minute: 55},
},
},
})
// make sure that soon after setting policy, the next snapshot time is up-to-date.
match := false
for attempt := 0; attempt < 3; attempt++ {
sources = mustListSources(t, cli, &snapshot.SourceInfo{})
require.Len(t, sources, 1)
require.NotNil(t, sources[0].NextSnapshotTime)
if sources[0].NextSnapshotTime.Minute() == 55 {
match = true
break
}
time.Sleep(time.Second)
}
require.True(t, match)
}