This commit introduces the `Enchantrix` library to add support for the `.trix` encrypted file format. The main changes are: - The `matrix` format has been renamed to `tim` (Terminal Isolation Matrix). - The `.tim` format is now a specialized `.trix` file. - A new `decode` command has been added to decode `.trix` and `.tim` files. - The `collect` commands now support the `trix` and `tim` formats. - A `--password` flag has been added to the `collect` commands for encryption. - A `--i-am-in-isolation` flag has been added to the `decode` command for safely decoding `.tim` files. - The decryption functionality is currently disabled due to a bug in the `Enchantrix` library. A follow-up PR will be created to re-enable it.
147 lines
2.8 KiB
Go
147 lines
2.8 KiB
Go
package tim
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"io/fs"
|
|
|
|
"github.com/Snider/Borg/pkg/datanode"
|
|
)
|
|
|
|
var (
|
|
ErrDataNodeRequired = errors.New("datanode is required")
|
|
ErrConfigIsNil = errors.New("config is nil")
|
|
)
|
|
|
|
// TerminalIsolationMatrix represents a runc bundle.
|
|
type TerminalIsolationMatrix struct {
|
|
Config []byte
|
|
RootFS *datanode.DataNode
|
|
}
|
|
|
|
// New creates a new, empty TerminalIsolationMatrix.
|
|
func New() (*TerminalIsolationMatrix, error) {
|
|
// Use the default runc spec as a starting point.
|
|
// This can be customized later.
|
|
spec, err := defaultConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
specBytes, err := json.Marshal(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TerminalIsolationMatrix{
|
|
Config: specBytes,
|
|
RootFS: datanode.New(),
|
|
}, nil
|
|
}
|
|
|
|
// FromDataNode creates a new TerminalIsolationMatrix from a DataNode.
|
|
func FromDataNode(dn *datanode.DataNode) (*TerminalIsolationMatrix, error) {
|
|
if dn == nil {
|
|
return nil, ErrDataNodeRequired
|
|
}
|
|
m, err := New()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.RootFS = dn
|
|
return m, nil
|
|
}
|
|
|
|
// ToTar serializes the TerminalIsolationMatrix to a tarball.
|
|
func (m *TerminalIsolationMatrix) ToTar() ([]byte, error) {
|
|
if m.Config == nil {
|
|
return nil, ErrConfigIsNil
|
|
}
|
|
buf := new(bytes.Buffer)
|
|
tw := tar.NewWriter(buf)
|
|
|
|
// Add the config.json file.
|
|
hdr := &tar.Header{
|
|
Name: "config.json",
|
|
Mode: 0600,
|
|
Size: int64(len(m.Config)),
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := tw.Write(m.Config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add the rootfs directory.
|
|
hdr = &tar.Header{
|
|
Name: "rootfs/",
|
|
Mode: 0755,
|
|
Typeflag: tar.TypeDir,
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add the rootfs files.
|
|
err := m.RootFS.Walk(".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
// If the root directory doesn't exist (i.e. empty datanode), it's not an error.
|
|
if path == "." && errors.Is(err, fs.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
file, err := m.RootFS.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
info, err := file.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hdr := &tar.Header{
|
|
Name: "rootfs/" + path,
|
|
Mode: 0600,
|
|
Size: info.Size(),
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
if _, err := buf.ReadFrom(file); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tw.Write(buf.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := tw.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// ToTrix is not yet implemented.
|
|
// func (m *TerminalIsolationMatrix) ToTrix(password string) ([]byte, error) {
|
|
// return nil, errors.New("not implemented")
|
|
// }
|