Compare commits
2 commits
2478f08e5c
...
9d24194637
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d24194637 | ||
|
|
0bef3b5f03 |
|
|
@ -789,10 +789,10 @@ func writeOpenBaoUnsealSecret(path, recoveryIdentityPath, recoveryBundlePath, ag
|
|||
}
|
||||
|
||||
func renderOpenBaoUnsealSecret(material openbao.RecoveryMaterial) ([]byte, error) {
|
||||
if material.RootToken == "" || material.UnsealThreshold < 1 || len(material.UnsealKeysB64) < material.UnsealThreshold {
|
||||
if material.UnsealThreshold < 1 || len(material.UnsealKeysB64) < material.UnsealThreshold {
|
||||
return nil, errors.New("OpenBao recovery material is incomplete")
|
||||
}
|
||||
data := map[string]string{"root-token": material.RootToken}
|
||||
data := make(map[string]string, len(material.UnsealKeysB64))
|
||||
for index, share := range material.UnsealKeysB64 {
|
||||
if share == "" {
|
||||
return nil, errors.New("OpenBao recovery material contains an invalid unseal key")
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ func TestWriteDemocraticCSISecretEncryptsValues(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestWriteOpenBaoUnsealSecretRendersRecoveryMaterial(t *testing.T) {
|
||||
func TestWriteOpenBaoUnsealSecretRendersOnlyUnsealShares(t *testing.T) {
|
||||
originalRead := readOpenBaoRecovery
|
||||
originalWrite := writeGeneratedSOPS
|
||||
t.Cleanup(func() {
|
||||
|
|
@ -410,24 +410,66 @@ func TestWriteOpenBaoUnsealSecretRendersRecoveryMaterial(t *testing.T) {
|
|||
}
|
||||
return openbao.RecoveryMaterial{RootToken: "test-root", UnsealThreshold: 2, UnsealKeysB64: []string{"test-share-1", "test-share-2", "test-share-3"}}, nil
|
||||
}
|
||||
var rendered []byte
|
||||
writeGeneratedSOPS = func(path, ageKeyPath string, plaintext []byte) error {
|
||||
if ageKeyPath != "flux-age-identity" {
|
||||
t.Fatal("OpenBao unseal secret used the wrong Flux age identity")
|
||||
}
|
||||
rendered = append([]byte(nil), plaintext...)
|
||||
return os.WriteFile(path, []byte("sops: {}\n"), 0600)
|
||||
var secret struct {
|
||||
Metadata map[string]string `yaml:"metadata"`
|
||||
StringData map[string]string `yaml:"stringData"`
|
||||
}
|
||||
if err := yaml.Unmarshal(plaintext, &secret); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if secret.Metadata["name"] != "openbao-unseal" || secret.Metadata["namespace"] != "openbao" || len(secret.StringData) != 3 {
|
||||
t.Fatal("OpenBao unseal Secret was not rendered")
|
||||
}
|
||||
if _, ok := secret.StringData["root-token"]; ok {
|
||||
t.Fatal("OpenBao unseal Secret contains a root token")
|
||||
}
|
||||
for _, name := range []string{"unseal-1", "unseal-2", "unseal-3"} {
|
||||
if _, ok := secret.StringData[name]; !ok {
|
||||
t.Fatalf("OpenBao unseal Secret is missing %s", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := writeOpenBaoUnsealSecret(filepath.Join(t.TempDir(), "unseal.sops.yaml"), "recovery-identity", "recovery-bundle", "flux-age-identity"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var secret struct {
|
||||
Metadata map[string]string `yaml:"metadata"`
|
||||
StringData map[string]string `yaml:"stringData"`
|
||||
}
|
||||
|
||||
func TestWriteOpenBaoUnsealSecretEncryptsSharesWithoutRootToken(t *testing.T) {
|
||||
if _, err := exec.LookPath("age-keygen"); err != nil {
|
||||
t.Skip("age-keygen is required for bootstrap encryption")
|
||||
}
|
||||
if err := yaml.Unmarshal(rendered, &secret); err != nil || secret.Metadata["name"] != "openbao-unseal" || secret.Metadata["namespace"] != "openbao" || len(secret.StringData) != 4 || secret.StringData["root-token"] == "" || secret.StringData["unseal-3"] == "" {
|
||||
t.Fatal("OpenBao unseal Secret was not rendered")
|
||||
if _, err := exec.LookPath("sops"); err != nil {
|
||||
t.Skip("sops is required for bootstrap encryption")
|
||||
}
|
||||
originalRead := readOpenBaoRecovery
|
||||
t.Cleanup(func() { readOpenBaoRecovery = originalRead })
|
||||
readOpenBaoRecovery = func(string, string) (openbao.RecoveryMaterial, error) {
|
||||
return openbao.RecoveryMaterial{RootToken: "test-root", UnsealThreshold: 2, UnsealKeysB64: []string{"test-share-1", "test-share-2", "test-share-3"}}, nil
|
||||
}
|
||||
dir := t.TempDir()
|
||||
identity := filepath.Join(dir, "age-key.txt")
|
||||
if err := exec.Command("age-keygen", "-o", identity).Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(dir, "unseal.sops.yaml")
|
||||
if err := writeOpenBaoUnsealSecret(path, "recovery-identity", "recovery-bundle", identity); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
encrypted, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(encrypted), "sops:") {
|
||||
t.Fatal("OpenBao unseal Secret was not SOPS encrypted")
|
||||
}
|
||||
if strings.Contains(string(encrypted), "root-token") {
|
||||
t.Fatal("SOPS-encrypted OpenBao unseal Secret contains a root token")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,17 +22,20 @@ func TestEnsureRecoveryIdentity(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnsealSubmitsAllSharesAndVerifiesResult(t *testing.T) {
|
||||
func TestUnsealUsesPromptedStdinOnly(t *testing.T) {
|
||||
originalExec, originalStatus := execInPod, openBaoStatus
|
||||
t.Cleanup(func() { execInPod, openBaoStatus = originalExec, originalStatus })
|
||||
var shares []string
|
||||
execInPod = func(_ string, input []byte, _ ...string) ([]byte, error) {
|
||||
shares = append(shares, strings.TrimSpace(string(input)))
|
||||
calls := 0
|
||||
execInPod = func(_ string, input []byte, args ...string) ([]byte, error) {
|
||||
if len(input) == 0 || len(args) != 3 || args[0] != "sh" || args[1] != "-ec" || args[2] != "read -r key; bao operator unseal \"$key\" >/dev/null" {
|
||||
t.Fatal("unseal share was not submitted through prompted stdin")
|
||||
}
|
||||
calls++
|
||||
return nil, nil
|
||||
}
|
||||
openBaoStatus = func(string) (status, error) { return status{Initialized: true}, nil }
|
||||
if err := unseal("kubeconfig", RecoveryMaterial{UnsealKeysB64: []string{"share-1", "share-2", "share-3"}, UnsealThreshold: 2}); err != nil || strings.Join(shares, ",") != "share-1,share-2,share-3" {
|
||||
t.Fatalf("unseal = shares:%q err:%v", shares, err)
|
||||
if err := unseal("kubeconfig", RecoveryMaterial{UnsealKeysB64: []string{"share-1", "share-2", "share-3"}, UnsealThreshold: 2}); err != nil || calls != 3 {
|
||||
t.Fatalf("unseal calls:%d err:%v", calls, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue