feat: publish Tend v0.2 preview source
Publish the reviewed allowlisted snapshot whose exact binary completed maintenance deployment, rollback, and reactivation exercises for Gamertan and Sandwich Hime. Private-Source-Commit: 4d7094c8b7c61991bfb67b11fc1558724c874eb2 Private-Source-Tree: 54a2f74804f7acddf3755d7d4da5b97f5fc28381 AI-Assistance: OpenAI Codex assisted implementation, testing, security review, and release verification. Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var targetPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.-]*@[A-Za-z0-9][A-Za-z0-9.-]*$`)
|
||||
|
||||
type PushOptions struct {
|
||||
Target string
|
||||
Port int
|
||||
KnownHosts string
|
||||
Identity string
|
||||
Service string
|
||||
Artifact string
|
||||
SHA256 string
|
||||
ApprovedSHA256 string
|
||||
Activate bool
|
||||
}
|
||||
|
||||
type SSHRunner interface {
|
||||
Run(context.Context, string, []string, io.Reader) ([]byte, error)
|
||||
}
|
||||
|
||||
type ExecSSHRunner struct{}
|
||||
|
||||
func (ExecSSHRunner) Run(ctx context.Context, name string, args []string, stdin io.Reader) ([]byte, error) {
|
||||
command := exec.CommandContext(ctx, name, args...)
|
||||
command.Stdin = stdin
|
||||
var output, diagnostic boundedBuffer
|
||||
command.Stdout = &output
|
||||
command.Stderr = &diagnostic
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
return output.Bytes(), fmt.Errorf("ssh failed: %w: %s", err, strings.TrimSpace(diagnostic.String()))
|
||||
}
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
|
||||
type boundedBuffer struct{ bytes.Buffer }
|
||||
|
||||
func (b *boundedBuffer) Write(value []byte) (int, error) {
|
||||
written := len(value)
|
||||
remaining := (1 << 20) - b.Len()
|
||||
if remaining > 0 {
|
||||
if len(value) > remaining {
|
||||
value = value[:remaining]
|
||||
}
|
||||
_, _ = b.Buffer.Write(value)
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func Push(ctx context.Context, runner SSHRunner, options PushOptions) (json.RawMessage, error) {
|
||||
if !targetPattern.MatchString(options.Target) || strings.HasPrefix(options.Target, "-") {
|
||||
return nil, errors.New("target must be user@host without shell syntax")
|
||||
}
|
||||
if options.Port < 1 || options.Port > 65535 {
|
||||
return nil, errors.New("SSH port is invalid")
|
||||
}
|
||||
if err := safeClientFile(options.KnownHosts, false); err != nil {
|
||||
return nil, fmt.Errorf("known-hosts file: %w", err)
|
||||
}
|
||||
if options.Identity != "" {
|
||||
if err := safeClientFile(options.Identity, true); err != nil {
|
||||
return nil, fmt.Errorf("identity file: %w", err)
|
||||
}
|
||||
}
|
||||
if !filepath.IsAbs(options.Artifact) || filepath.Clean(options.Artifact) != options.Artifact || strings.ContainsAny(options.Artifact, "\x00\r\n\t") {
|
||||
return nil, errors.New("artifact path must be clean and absolute")
|
||||
}
|
||||
artifactInfo, err := os.Lstat(options.Artifact)
|
||||
if err != nil || !artifactInfo.Mode().IsRegular() || artifactInfo.Mode()&os.ModeSymlink != 0 || artifactInfo.Size() <= 0 || artifactInfo.Size() > MaxArtifactBytes {
|
||||
return nil, errors.New("artifact must be a bounded regular non-symlink file")
|
||||
}
|
||||
artifact, err := os.Open(options.Artifact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer artifact.Close()
|
||||
info, err := artifact.Stat()
|
||||
if err != nil || !info.Mode().IsRegular() || !os.SameFile(artifactInfo, info) {
|
||||
return nil, errors.New("artifact must be a bounded regular file")
|
||||
}
|
||||
hash := sha256.New()
|
||||
if _, err = io.Copy(hash, artifact); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actual := hex.EncodeToString(hash.Sum(nil))
|
||||
if actual != options.SHA256 || options.SHA256 != options.ApprovedSHA256 {
|
||||
return nil, errors.New("artifact digest was not explicitly approved")
|
||||
}
|
||||
if _, err = artifact.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header := Header{Protocol: Protocol, Service: options.Service, ArtifactName: filepath.Base(options.Artifact), Size: info.Size(), SHA256: options.SHA256, ApprovedSHA256: options.ApprovedSHA256, Activate: options.Activate}
|
||||
prefix, err := Prefix(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := []string{"-F", os.DevNull, "-T", "-p", strconv.Itoa(options.Port), "-o", "BatchMode=yes", "-o", "ClearAllForwardings=yes", "-o", "ExitOnForwardFailure=yes", "-o", "ForwardAgent=no", "-o", "IdentitiesOnly=yes", "-o", "LogLevel=ERROR", "-o", "PermitLocalCommand=no", "-o", "ProxyCommand=none", "-o", "RequestTTY=no", "-o", "StrictHostKeyChecking=yes", "-o", "UserKnownHostsFile=" + options.KnownHosts}
|
||||
if options.Identity != "" {
|
||||
args = append(args, "-i", options.Identity)
|
||||
}
|
||||
args = append(args, options.Target, Protocol)
|
||||
output, err := runner.Run(ctx, "ssh", args, io.MultiReader(bytes.NewReader(prefix), artifact))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !json.Valid(output) {
|
||||
return nil, errors.New("receiver returned invalid JSON")
|
||||
}
|
||||
return json.RawMessage(output), nil
|
||||
}
|
||||
|
||||
func safeClientFile(path string, private bool) error {
|
||||
if !filepath.IsAbs(path) || filepath.Clean(path) != path || strings.ContainsAny(path, "\x00\r\n\t") {
|
||||
return errors.New("path must be clean and absolute")
|
||||
}
|
||||
parent, err := os.Lstat(filepath.Dir(path))
|
||||
if err != nil || !parent.IsDir() || parent.Mode()&os.ModeSymlink != 0 || parent.Mode().Perm()&0o022 != 0 {
|
||||
return errors.New("parent must be a real directory not writable by group or others")
|
||||
}
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
|
||||
return errors.New("must be a regular non-symlink file")
|
||||
}
|
||||
if info.Mode().Perm()&0o022 != 0 {
|
||||
return errors.New("must not be group- or world-writable")
|
||||
}
|
||||
if private && info.Mode().Perm()&0o077 != 0 {
|
||||
return errors.New("must not be accessible by group or others")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user