this repo has no description

feat: add simple enc/dec with generic io wrapper

Signed-off-by: A. Ottr <alex@otter.foo>

+124 -56
+1 -1
.nox.yaml
··· 2 2 age: 3 3 identity: "keys/key.txt" 4 4 recipients: 5 - - "age1nuxu3q9wr5wrd53dj8hj5flhz86q2dpjyuq7agseh0wzwq5t696s2dm0ht" 5 + - "age1qq5sazxv755u2vs5ulyl486jxhlg7ztrvm27nya47aln668xldkqsm4kn5" 6 6 statePath: ".nox-state.json" 7 7 defaultRepo: git@github.com:ShorkBytes/nox-secrets.git 8 8
+47 -19
cmd/nox/main.go
··· 36 36 Flags: []cli.Flag{ 37 37 &cli.StringFlag{ 38 38 Name: "config", 39 + Aliases: []string{"c"}, 39 40 Value: constants.DefaultConfigPath, 40 41 Usage: "path to config file", 41 42 Destination: &configPath, ··· 98 99 }, 99 100 }, 100 101 Action: func(ctx context.Context, cmd *cli.Command) error { 101 - fmt.Println("encrypting file", inputPath) 102 - fmt.Println("writing to", outputPath) 103 102 recipients, err := crypto.StringsToRecipients(cmd.StringSlice("recipient")) 104 103 if err != nil { 105 104 return err 106 105 } 107 - out, err := crypto.EncryptFile(inputPath, recipients) 108 - if err != nil { 106 + if err := processor.IOWrapper(inputPath, outputPath, recipients, crypto.EncryptBytes); err != nil { 109 107 return err 110 108 } 111 - fmt.Println(string(out)) 112 - // priv, pub, err := crypto.GenerateAndWriteX25519Identity("test.key") 113 - // if err != nil { 114 - // return err 115 - // } 116 - // fmt.Println(priv) 117 - // fmt.Println(pub) 118 - 119 109 return nil 120 110 }, 121 111 }, 122 112 { 123 - Name: "generate", 113 + Name: "decrypt", 114 + Aliases: []string{"dec"}, 115 + Usage: "Decrypt a file", 124 116 Flags: []cli.Flag{ 117 + &cli.StringFlag{ 118 + Name: "input", 119 + Usage: "path to input file", 120 + Aliases: []string{"i"}, 121 + Value: constants.StandardInput, 122 + Destination: &inputPath, 123 + }, 125 124 &cli.StringFlag{ 126 125 Name: "output", 127 126 Usage: "path to output file", ··· 131 130 }, 132 131 }, 133 132 Action: func(ctx context.Context, cmd *cli.Command) error { 134 - priv, pub, err := crypto.GenerateIdentity(cmd.String("output")) 133 + identities, err := crypto.LoadAgeIdentitiesFromPaths(identityPaths) 135 134 if err != nil { 136 135 return err 137 136 } 138 - fmt.Println(priv) 139 - fmt.Println(pub) 137 + return processor.IOWrapper(inputPath, constants.StandardOutput, identities, crypto.DecryptBytes) 138 + }, 139 + }, 140 + { 141 + Name: "generate", 142 + Flags: []cli.Flag{ 143 + &cli.StringFlag{ 144 + Name: "output", 145 + Usage: "path to output file", 146 + Aliases: []string{"o"}, 147 + Value: constants.StandardOutput, 148 + Destination: &outputPath, 149 + }, 150 + }, 151 + Action: func(ctx context.Context, cmd *cli.Command) error { 152 + 153 + output := cmd.String("output") 154 + switch output { 155 + case constants.StandardOutput: 156 + priv, pub, err := crypto.GenerateIdentity("") 157 + if err != nil { 158 + return err 159 + } 160 + fmt.Println("Public key:\n", pub, "\nPrivate Key:\n", priv) 161 + default: 162 + _, _, err := crypto.GenerateIdentity(cmd.String("output")) 163 + if err != nil { 164 + return err 165 + } 166 + } 167 + 140 168 return nil 141 169 }, 142 170 }, 143 171 { 144 - Name: "decrypt", 145 - Aliases: []string{"d"}, 146 - Usage: "Decrypts all secrets of one or all apps", 172 + Name: "sync", 173 + Aliases: []string{"fetch"}, 174 + Usage: "Fetches and decrypts all secrets of one or all apps", 147 175 Flags: []cli.Flag{ 148 176 &cli.StringFlag{ 149 177 Name: "app",
+1 -1
internal/crypto/encrypt.go
··· 29 29 } 30 30 31 31 if _, err := io.Copy(enc, src); err != nil { 32 - enc.Close() // ensure resources are freed 32 + enc.Close() 33 33 return nil, fmt.Errorf("encryption failed: %w", err) 34 34 } 35 35
+4
internal/crypto/identity.go
··· 47 47 priv = id.String() // "AGE-SECRET-KEY-1..." 48 48 pub = id.Recipient().String() // "age1..." 49 49 50 + if path == "" { 51 + return priv, pub, nil 52 + } 53 + 50 54 // ensure directory exists and apply permissions 51 55 if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { 52 56 return "", "", fmt.Errorf("mkdir %s: %w", filepath.Dir(path), err)
-35
internal/processor/file.go
··· 1 - package processor 2 - 3 - import ( 4 - "fmt" 5 - "os" 6 - "path/filepath" 7 - 8 - "github.com/aottr/nox/internal/config" 9 - ) 10 - 11 - type FileProcessorOptions struct { 12 - CreateDir bool 13 - } 14 - 15 - func WriteToFile(data []byte, file config.FileConfig, opts *FileProcessorOptions) error { 16 - path := file.Output 17 - if path == "" { 18 - // Default output filename if none specified, e.g. replace .age with .env 19 - path = filepath.Base(file.Path) 20 - fmt.Println(path) 21 - if filepath.Ext(path) == ".age" { 22 - path = path[:len(path)-4] + ".env" 23 - } 24 - } 25 - if opts.CreateDir { 26 - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 27 - return fmt.Errorf("failed to create directories for %s: %w", path, err) 28 - } 29 - } 30 - 31 - if err := os.WriteFile(path, data, 0600); err != nil { 32 - return fmt.Errorf("failed to write decrypted file to %s: %w", path, err) 33 - } 34 - return nil 35 - }
+71
internal/processor/fsutil.go
··· 1 + package processor 2 + 3 + import ( 4 + "fmt" 5 + "io" 6 + "os" 7 + "path/filepath" 8 + 9 + "github.com/aottr/nox/internal/config" 10 + "github.com/aottr/nox/internal/constants" 11 + ) 12 + 13 + type FileProcessorOptions struct { 14 + CreateDir bool 15 + } 16 + 17 + func WriteToFile(data []byte, file config.FileConfig, opts *FileProcessorOptions) error { 18 + path := file.Output 19 + if path == "" { 20 + // Default output filename if none specified, e.g. replace .age with .env 21 + path = filepath.Base(file.Path) 22 + fmt.Println(path) 23 + if filepath.Ext(path) == ".age" { 24 + path = path[:len(path)-4] + ".env" 25 + } 26 + } 27 + if opts.CreateDir { 28 + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 29 + return fmt.Errorf("failed to create directories for %s: %w", path, err) 30 + } 31 + } 32 + 33 + if err := os.WriteFile(path, data, 0600); err != nil { 34 + return fmt.Errorf("failed to write decrypted file to %s: %w", path, err) 35 + } 36 + return nil 37 + } 38 + 39 + // IOWrapper is a generic wrapper for reading/writing files/STDIN/STDOUT 40 + func IOWrapper[T any](input, output string, additional T, process func([]byte, T) ([]byte, error)) error { 41 + var inputBytes []byte 42 + var err error 43 + 44 + if input == constants.StandardInput { 45 + inputBytes, err = io.ReadAll(os.Stdin) 46 + if err != nil { 47 + return err 48 + } 49 + } else { 50 + inputBytes, err = os.ReadFile(input) 51 + if err != nil { 52 + return err 53 + } 54 + } 55 + 56 + outbutBytes, err := process(inputBytes, additional) 57 + if err != nil { 58 + return err 59 + } 60 + 61 + if output == constants.StandardOutput { 62 + if _, err = os.Stdout.Write(outbutBytes); err != nil { 63 + return err 64 + } 65 + } else { 66 + if err = os.WriteFile(output, outbutBytes, 0600); err != nil { 67 + return err 68 + } 69 + } 70 + return nil 71 + }