mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-22 10:58:57 +00:00
This prevents combining untrusted with introducer and auto-accept, and also verifies that folders shared with untrusted devices have passwords at config loading time. Co-authored-by: Simon Frei <freisim93@gmail.com>
This commit is contained in:
parent
7d56fba321
commit
6b475bdb78
@ -549,6 +549,18 @@ ul.three-columns li, ul.two-columns li {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox[disabled] {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
opacity: 1;
|
||||||
|
margin-left: -5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox[disabled] *, .checkbox[disabled] .help-block {
|
||||||
|
color: #999999;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* Make a "well" look more like a readonly text input when grouped with a button */
|
/* Make a "well" look more like a readonly text input when grouped with a button */
|
||||||
.input-group .well-sm {
|
.input-group .well-sm {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
|
@ -1709,6 +1709,13 @@ angular.module('syncthing.core')
|
|||||||
return $scope.currentDevice._editing == 'new';
|
return $scope.currentDevice._editing == 'new';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.editDeviceUntrustedChanged = function () {
|
||||||
|
if (currentDevice.untrusted) {
|
||||||
|
currentDevice.introducer = false;
|
||||||
|
currentDevice.autoAcceptFolders = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$scope.editDeviceExisting = function (deviceCfg) {
|
$scope.editDeviceExisting = function (deviceCfg) {
|
||||||
$scope.currentDevice = $.extend({}, deviceCfg);
|
$scope.currentDevice = $.extend({}, deviceCfg);
|
||||||
$scope.currentDevice._editing = "existing";
|
$scope.currentDevice._editing = "existing";
|
||||||
|
@ -62,9 +62,9 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div ng-disabled="currentDevice.untrusted" class="checkbox" ng-attr-tooltip="{{currentDevice.untrusted ? null : undefined}}" ng-attr-data-original-title="{{currentDevice.untrusted ? ('Always disabled for untrusted devices' | translate) : undefined}}">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ng-model="currentDevice.introducer">
|
<input ng-disabled="currentDevice.untrusted" type="checkbox" ng-model="currentDevice.introducer">
|
||||||
<span translate>Introducer</span>
|
<span translate>Introducer</span>
|
||||||
<p translate class="help-block">Add devices from the introducer to our device list, for mutually shared folders.</p>
|
<p translate class="help-block">Add devices from the introducer to our device list, for mutually shared folders.</p>
|
||||||
</label>
|
</label>
|
||||||
@ -73,9 +73,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div ng-disabled="currentDevice.untrusted" class="checkbox" ng-attr-tooltip="{{currentDevice.untrusted ? null : undefined}}" ng-attr-data-original-title="{{currentDevice.untrusted ? ('Always disabled for untrusted devices' | translate) : undefined}}">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ng-model="currentDevice.autoAcceptFolders">
|
<input ng-disabled="currentDevice.untrusted" type="checkbox" ng-model="currentDevice.autoAcceptFolders">
|
||||||
<span translate>Auto Accept</span>
|
<span translate>Auto Accept</span>
|
||||||
<p translate class="help-block">Automatically create or share folders that this device advertises at the default path.</p>
|
<p translate class="help-block">Automatically create or share folders that this device advertises at the default path.</p>
|
||||||
</label>
|
</label>
|
||||||
@ -164,7 +164,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
<input type="checkbox" id="untrusted" ng-model="currentDevice.untrusted" />
|
<input type="checkbox" id="untrusted" ng-model="currentDevice.untrusted" ng-change="editDeviceUntrustedChanged()"/>
|
||||||
<label for="untrusted" translate>Untrusted</label>
|
<label for="untrusted" translate>Untrusted</label>
|
||||||
<p translate class="help-block">All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.</p>
|
<p translate class="help-block">All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -284,7 +284,7 @@ func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[protocol.DeviceID]bool, error) {
|
func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[protocol.DeviceID]*DeviceConfiguration, error) {
|
||||||
existingDevices := cfg.prepareDeviceList()
|
existingDevices := cfg.prepareDeviceList()
|
||||||
|
|
||||||
sharedFolders, err := cfg.prepareFolders(myID, existingDevices)
|
sharedFolders, err := cfg.prepareFolders(myID, existingDevices)
|
||||||
@ -297,7 +297,7 @@ func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[
|
|||||||
return existingDevices, nil
|
return existingDevices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]bool {
|
func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]*DeviceConfiguration {
|
||||||
// Ensure that the device list is
|
// Ensure that the device list is
|
||||||
// - free from duplicates
|
// - free from duplicates
|
||||||
// - no devices with empty ID
|
// - no devices with empty ID
|
||||||
@ -309,14 +309,14 @@ func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Build a list of available devices
|
// Build a list of available devices
|
||||||
existingDevices := make(map[protocol.DeviceID]bool, len(cfg.Devices))
|
existingDevices := make(map[protocol.DeviceID]*DeviceConfiguration, len(cfg.Devices))
|
||||||
for _, device := range cfg.Devices {
|
for i, device := range cfg.Devices {
|
||||||
existingDevices[device.DeviceID] = true
|
existingDevices[device.DeviceID] = &cfg.Devices[i]
|
||||||
}
|
}
|
||||||
return existingDevices
|
return existingDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) (map[protocol.DeviceID][]string, error) {
|
func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]*DeviceConfiguration) (map[protocol.DeviceID][]string, error) {
|
||||||
// Prepare folders and check for duplicates. Duplicates are bad and
|
// Prepare folders and check for duplicates. Duplicates are bad and
|
||||||
// dangerous, can't currently be resolved in the GUI, and shouldn't
|
// dangerous, can't currently be resolved in the GUI, and shouldn't
|
||||||
// happen when configured by the GUI. We return with an error in that
|
// happen when configured by the GUI. We return with an error in that
|
||||||
@ -359,13 +359,13 @@ func (cfg *Configuration) prepareDevices(sharedFolders map[protocol.DeviceID][]s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]bool) map[protocol.DeviceID]bool {
|
func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]*DeviceConfiguration) map[protocol.DeviceID]bool {
|
||||||
// The list of ignored devices should not contain any devices that have
|
// The list of ignored devices should not contain any devices that have
|
||||||
// been manually added to the config.
|
// been manually added to the config.
|
||||||
newIgnoredDevices := cfg.IgnoredDevices[:0]
|
newIgnoredDevices := cfg.IgnoredDevices[:0]
|
||||||
ignoredDevices := make(map[protocol.DeviceID]bool, len(cfg.IgnoredDevices))
|
ignoredDevices := make(map[protocol.DeviceID]bool, len(cfg.IgnoredDevices))
|
||||||
for _, dev := range cfg.IgnoredDevices {
|
for _, dev := range cfg.IgnoredDevices {
|
||||||
if !existingDevices[dev.ID] {
|
if _, ok := existingDevices[dev.ID]; !ok {
|
||||||
ignoredDevices[dev.ID] = true
|
ignoredDevices[dev.ID] = true
|
||||||
newIgnoredDevices = append(newIgnoredDevices, dev)
|
newIgnoredDevices = append(newIgnoredDevices, dev)
|
||||||
}
|
}
|
||||||
@ -502,7 +502,7 @@ func ensureDevicePresent(devices []FolderDeviceConfiguration, myID protocol.Devi
|
|||||||
return devices
|
return devices
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureExistingDevices(devices []FolderDeviceConfiguration, existingDevices map[protocol.DeviceID]bool) []FolderDeviceConfiguration {
|
func ensureExistingDevices(devices []FolderDeviceConfiguration, existingDevices map[protocol.DeviceID]*DeviceConfiguration) []FolderDeviceConfiguration {
|
||||||
count := len(devices)
|
count := len(devices)
|
||||||
i := 0
|
i := 0
|
||||||
loop:
|
loop:
|
||||||
@ -553,6 +553,23 @@ loop:
|
|||||||
return devices[0:count]
|
return devices[0:count]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureNoUntrustedTrustingSharing(f *FolderConfiguration, devices []FolderDeviceConfiguration, existingDevices map[protocol.DeviceID]*DeviceConfiguration) []FolderDeviceConfiguration {
|
||||||
|
for i := 0; i < len(devices); i++ {
|
||||||
|
dev := devices[i]
|
||||||
|
if dev.EncryptionPassword != "" {
|
||||||
|
// There's a password set, no check required
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if devCfg := existingDevices[dev.DeviceID]; devCfg.Untrusted {
|
||||||
|
l.Warnf("Folder %s (%s) is shared in trusted mode with untrusted device %s (%s); unsharing.", f.ID, f.Label, dev.DeviceID.Short(), devCfg.Name)
|
||||||
|
copy(devices[i:], devices[i+1:])
|
||||||
|
devices = devices[:len(devices)-1]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
|
||||||
func cleanSymlinks(filesystem fs.Filesystem, dir string) {
|
func cleanSymlinks(filesystem fs.Filesystem, dir string) {
|
||||||
if build.IsWindows {
|
if build.IsWindows {
|
||||||
// We don't do symlinks on Windows. Additionally, there may
|
// We don't do symlinks on Windows. Additionally, there may
|
||||||
@ -611,7 +628,7 @@ func getFreePort(host string, ports ...int) (int, error) {
|
|||||||
return addr.Port, nil
|
return addr.Port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
|
func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]*DeviceConfiguration) {
|
||||||
ensureZeroForNodefault(&FolderConfiguration{}, &defaults.Folder)
|
ensureZeroForNodefault(&FolderConfiguration{}, &defaults.Folder)
|
||||||
ensureZeroForNodefault(&DeviceConfiguration{}, &defaults.Device)
|
ensureZeroForNodefault(&DeviceConfiguration{}, &defaults.Device)
|
||||||
defaults.Folder.prepare(myID, existingDevices)
|
defaults.Folder.prepare(myID, existingDevices)
|
||||||
|
@ -1489,6 +1489,65 @@ func TestXattrFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUntrustedIntroducer(t *testing.T) {
|
||||||
|
fd, err := os.Open("testdata/untrustedintroducer.xml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg, _, err := ReadXML(fd, device1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Devices) != 2 {
|
||||||
|
// ourselves and the remote in the config
|
||||||
|
t.Fatal("Expected two devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the introducer and auto-accept flags were negated
|
||||||
|
var foundUntrusted protocol.DeviceID
|
||||||
|
for _, d := range cfg.Devices {
|
||||||
|
if d.Name == "untrusted" {
|
||||||
|
foundUntrusted = d.DeviceID
|
||||||
|
if !d.Untrusted {
|
||||||
|
t.Error("untrusted device should be untrusted")
|
||||||
|
}
|
||||||
|
if d.Introducer {
|
||||||
|
t.Error("untrusted device should not be an introducer")
|
||||||
|
}
|
||||||
|
if d.AutoAcceptFolders {
|
||||||
|
t.Error("untrusted device should not auto-accept folders")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundUntrusted.Equals(protocol.EmptyDeviceID) {
|
||||||
|
t.Error("untrusted device not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Folder A has the device added without a password, which is not permitted
|
||||||
|
folderA := cfg.FolderMap()["a"]
|
||||||
|
for _, dev := range folderA.Devices {
|
||||||
|
if dev.DeviceID == foundUntrusted {
|
||||||
|
t.Error("untrusted device should not be in folder A")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Folder B has the device added with a password, this is just a sanity check
|
||||||
|
folderB := cfg.FolderMap()["b"]
|
||||||
|
found := false
|
||||||
|
for _, dev := range folderB.Devices {
|
||||||
|
if dev.DeviceID == foundUntrusted {
|
||||||
|
found = true
|
||||||
|
if dev.EncryptionPassword == "" {
|
||||||
|
t.Error("untrusted device should have a password in folder B")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("untrusted device not found in folder B")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that opening a config with myID == protocol.EmptyDeviceID doesn't add that ID to the config.
|
// Verify that opening a config with myID == protocol.EmptyDeviceID doesn't add that ID to the config.
|
||||||
// Done in various places where config is needed, but the device ID isn't known.
|
// Done in various places where config is needed, but the device ID isn't known.
|
||||||
func TestLoadEmptyDeviceID(t *testing.T) {
|
func TestLoadEmptyDeviceID(t *testing.T) {
|
||||||
|
@ -34,6 +34,19 @@ func (cfg *DeviceConfiguration) prepare(sharedFolders []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg.IgnoredFolders = sortedObservedFolderSlice(ignoredFolders)
|
cfg.IgnoredFolders = sortedObservedFolderSlice(ignoredFolders)
|
||||||
|
|
||||||
|
// A device cannot be simultaneously untrusted and an introducer, nor
|
||||||
|
// auto accept folders.
|
||||||
|
if cfg.Untrusted {
|
||||||
|
if cfg.Introducer {
|
||||||
|
l.Warnf("Device %s (%s) is both untrusted and an introducer, removing introducer flag", cfg.DeviceID.Short(), cfg.Name)
|
||||||
|
cfg.Introducer = false
|
||||||
|
}
|
||||||
|
if cfg.AutoAcceptFolders {
|
||||||
|
l.Warnf("Device %s (%s) is both untrusted and auto-accepting folders, removing auto-accept flag", cfg.DeviceID.Short(), cfg.Name)
|
||||||
|
cfg.AutoAcceptFolders = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *DeviceConfiguration) IgnoredFolder(folder string) bool {
|
func (cfg *DeviceConfiguration) IgnoredFolder(folder string) bool {
|
||||||
|
@ -181,14 +181,16 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
|
|||||||
return deviceIDs
|
return deviceIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
|
func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]*DeviceConfiguration) {
|
||||||
// Ensure that
|
// Ensure that
|
||||||
// - any loose devices are not present in the wrong places
|
// - any loose devices are not present in the wrong places
|
||||||
// - there are no duplicate devices
|
// - there are no duplicate devices
|
||||||
// - we are part of the devices
|
// - we are part of the devices
|
||||||
|
// - folder is not shared in trusted mode with an untrusted device
|
||||||
f.Devices = ensureExistingDevices(f.Devices, existingDevices)
|
f.Devices = ensureExistingDevices(f.Devices, existingDevices)
|
||||||
f.Devices = ensureNoDuplicateFolderDevices(f.Devices)
|
f.Devices = ensureNoDuplicateFolderDevices(f.Devices)
|
||||||
f.Devices = ensureDevicePresent(f.Devices, myID)
|
f.Devices = ensureDevicePresent(f.Devices, myID)
|
||||||
|
f.Devices = ensureNoUntrustedTrustingSharing(f, f.Devices, existingDevices)
|
||||||
|
|
||||||
sort.Slice(f.Devices, func(a, b int) bool {
|
sort.Slice(f.Devices, func(a, b int) bool {
|
||||||
return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
|
return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
|
||||||
|
17
lib/config/testdata/untrustedintroducer.xml
vendored
Normal file
17
lib/config/testdata/untrustedintroducer.xml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<configuration version="37">
|
||||||
|
<folder id="a" label="a" path="a">
|
||||||
|
<device id="6564BQV-R2WYPMN-5OLXMII-CDJUKFD-BHNNCRA-WLQPAIV-ELSGAD2-RMFBFQU" introducedBy="">
|
||||||
|
<encryptionPassword></encryptionPassword>
|
||||||
|
</device>
|
||||||
|
</folder>
|
||||||
|
<folder id="b" label="b" path="b">
|
||||||
|
<device id="6564BQV-R2WYPMN-5OLXMII-CDJUKFD-BHNNCRA-WLQPAIV-ELSGAD2-RMFBFQU" introducedBy="">
|
||||||
|
<encryptionPassword>a complex password</encryptionPassword>
|
||||||
|
</device>
|
||||||
|
</folder>
|
||||||
|
<device id="6564BQV-R2WYPMN-5OLXMII-CDJUKFD-BHNNCRA-WLQPAIV-ELSGAD2-RMFBFQU" name="untrusted" compression="metadata" introducer="true" skipIntroductionRemovals="false" introducedBy="">
|
||||||
|
<address>dynamic</address>
|
||||||
|
<autoAcceptFolders>true</autoAcceptFolders>
|
||||||
|
<untrusted>true</untrusted>
|
||||||
|
</device>
|
||||||
|
</configuration>
|
Loading…
Reference in New Issue
Block a user