bastion 0.53.2
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The managed install signs the agent's manifest in Go (faSignManifest): the
|
||||
// agent prints `payload`, the bastion signs it with the key it minted, the
|
||||
// agent records it with `sign --value`. No openssl anywhere — Windows ships
|
||||
// none, and every Windows agent used to stop at `missing-openssl`.
|
||||
|
||||
const fakeAgent = `import base64, sys
|
||||
args = sys.argv[1:]
|
||||
if args[0] == "payload":
|
||||
sys.stdout.buffer.write(open(args[1], "rb").read())
|
||||
elif args[0] == "sign":
|
||||
path = args[1]
|
||||
val = args[args.index("--value") + 1]
|
||||
cert = args[args.index("--cert") + 1]
|
||||
base64.b64decode(val, validate=True)
|
||||
open(path + ".sig", "w").write(val + "\n" + cert)
|
||||
else:
|
||||
sys.exit(2)
|
||||
`
|
||||
|
||||
func findPython(t *testing.T) string {
|
||||
for _, n := range []string{"python3", "python"} {
|
||||
if p, err := exec.LookPath(n); err == nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
t.Skip("no python on PATH")
|
||||
return ""
|
||||
}
|
||||
|
||||
// pythonOnlyPath: a PATH holding a link to python and NOTHING else, so a step
|
||||
// that reached for openssl would fail here the way it does on Windows.
|
||||
func pythonOnlyPath(t *testing.T, py string) string {
|
||||
d := t.TempDir()
|
||||
if err := os.Symlink(py, filepath.Join(d, filepath.Base(py))); err != nil {
|
||||
t.Skip("cannot link python: " + err.Error())
|
||||
}
|
||||
if _, err := exec.LookPath("openssl"); err == nil {
|
||||
old := os.Getenv("PATH")
|
||||
os.Setenv("PATH", d)
|
||||
defer os.Setenv("PATH", old)
|
||||
if _, err := exec.LookPath("openssl"); err == nil {
|
||||
t.Fatal("openssl still resolvable on the python-only PATH")
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func TestFaSignManifestSignsPayloadWithMintedKey(t *testing.T) {
|
||||
py := findPython(t)
|
||||
dir := t.TempDir()
|
||||
cert, key, _, err := faEnsureIdentity(dir, "desk-example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
agent := filepath.Join(dir, "agent.py")
|
||||
if err := os.WriteFile(agent, []byte(fakeAgent), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
staged := filepath.Join(dir, "manifest.yaml")
|
||||
body := []byte("freeagent: 1\nid: t\n")
|
||||
if err := os.WriteFile(staged, body, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// PATH without openssl: the step must not need it.
|
||||
env := []string{"PATH=" + pythonOnlyPath(t, py)}
|
||||
if err := faSignManifest(py, nil, agent, staged, key, cert, env); err != nil {
|
||||
t.Fatalf("faSignManifest: %v", err)
|
||||
}
|
||||
rec, err := os.ReadFile(staged + ".sig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parts := strings.SplitN(string(rec), "\n", 2)
|
||||
if parts[1] != cert {
|
||||
t.Fatalf("recorded cert %q, want %q", parts[1], cert)
|
||||
}
|
||||
sig, err := base64.StdEncoding.DecodeString(parts[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cb, _ := os.ReadFile(cert)
|
||||
blk, _ := pem.Decode(cb)
|
||||
c, err := x509.ParseCertificate(blk.Bytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sum := sha256.Sum256(body)
|
||||
if !ecdsa.VerifyASN1(c.PublicKey.(*ecdsa.PublicKey), sum[:], sig) {
|
||||
t.Fatal("signature does not verify against the minted cert over the payload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFaSignManifestReportsAgentFailure(t *testing.T) {
|
||||
py := findPython(t)
|
||||
dir := t.TempDir()
|
||||
cert, key, _, err := faEnsureIdentity(dir, "desk-example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
agent := filepath.Join(dir, "agent.py")
|
||||
_ = os.WriteFile(agent, []byte("import sys\nsys.stderr.write('bad manifest')\nsys.exit(1)\n"), 0o600)
|
||||
err = faSignManifest(py, nil, agent, filepath.Join(dir, "m.yaml"), key, cert, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "bad manifest") {
|
||||
t.Fatalf("want the agent's own error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// With FA_TEST_AGENT_PYZ pointing at a built agent (bastion_agent/build.py),
|
||||
// the real `payload` / `sign --value` / gate round-trip runs, openssl-free.
|
||||
func TestFaSignManifestWithRealAgent(t *testing.T) {
|
||||
pyz := os.Getenv("FA_TEST_AGENT_PYZ")
|
||||
if pyz == "" {
|
||||
t.Skip("FA_TEST_AGENT_PYZ not set")
|
||||
}
|
||||
py := findPython(t)
|
||||
dir := t.TempDir()
|
||||
cert, key, _, err := faEnsureIdentity(dir, "desk-example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root := filepath.Join(dir, "root")
|
||||
_ = os.MkdirAll(root, 0o700)
|
||||
staged := filepath.Join(dir, "manifest.yaml")
|
||||
text := "freeagent: 1\nid: t\nowner:\n host: desk-example\nscope:\n roots:\n - " + root +
|
||||
"\nmask:\n- read\n- index\nstages:\n- walk\nsweep: manual\nsink:\n catalog: local\n vectors: local\n plaintext: never\n"
|
||||
_ = os.WriteFile(staged, []byte(text), 0o600)
|
||||
env := []string{"PATH=" + pythonOnlyPath(t, py), "FREEAGENT_CA_ROOT=" + cert}
|
||||
if err := faSignManifest(py, nil, pyz, staged, key, cert, env); err != nil {
|
||||
t.Fatalf("faSignManifest: %v", err)
|
||||
}
|
||||
cmd := exec.Command(py, pyz, "sweep", staged, "--out", filepath.Join(dir, "out"), "--once")
|
||||
cmd.Env = env
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("the real agent's gate refused the Go-signed manifest: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFaVersionLess(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
a, b string
|
||||
less bool
|
||||
}{
|
||||
{"0.1.10", "0.1.11", true}, {"0.1.9", "0.1.11", true}, {"0.1.11", "0.1.11", false},
|
||||
{"0.1.12", "0.1.11", false}, {"0.2", "0.1.11", false}, {"1.0.0", "0.1.11", false},
|
||||
{"", "0.1.11", true}, {"dev", "0.1.11", true},
|
||||
} {
|
||||
if got := faVersionLess(c.a, c.b); got != c.less {
|
||||
t.Errorf("faVersionLess(%q, %q) = %v, want %v", c.a, c.b, got, c.less)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user