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) {
|
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")
|
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 {
|
for index, share := range material.UnsealKeysB64 {
|
||||||
if share == "" {
|
if share == "" {
|
||||||
return nil, errors.New("OpenBao recovery material contains an invalid unseal key")
|
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
|
originalRead := readOpenBaoRecovery
|
||||||
originalWrite := writeGeneratedSOPS
|
originalWrite := writeGeneratedSOPS
|
||||||
t.Cleanup(func() {
|
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
|
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 {
|
writeGeneratedSOPS = func(path, ageKeyPath string, plaintext []byte) error {
|
||||||
if ageKeyPath != "flux-age-identity" {
|
if ageKeyPath != "flux-age-identity" {
|
||||||
t.Fatal("OpenBao unseal secret used the wrong Flux age identity")
|
t.Fatal("OpenBao unseal secret used the wrong Flux age identity")
|
||||||
}
|
}
|
||||||
rendered = append([]byte(nil), plaintext...)
|
var secret struct {
|
||||||
return os.WriteFile(path, []byte("sops: {}\n"), 0600)
|
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 {
|
if err := writeOpenBaoUnsealSecret(filepath.Join(t.TempDir(), "unseal.sops.yaml"), "recovery-identity", "recovery-bundle", "flux-age-identity"); err != nil {
|
||||||
t.Fatal(err)
|
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"] == "" {
|
if _, err := exec.LookPath("sops"); err != nil {
|
||||||
t.Fatal("OpenBao unseal Secret was not rendered")
|
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
|
originalExec, originalStatus := execInPod, openBaoStatus
|
||||||
t.Cleanup(func() { execInPod, openBaoStatus = originalExec, originalStatus })
|
t.Cleanup(func() { execInPod, openBaoStatus = originalExec, originalStatus })
|
||||||
var shares []string
|
calls := 0
|
||||||
execInPod = func(_ string, input []byte, _ ...string) ([]byte, error) {
|
execInPod = func(_ string, input []byte, args ...string) ([]byte, error) {
|
||||||
shares = append(shares, strings.TrimSpace(string(input)))
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
openBaoStatus = func(string) (status, error) { return status{Initialized: true}, 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" {
|
if err := unseal("kubeconfig", RecoveryMaterial{UnsealKeysB64: []string{"share-1", "share-2", "share-3"}, UnsealThreshold: 2}); err != nil || calls != 3 {
|
||||||
t.Fatalf("unseal = shares:%q err:%v", shares, err)
|
t.Fatalf("unseal calls:%d err:%v", calls, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue