Bind Agent execution to task providers and SIP revision
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
package configread
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ExecutionBindingSHA256 binds the exact immutable task and provider bytes
|
||||
// to the SIP revision delivered to an Agent. Length prefixes prevent
|
||||
// concatenation collisions; no credential content is returned or logged.
|
||||
func ExecutionBindingSHA256(taskJSON, providersJSON []byte, sipRevision int64) (string, error) {
|
||||
if !json.Valid(taskJSON) || !json.Valid(providersJSON) || sipRevision <= 0 {
|
||||
return "", errors.New("execution snapshot has invalid JSON or SIP revision")
|
||||
}
|
||||
h := sha256.New()
|
||||
var number [8]byte
|
||||
binary.BigEndian.PutUint64(number[:], uint64(len(taskJSON)))
|
||||
_, _ = h.Write(number[:])
|
||||
_, _ = h.Write(taskJSON)
|
||||
binary.BigEndian.PutUint64(number[:], uint64(len(providersJSON)))
|
||||
_, _ = h.Write(number[:])
|
||||
_, _ = h.Write(providersJSON)
|
||||
binary.BigEndian.PutUint64(number[:], uint64(sipRevision))
|
||||
_, _ = h.Write(number[:])
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package configread
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecutionBindingSHA256CoversTaskProvidersAndSIPRevision(t *testing.T) {
|
||||
task, err := os.ReadFile("../../contracts/local/examples/config-read-task-full.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
providers, err := os.ReadFile("../../contracts/local/examples/config-read-providers.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original, err := ExecutionBindingSHA256(task, providers, 8)
|
||||
if err != nil || len(original) != 64 {
|
||||
t.Fatalf("binding hash: %q %v", original, err)
|
||||
}
|
||||
changedTask := append(append([]byte(nil), task...), ' ')
|
||||
changedProviders := append(append([]byte(nil), providers...), ' ')
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
task []byte
|
||||
providers []byte
|
||||
revision int64
|
||||
}{
|
||||
{"task", changedTask, providers, 8},
|
||||
{"providers", task, changedProviders, 8},
|
||||
{"sip-revision", task, providers, 9},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := ExecutionBindingSHA256(tc.task, tc.providers, tc.revision)
|
||||
if err != nil || got == original {
|
||||
t.Fatalf("snapshot field not bound: %q %v", got, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
task, providers []byte
|
||||
revision int64
|
||||
}{
|
||||
{nil, providers, 8}, {task, nil, 8}, {[]byte(`not-json`), providers, 8}, {task, providers, 0},
|
||||
} {
|
||||
if _, err := ExecutionBindingSHA256(tc.task, tc.providers, tc.revision); err == nil || strings.Contains(err.Error(), "example-only-not-a-real-secret") {
|
||||
t.Fatalf("invalid binding must be rejected without printing provider credentials: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user