@@ -17,7 +17,6 @@ import (
1717
1818 "github.com/CodeMonkeyCybersecurity/eos/pkg/eos_io"
1919 "github.com/CodeMonkeyCybersecurity/eos/pkg/shared"
20- "github.com/CodeMonkeyCybersecurity/eos/pkg/vault"
2120 "github.com/uptrace/opentelemetry-go-extra/otelzap"
2221 "go.uber.org/zap"
2322)
@@ -57,7 +56,7 @@ func NewClient(rc *eos_io.RuntimeContext, repoName string) (*Client, error) {
5756func (c * Client ) RunRestic (args ... string ) ([]byte , error ) {
5857 logger := otelzap .Ctx (c .rc .Ctx )
5958
60- // Get password from Vault
59+ // Get repository password
6160 password , err := c .getRepositoryPassword ()
6261 if err != nil {
6362 return nil , fmt .Errorf ("getting repository password: %w" , err )
@@ -139,54 +138,61 @@ func (c *Client) RunRestic(args ...string) ([]byte, error) {
139138 return output , nil
140139}
141140
142- // getRepositoryPassword retrieves password from Vault
141+ // getRepositoryPassword retrieves the repository password using local secret stores
143142func (c * Client ) getRepositoryPassword () (string , error ) {
144143 logger := otelzap .Ctx (c .rc .Ctx )
145144
146- vaultPath := fmt .Sprintf ("eos/backup/repositories/%s" , c .repository .Name )
147- logger .Info ("Retrieving repository password from Vault" ,
148- zap .String ("path" , vaultPath ))
149-
150- vaultAddr := shared .GetVaultAddrWithEnv ()
151-
152- // Try to connect to Vault
153- vClient , err := vault .NewClient (vaultAddr , logger .Logger ().Logger )
154- if err != nil {
155- // Fall back to local password file if Vault unavailable
156- logger .Warn ("Vault unavailable, checking local password file" ,
145+ // 1. Repository-local password file (created by quick backup generator)
146+ localPasswordPath := filepath .Join (c .repository .URL , ".password" )
147+ if password , err := readPasswordFile (localPasswordPath ); err == nil {
148+ logger .Debug ("Using repository-local password file" ,
149+ zap .String ("path" , localPasswordPath ))
150+ return password , nil
151+ } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
152+ logger .Warn ("Failed to read repository-local password file" ,
153+ zap .String ("path" , localPasswordPath ),
157154 zap .Error (err ))
155+ }
158156
159- passwordFile := fmt .Sprintf ("/var/lib/eos/secrets/backup/%s.password" , c .repository .Name )
160- if data , err := os .ReadFile (passwordFile ); err == nil {
161- return strings .TrimSpace (string (data )), nil
162- }
163-
164- // Fallback: repository-local password file (used by quick backups)
165- localPasswordFile := filepath .Join (c .repository .URL , ".password" )
166- if data , err := os .ReadFile (localPasswordFile ); err == nil {
167- logger .Info ("Using repository-local password file" ,
168- zap .String ("path" , localPasswordFile ))
169- return strings .TrimSpace (string (data )), nil
170- }
171-
172- return "" , fmt .Errorf ("vault unavailable and no local password found" )
157+ // 2. Global secrets directory fallback (used by managed repositories)
158+ secretsPasswordPath := filepath .Join (SecretsDir , fmt .Sprintf ("%s.password" , c .repository .Name ))
159+ if password , err := readPasswordFile (secretsPasswordPath ); err == nil {
160+ logger .Debug ("Using secrets directory password file" ,
161+ zap .String ("path" , secretsPasswordPath ))
162+ return password , nil
163+ } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
164+ logger .Warn ("Failed to read secrets directory password file" ,
165+ zap .String ("path" , secretsPasswordPath ),
166+ zap .Error (err ))
173167 }
174168
175- secret , err := vClient .GetSecret (c .rc .Ctx , vaultPath )
176- if err != nil {
177- return "" , fmt .Errorf ("reading from vault: %w" , err )
169+ // 3. Repository `.env` file (temporary secret storage during Vault testing)
170+ envPath := filepath .Join (c .repository .URL , ".env" )
171+ if password , err := readPasswordFromEnvFile (envPath ); err == nil {
172+ logger .Debug ("Using repository .env file for restic password" ,
173+ zap .String ("path" , envPath ))
174+ return password , nil
175+ } else if err != nil && ! errors .Is (err , os .ErrNotExist ) {
176+ logger .Warn ("Failed to read repository .env file" ,
177+ zap .String ("path" , envPath ),
178+ zap .Error (err ))
178179 }
179180
180- if secret == nil || secret .Data == nil {
181- return "" , fmt .Errorf ("no secret found at %s" , vaultPath )
181+ // 4. Environment variable overrides (least preferred, but supported for manual ops)
182+ if password := strings .TrimSpace (os .Getenv ("RESTIC_PASSWORD" )); password != "" {
183+ logger .Warn ("Using RESTIC_PASSWORD environment variable; prefer password files for security" )
184+ return password , nil
182185 }
183186
184- password , ok := secret .Data ["password" ].(string )
185- if ! ok {
186- return "" , fmt .Errorf ("invalid password format in vault" )
187+ if passwordFile := strings .TrimSpace (os .Getenv ("RESTIC_PASSWORD_FILE" )); passwordFile != "" {
188+ if password , err := readPasswordFile (passwordFile ); err == nil {
189+ logger .Warn ("Using RESTIC_PASSWORD_FILE override; prefer managed password files" ,
190+ zap .String ("path" , passwordFile ))
191+ return password , nil
192+ }
187193 }
188194
189- return password , nil
195+ return "" , fmt . Errorf ( "restic repository password not found; expected password file at %s or %s" , localPasswordPath , secretsPasswordPath )
190196}
191197
192198// InitRepository initializes a new restic repository
@@ -295,6 +301,45 @@ func (c *Client) Backup(profileName string) error {
295301 return nil
296302}
297303
304+ // readPasswordFile reads and trims a password from the provided file path.
305+ func readPasswordFile (path string ) (string , error ) {
306+ data , err := os .ReadFile (path )
307+ if err != nil {
308+ return "" , err
309+ }
310+
311+ password := strings .TrimSpace (string (data ))
312+ if password == "" {
313+ return "" , fmt .Errorf ("password file %s is empty" , path )
314+ }
315+
316+ return password , nil
317+ }
318+
319+ // readPasswordFromEnvFile retrieves a restic password from a .env file if present.
320+ // The file may contain either RESTIC_PASSWORD or RESTIC_PASSWORD_FILE pointing to a
321+ // secondary password file.
322+ func readPasswordFromEnvFile (path string ) (string , error ) {
323+ if _ , err := os .Stat (path ); err != nil {
324+ return "" , err
325+ }
326+
327+ vars , err := shared .ParseEnvFile (path )
328+ if err != nil {
329+ return "" , err
330+ }
331+
332+ if passwordFile , ok := vars ["RESTIC_PASSWORD_FILE" ]; ok && strings .TrimSpace (passwordFile ) != "" {
333+ return readPasswordFile (strings .TrimSpace (passwordFile ))
334+ }
335+
336+ if password , ok := vars ["RESTIC_PASSWORD" ]; ok && strings .TrimSpace (password ) != "" {
337+ return strings .TrimSpace (password ), nil
338+ }
339+
340+ return "" , fmt .Errorf ("restic password not found in %s" , path )
341+ }
342+
298343// runBackupWithProgress executes backup with JSON progress parsing
299344// SECURITY: Uses password file instead of environment variable to prevent
300345// password exposure via 'ps auxe' (CVSS 7.5 vulnerability mitigation)
@@ -303,7 +348,7 @@ func (c *Client) runBackupWithProgress(args []string) error {
303348
304349 cmd := exec .CommandContext (c .rc .Ctx , "restic" , args ... )
305350
306- // Get password from Vault
351+ // Get repository password
307352 password , err := c .getRepositoryPassword ()
308353 if err != nil {
309354 return err
0 commit comments