83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"git.zaynet.fr/DoGaMa/DoGaMa-serv/internal/agentwire"
|
|
)
|
|
|
|
func (s *service) prepareAssets(plan agentwire.DeploymentPlan) ([]AssetMount, error) {
|
|
approved, err := s.plans.Assets(plan)
|
|
if err != nil || len(approved) == 0 {
|
|
return nil, err
|
|
}
|
|
assetRoot := filepath.Join(filepath.Dir(plan.Mounts[0].HostPath), ".dogama", "assets")
|
|
assetRoot, err = s.paths.Prepare(assetRoot)
|
|
if err != nil {
|
|
return nil, errors.New("asset root is not allowed")
|
|
}
|
|
result := make([]AssetMount, 0, len(approved))
|
|
for _, asset := range approved {
|
|
digest := sha256.Sum256(asset.Content)
|
|
if hex.EncodeToString(digest[:]) != asset.SHA256 {
|
|
return nil, errors.New("approved asset integrity check failed")
|
|
}
|
|
target := filepath.Join(assetRoot, asset.SHA256)
|
|
if err := writeImmutableAsset(target, asset.Content); err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, AssetMount{HostPath: target, ContainerPath: asset.Destination})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func writeImmutableAsset(path string, content []byte) error {
|
|
if info, err := os.Lstat(path); err == nil {
|
|
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
|
|
return errors.New("approved asset path is not a regular file")
|
|
}
|
|
existing, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return errors.New("read approved asset")
|
|
}
|
|
existingDigest, wantedDigest := sha256.Sum256(existing), sha256.Sum256(content)
|
|
if existingDigest != wantedDigest {
|
|
return errors.New("approved asset content conflict")
|
|
}
|
|
return os.Chmod(path, 0o555)
|
|
} else if !errors.Is(err, os.ErrNotExist) {
|
|
return errors.New("inspect approved asset path")
|
|
}
|
|
temporary, err := os.CreateTemp(filepath.Dir(path), ".asset-*")
|
|
if err != nil {
|
|
return fmt.Errorf("create approved asset: %w", err)
|
|
}
|
|
temporaryPath := temporary.Name()
|
|
defer func() { _ = os.Remove(temporaryPath) }()
|
|
// Template entrypoint assets are bind-mounted into images that can run as a
|
|
// non-root image user. The host-side agent owns these files, so the image
|
|
// user must be able to read and execute an approved helper without gaining
|
|
// write access.
|
|
if err = temporary.Chmod(0o555); err == nil {
|
|
_, err = temporary.Write(content)
|
|
}
|
|
if err == nil {
|
|
err = temporary.Sync()
|
|
}
|
|
if closeErr := temporary.Close(); err == nil {
|
|
err = closeErr
|
|
}
|
|
if err != nil {
|
|
return errors.New("write approved asset")
|
|
}
|
|
if err := os.Rename(temporaryPath, path); err != nil {
|
|
return errors.New("publish approved asset")
|
|
}
|
|
return nil
|
|
}
|