Skip to content

Commit 818f8bc

Browse files
Merge branch 'main' into add-bucket-fallback
2 parents 7b0b035 + 2958d36 commit 818f8bc

25 files changed

+334
-64
lines changed

.github/workflows/publish-release.yml

+3-6
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,11 @@ jobs:
191191
contents: write
192192

193193
steps:
194-
# libfuse-dev is required to build our command-line program
194+
# libfuse-dev is required to build our command-line program and enable GoReleaser to build for ARM64
195195
- name: Install Libfuse
196196
run: |
197-
sudo apt-get install -y libfuse-dev
198-
# enable GoReleaser to build for ARM64
199-
- name: Install ARM64 compilers
200-
run: |
201-
sudo apt-get install -y gcc-aarch64-linux-gnu
197+
sudo apt-get update
198+
sudo apt-get install -y gcc-aarch64-linux-gnu libfuse-dev
202199
# Get code and Go ready
203200
- name: Checkout code
204201
uses: actions/checkout@v4

CHANGELOG.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
# Cloudfuse Changelog #
22

3+
## **1.9.1** ##
4+
5+
March 6th 2025
6+
This version is based on [blobfuse2 2.3.2](https://github.com/Azure/azure-storage-fuse/releases/tag/blobfuse2-2.3.2) (upstream).
7+
8+
### Bug Fixes ###
9+
10+
- [#471](https://github.com/Seagate/cloudfuse/pull/471) Fix bug where passphrase for secure encryption was not properly decoded as base64
11+
12+
## **1.9.0** ##
13+
14+
March 4th 2025
15+
This version is based on [blobfuse2 2.3.2](https://github.com/Azure/azure-storage-fuse/releases/tag/blobfuse2-2.3.2) (upstream).
16+
17+
### Changes ###
18+
19+
- [#469](https://github.com/Seagate/cloudfuse/pull/469) Add enable-remount and disable-remount flags to CLI for Windows to better enable customizability on which mounts should remount on restart
20+
- [#467](https://github.com/Seagate/cloudfuse/pull/467) Fixed bug with creation of windows startup utility
21+
22+
## **1.8.2** ##
23+
24+
March 3rd 2025
25+
This version is based on [blobfuse2 2.3.2](https://github.com/Azure/azure-storage-fuse/releases/tag/blobfuse2-2.3.2) (upstream).
26+
27+
### Changes ###
28+
29+
- [#445](https://github.com/Seagate/cloudfuse/pull/462) Support custom SDDL strings on Windows to customize mount permissions
30+
- [#464](https://github.com/Seagate/cloudfuse/pull/464) No_gui installer now is able to restart mounts on restart if the user installs cloudfuse as a service
31+
- [#445](https://github.com/Seagate/cloudfuse/pull/450) GUI is able to mount as a drive letter on Windows
32+
333
## **1.8.1** ##
434

535
February 20th 2025
@@ -16,7 +46,6 @@ This version is based on [blobfuse2 2.3.2](https://github.com/Azure/azure-storag
1646
- [#444](https://github.com/Seagate/cloudfuse/pull/444) Fix issue when renaming directories
1747
- [#455](https://github.com/Seagate/cloudfuse/pull/455) Network share on Windows now correctly uses the hostname
1848

19-
2049
## **1.8.0** ##
2150

2251
February 4th 2025

WINDOWS.md

+50-3
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,71 @@ To run in foreground mode, you must pass the option `--foreground=true` when usi
1313
## Running in background mode (recommended)
1414

1515
Cloudfuse runs in the background by default. It uses the WinFSP launcher to run the mount in the background.
16-
Cloudfuse will also automatically restart existing mounts on user login.
1716

1817
cloudfuse.exe mount <mount path> --config-file=<config file>
1918

20-
To unmount a specific instance, use the unmount command. This will also prevent this mount from persisting on restarts.
19+
Cloudfuse can also automatically restart existing mounts on user login. To do so, pass the --enable-remount flag
20+
when mounting
21+
22+
cloudfuse.exe mount <mount path> --config-file=<config file> --enable-remount
23+
24+
To unmount a specific instance, use the unmount command.
2125

2226
cloudfuse.exe unmount <mount path>
2327

28+
To unmount and also prevent this mount from persisting on restarts pass the --disable-remount flag.
29+
30+
cloudfuse.exe unmount <mount path> --disable-remount.
31+
2432
Cloudfuse supports mounting any number of buckets.
2533

26-
If the container is not automatically mounted on user login after a reboot, you may need to (re)install the Cloudfuse startup program:
34+
If the container is not automatically mounted on user login after a reboot, you may need to (re)install the Cloudfuse
35+
startup program:
2736

2837
cloudfuse.exe service install
2938

3039
To uninstall the Cloudfuse startup program use the uninstall command.
3140

3241
cloudfuse.exe service uninstall
3342

43+
## Windows Security and User Permissions
44+
45+
By default, cloudfuse allows all users to read/write to the mounted directory. If you need specific permissions for your
46+
share you must provide them in your config file when you mount using cloudfuse. This requires you to generate and use
47+
Security Descriptor Definition Language (SDDL) strings to manage permissions. We provide a brief example for how to
48+
generate SDDL strings to change permissions.
49+
50+
1. Ensure that you have WinFsp installed on your system:
51+
52+
If you used the standard cloudfuse installer on Windows, then WinFsp is already installed on your system.
53+
54+
2. Find Your Account's SID and UID:
55+
56+
Use the fsptool utility to discover your account's SID and UID. Open a command prompt and run:
57+
58+
'C:\Program Files (x86)\WinFsp\bin\fsptool-x64.exe' id
59+
60+
This will output something like:
61+
62+
User=S-1-5-21-773277305-2169295204-1991566178-478888(user) (uid=21479625)
63+
Owner=S-1-5-21-773277305-2169295204-1991566178-478888(user) (uid=21479625)
64+
Group=S-1-5-21-773277305-2169295204-1991566178-478888(user) (gid=21479625)
65+
66+
3. Generate SDDL for Specific Permissions: Use the fsptool to generate the SDDL string for your account's UID with
67+
specific permissions. For example, to generate an SDDL for rwx------ permissions:
68+
69+
'C:\Program Files (x86)\WinFsp\bin\fsptool-x64.exe' perm 21479625:0:700
70+
71+
This will output something like:
72+
73+
O:S-1-0-65534G:S-1-5-0D:P(A;;0x1f01bf;;;S-1-0-65534)(A;;0x120088;;;S-1-5-0)(A;;0x120088;;;WD) (perm=65534:0:0700)
74+
75+
4. Edit your config file: Edit the libfuse section of the config.yaml file to include the windows-sddl entry. Add the
76+
SDDL string you generated in the previous step. For example:
77+
78+
libfuse:
79+
windows-sddl: O:S-1-0-65534G:S-1-5-0D:P(A;;0x1f01bf;;;S-1-0-65534)(A;;0x120088;;;S-1-5-0)(A;;0x120088;;;WD)
80+
3481
## Filename Limitations
3582

3683
As Cloudfuse supports both Windows and Linux as well as Azure and S3 storage, there are naming restrictions that must be

build/windows_installer_build.iss

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
; https://jrsoftware.org/ishelp/index.php
44

55
#define MyAppName "Cloudfuse"
6-
#define MyAppVersion "1.8.1"
6+
#define MyAppVersion "1.9.1"
77
#define MyAppPublisher "SEAGATE TECHNOLOGY LLC"
88
#define MyAppURL "https://github.com/Seagate/cloudfuse"
99
#define MyAppExeName "cloudfuseGUI.exe"

build/windows_installer_build_no_gui.iss

+8-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
; https://jrsoftware.org/ishelp/index.php
44

55
#define MyAppName "Cloudfuse"
6-
#define MyAppVersion "1.8.1"
6+
#define MyAppVersion "1.9.1"
77
#define MyAppPublisher "SEAGATE TECHNOLOGY LLC"
88
#define MyAppURL "https://github.com/Seagate/cloudfuse"
99
#define MyAppExeCLIName "cloudfuse.exe"
@@ -44,6 +44,7 @@ Name: "{userappdata}\{#MyAppName}"; Flags: uninsalwaysuninstall
4444
[Files]
4545
Source: "..\cloudfuse.exe"; DestDir: "{app}"; Flags: ignoreversion
4646
Source: "..\cfusemon.exe"; DestDir: "{app}"; Flags: ignoreversion
47+
Source: "..\windows-startup.exe"; DestDir: "{app}"; Flags: ignoreversion
4748
Source: "..\NOTICE"; DestDir: "{app}"; Flags: ignoreversion
4849
Source: "..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
4950
Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion
@@ -100,10 +101,10 @@ begin
100101
end;
101102
end;
102103
103-
// Add Cloudfuse registry
104-
if not Exec(ExpandConstant('{app}\{#MyAppExeCLIName}'), 'service add-registry', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
104+
// Install the Cloudfuse Startup Tool
105+
if not Exec(ExpandConstant('{app}\{#MyAppExeCLIName}'), 'service install', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
105106
begin
106-
SuppressibleMsgBox('Failed to add cloudfuse registry. This will prevent cloudfuse from starting.', mbError, MB_OK, IDOK);
107+
SuppressibleMsgBox('Failed to install cloudfuse as a service. You may need to do this manually from the command line.', mbError, MB_OK, IDOK);
107108
end;
108109
end;
109110
end;
@@ -114,10 +115,10 @@ var
114115
begin
115116
if CurUninstallStep = usUninstall then
116117
begin
117-
// Remove Cloudfuse registry
118-
if not Exec(ExpandConstant('{app}\{#MyAppExeCLIName}'), 'service remove-registry', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
118+
// Install the Cloudfuse Startup Tool
119+
if not Exec(ExpandConstant('{app}\{#MyAppExeCLIName}'), 'service uninstall', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
119120
begin
120-
SuppressibleMsgBox('Failed to remove cloudfuse registry.', mbError, MB_OK, IDOK);
121+
SuppressibleMsgBox('Failed to remove cloudfuse as a service.', mbError, MB_OK, IDOK);
121122
end;
122123
123124
// Ask the user if they would like to also uninstall WinFSP

cmd/config-gen.go

+24-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
package cmd
2727

2828
import (
29+
"encoding/base64"
30+
"errors"
2931
"fmt"
3032
"os"
3133
"regexp"
@@ -111,9 +113,11 @@ var generateConfig = &cobra.Command{
111113
FlagErrorHandling: cobra.ExitOnError,
112114
RunE: func(cmd *cobra.Command, args []string) error {
113115
var templateConfig []byte
114-
var err error
115116

116-
encryptedPassphrase = memguard.NewEnclave([]byte(opts.passphrase))
117+
err := validateGenConfigOptions()
118+
if err != nil {
119+
return fmt.Errorf("failed to validate options [%s]", err.Error())
120+
}
117121

118122
templateConfig, err = os.ReadFile(opts.configFilePath)
119123
if err != nil {
@@ -152,6 +156,24 @@ var generateConfig = &cobra.Command{
152156
},
153157
}
154158

159+
func validateGenConfigOptions() error {
160+
if opts.passphrase == "" {
161+
opts.passphrase = os.Getenv(SecureConfigEnvName)
162+
if opts.passphrase == "" {
163+
return errors.New("provide the passphrase as a cli parameter or configure the CLOUDFUSE_SECURE_CONFIG_PASSPHRASE environment variable")
164+
}
165+
}
166+
167+
_, err := base64.StdEncoding.DecodeString(string(opts.passphrase))
168+
if err != nil {
169+
return fmt.Errorf("passphrase is not valid base64 encoded [%s]", err.Error())
170+
}
171+
172+
encryptedPassphrase = memguard.NewEnclave([]byte(opts.passphrase))
173+
174+
return nil
175+
}
176+
155177
func init() {
156178
rootCmd.AddCommand(generateTestConfig)
157179
generateTestConfig.Flags().StringVar(&opts.configFilePath, "config-file", "", "Input config file.")

cmd/mount.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type mountOptions struct {
8282
MonitorOpt monitorOptions `config:"health_monitor"`
8383
WaitForMount time.Duration `config:"wait-for-mount"`
8484
LazyWrite bool `config:"lazy-write"`
85+
EnableRemount bool
8586

8687
// v1 support
8788
Streaming bool `config:"streaming"`
@@ -328,7 +329,7 @@ var mountCmd = &cobra.Command{
328329
return errors.New("config file does not exist")
329330
}
330331
// mount using WinFSP, and persist on reboot
331-
err = createMountInstance()
332+
err = createMountInstance(options.EnableRemount)
332333
if err != nil {
333334
return fmt.Errorf("failed to mount instance [%s]", err.Error())
334335
}
@@ -745,6 +746,11 @@ func init() {
745746
config.BindPFlag("basic-remount-check", mountCmd.Flags().Lookup("basic-remount-check"))
746747
mountCmd.Flags().Lookup("basic-remount-check").Hidden = true
747748

749+
if runtime.GOOS == "windows" {
750+
mountCmd.Flags().BoolVar(&options.EnableRemount, "enable-remount", true, "Remount mount on server restart.")
751+
config.BindPFlag("enable-remount", mountCmd.Flags().Lookup("enable-remount"))
752+
}
753+
748754
mountCmd.PersistentFlags().StringSliceVarP(&options.LibfuseOptions, "o", "o", []string{}, "FUSE options.")
749755
config.BindPFlag("libfuse-options", mountCmd.PersistentFlags().ShorthandLookup("o"))
750756
mountCmd.PersistentFlags().ShorthandLookup("o").Hidden = true

cmd/mount_linux.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,6 @@ func sigusrHandler(pipeline *internal.Pipeline, ctx context.Context) daemon.Sign
131131
}
132132

133133
// stub for compilation
134-
func createMountInstance() error {
134+
func createMountInstance(bool) error {
135135
return nil
136136
}

cmd/mount_windows.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,17 @@ func createDaemon(pipeline *internal.Pipeline, ctx context.Context, pidFileName
4242
}
4343

4444
// Use WinFSP to mount and if successful, add instance to persistent mount list
45-
func createMountInstance() error {
45+
func createMountInstance(enableRemount bool) error {
4646
err := winservice.StartMount(options.MountPath, options.ConfigFile, encryptedPassphrase)
4747
if err != nil {
4848
return err
4949
}
5050
// Add the mount to the JSON file so it persists on restart.
51-
err = winservice.AddMountJSON(options.MountPath, options.ConfigFile)
52-
if err != nil {
53-
return fmt.Errorf("failed to add entry to json file [%s]", err.Error())
51+
if enableRemount {
52+
err = winservice.AddMountJSON(options.MountPath, options.ConfigFile)
53+
if err != nil {
54+
return fmt.Errorf("failed to add entry to json file [%s]", err.Error())
55+
}
5456
}
5557
return nil
5658
}

cmd/secure_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,27 @@ func (suite *secureConfigTestSuite) TestSecureConfigEncrypt() {
130130
suite.assert.NoFileExists(confFile.Name())
131131
}
132132

133+
func (suite *secureConfigTestSuite) TestSecureConfigEncrypt2() {
134+
defer suite.cleanupTest()
135+
confFile, _ := os.CreateTemp("", "conf*.yaml")
136+
outFile, _ := os.CreateTemp("", "conf*.yaml")
137+
passphrase := "hvHlJUKlmZql3gLAcP6Ho41Js5rm8zUAKnwGb1lIffg="
138+
139+
defer os.Remove(confFile.Name())
140+
defer os.Remove(outFile.Name())
141+
142+
_, err := confFile.WriteString(testPlainTextConfig)
143+
suite.assert.NoError(err)
144+
145+
confFile.Close()
146+
147+
_, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name()))
148+
suite.assert.NoError(err)
149+
150+
// Config file should be deleted
151+
suite.assert.NoFileExists(confFile.Name())
152+
}
153+
133154
func (suite *secureConfigTestSuite) TestSecureConfigEncryptNoOutfile() {
134155
defer suite.cleanupTest()
135156
confFile, _ := os.CreateTemp("", "conf*.yaml")
@@ -237,6 +258,40 @@ func (suite *secureConfigTestSuite) TestSecureConfigDecrypt() {
237258
os.Remove(confFile.Name() + "." + SecureConfigExtension)
238259
}
239260

261+
func (suite *secureConfigTestSuite) TestSecureConfigDecrypt2() {
262+
defer suite.cleanupTest()
263+
confFile, _ := os.CreateTemp("", "conf*.yaml")
264+
outFile, _ := os.CreateTemp("", "conf*.yaml")
265+
passphrase := "hvHlJUKlmZql3gLAcP6Ho41Js5rm8zUAKnwGb1lIffg="
266+
fmt.Println(passphrase)
267+
268+
defer os.Remove(confFile.Name())
269+
defer os.Remove(outFile.Name())
270+
271+
_, err := confFile.WriteString(testPlainTextConfig)
272+
suite.assert.NoError(err)
273+
274+
confFile.Close()
275+
outFile.Close()
276+
277+
_, err = executeCommandSecure(rootCmd, "secure", "encrypt", fmt.Sprintf("--config-file=%s", confFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=%s", outFile.Name()))
278+
suite.assert.NoError(err)
279+
280+
// Config file should be deleted
281+
suite.assert.NoFileExists(confFile.Name())
282+
283+
_, err = executeCommandSecure(rootCmd, "secure", "decrypt", fmt.Sprintf("--config-file=%s", outFile.Name()), fmt.Sprintf("--passphrase=%s", passphrase), fmt.Sprintf("--output-file=./tmp.yaml"))
284+
suite.assert.NoError(err)
285+
286+
data, err := os.ReadFile("./tmp.yaml")
287+
suite.assert.NoError(err)
288+
289+
suite.assert.Equal(testPlainTextConfig, string(data))
290+
291+
os.Remove("./tmp.yaml")
292+
os.Remove(confFile.Name() + "." + SecureConfigExtension)
293+
}
294+
240295
func (suite *secureConfigTestSuite) TestSecureConfigDecryptNoOutputFile() {
241296
defer suite.cleanupTest()
242297
confFile, _ := os.CreateTemp("", "conf*.yaml")

cmd/service_windows.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,9 @@ var installCmd = &cobra.Command{
7070
Example: "cloudfuse service install",
7171
FlagErrorHandling: cobra.ExitOnError,
7272
RunE: func(cmd *cobra.Command, args []string) error {
73-
dir, err := os.Getwd()
74-
if err != nil {
75-
return fmt.Errorf("unable to determine location of cloudfuse binary [%s]", err.Error())
76-
}
77-
programPath := filepath.Join(dir, "windows-startup.exe")
73+
programPath := filepath.Join("C:", "Program Files", "Cloudfuse", "windows-startup.exe")
7874
startupPath := filepath.Join(os.Getenv("APPDATA"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup", StartupName)
79-
err = makeLink(programPath, startupPath)
75+
err := makeLink(programPath, startupPath)
8076
if err != nil {
8177
return fmt.Errorf("unable to create startup link [%s]", err.Error())
8278
}

0 commit comments

Comments
 (0)