feat: enhance evobgp with new command-line tools for bundle management, including pull, verify, and apply functionalities. Update go.mod to include necessary dependencies and complete todos in architecture plan for improved observability and deployment practices.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package bundle
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Manifest describes bundle contents for evobgp-node verification.
|
||||
type Manifest struct {
|
||||
RevisionID string `json:"revision_id"`
|
||||
SpeakerID string `json:"speaker_id,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Files []FileEntry `json:"files"`
|
||||
Algorithm string `json:"signature_algorithm"`
|
||||
PublicKeyB64 string `json:"public_key_base64"`
|
||||
}
|
||||
|
||||
// FileEntry is one file inside the bundle archive.
|
||||
type FileEntry struct {
|
||||
Path string `json:"path"`
|
||||
SHA256 string `json:"sha256"`
|
||||
}
|
||||
|
||||
const sigFileName = "bundle.sig"
|
||||
const manifestName = "manifest.json"
|
||||
|
||||
// BuildGzippedTar builds a .tar.gz with manifest.json, bundle.sig (Ed25519 over manifest JSON), and payload files.
|
||||
func BuildGzippedTar(revisionID, speakerID string, fragments map[string]string, priv ed25519.PrivateKey) ([]byte, error) {
|
||||
if len(priv) != ed25519.PrivateKeySize {
|
||||
return nil, fmt.Errorf("bundle: invalid ed25519 private key size")
|
||||
}
|
||||
pub := priv.Public().(ed25519.PublicKey)
|
||||
|
||||
norm := make(map[string]string, len(fragments))
|
||||
for p, content := range fragments {
|
||||
p = path.Clean(strings.TrimPrefix(p, "/"))
|
||||
if p == "." || strings.HasPrefix(p, "..") {
|
||||
return nil, fmt.Errorf("bundle: invalid path %q", p)
|
||||
}
|
||||
norm[p] = content
|
||||
}
|
||||
|
||||
var files []FileEntry
|
||||
var payload [][]byte
|
||||
keys := sortedStringKeys(norm)
|
||||
for _, p := range keys {
|
||||
data := []byte(norm[p])
|
||||
h := sha256.Sum256(data)
|
||||
files = append(files, FileEntry{Path: p, SHA256: fmt.Sprintf("%x", h[:])})
|
||||
payload = append(payload, data)
|
||||
}
|
||||
|
||||
m := Manifest{
|
||||
RevisionID: revisionID,
|
||||
SpeakerID: speakerID,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Files: files,
|
||||
Algorithm: "ed25519",
|
||||
PublicKeyB64: base64.StdEncoding.EncodeToString(pub),
|
||||
}
|
||||
manifestJSON, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig := ed25519.Sign(priv, manifestJSON)
|
||||
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
tw := tar.NewWriter(gw)
|
||||
|
||||
add := func(name string, body []byte) error {
|
||||
hdr := &tar.Header{
|
||||
Name: name,
|
||||
Mode: 0o644,
|
||||
Size: int64(len(body)),
|
||||
ModTime: time.Now().UTC(),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := tw.Write(body)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := add(manifestName, manifestJSON); err != nil {
|
||||
_ = tw.Close()
|
||||
_ = gw.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := add(sigFileName, sig); err != nil {
|
||||
_ = tw.Close()
|
||||
_ = gw.Close()
|
||||
return nil, err
|
||||
}
|
||||
i := 0
|
||||
for _, p := range keys {
|
||||
if err := add(p, payload[i]); err != nil {
|
||||
_ = tw.Close()
|
||||
_ = gw.Close()
|
||||
return nil, err
|
||||
}
|
||||
i++
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
_ = gw.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := gw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func sortedStringKeys(m map[string]string) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user