Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6ecff6377 | ||
|
|
0e5597fcc0 |
@@ -0,0 +1,22 @@
|
||||
package backup
|
||||
|
||||
import "testing"
|
||||
|
||||
// user_mfa holds an encrypted TOTP secret. Absent from this map, verify's live
|
||||
// probe reports "this database stores no ciphertext yet" and the one gate that
|
||||
// catches a wrong encryption key becomes a no-op for MFA secrets.
|
||||
func TestUserMFACiphertextIsMirrored(t *testing.T) {
|
||||
fields, ok := ciphertextFields["user_mfa"]
|
||||
if !ok {
|
||||
t.Fatal("ciphertextFields has no entry for user_mfa")
|
||||
}
|
||||
found := false
|
||||
for _, f := range fields {
|
||||
if f == "totp_secret_enc" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("user_mfa entry %v does not name totp_secret_enc", fields)
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,7 @@ func probe(ctx context.Context, opt VerifyOptions, rep *VerifyReport) error {
|
||||
// secrets - models/secret.go: encrypted_value
|
||||
// auth_providers - models/auth_provider.go: client_secret_enc
|
||||
// console_sessions - models/console_session.go: rdp_user_enc, rdp_pass_enc
|
||||
// user_mfa - models/user_mfa.go: totp_secret_enc
|
||||
//
|
||||
// settings is deliberately absent: it holds no ciphertext at all. The ESO read
|
||||
// token is stored as a SHA-256 hash, which no key opens.
|
||||
@@ -136,6 +137,7 @@ var ciphertextFields = map[string][]string{
|
||||
"secrets": {"encrypted_value"},
|
||||
"auth_providers": {"client_secret_enc"},
|
||||
"console_sessions": {"rdp_user_enc", "rdp_pass_enc"},
|
||||
"user_mfa": {"totp_secret_enc"},
|
||||
}
|
||||
|
||||
func findCiphertext(ctx context.Context, db *mongo.Database, coll string) (string, bool, error) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package pb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A phased update is flagged on the wire; an ordinary one carries no field at
|
||||
// all, so an old server or agent sees exactly the shape it always did.
|
||||
func TestPackageUpdatePhasedOnWire(t *testing.T) {
|
||||
b, err := json.Marshal(PackageUpdate{Name: "libkrb5-3", NewVersion: "1.20", Phased: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(b), `"phased":true`) {
|
||||
t.Fatalf("phased missing: %s", b)
|
||||
}
|
||||
b, _ = json.Marshal(PackageUpdate{Name: "curl", NewVersion: "8"})
|
||||
if strings.Contains(string(b), "phased") {
|
||||
t.Fatalf("an ordinary update must not carry phased: %s", b)
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,10 @@ type PackageUpdate struct {
|
||||
Name string `json:"name"`
|
||||
CurrentVersion string `json:"current_version,omitempty"`
|
||||
NewVersion string `json:"new_version"`
|
||||
// Phased marks an Ubuntu phased update this host is not yet selected for:
|
||||
// apt lists it as upgradable, but an upgrade defers it until the host's
|
||||
// phase comes up. It is pending, not installable, so counts leave it out.
|
||||
Phased bool `json:"phased,omitempty"`
|
||||
}
|
||||
|
||||
type ReportUpdatesRequest struct {
|
||||
|
||||
@@ -35,6 +35,12 @@ type Settings struct {
|
||||
// upgrade. Nil means enabled.
|
||||
LocalLoginEnabled *bool `bson:"local_login_enabled,omitempty" json:"local_login_enabled,omitempty"`
|
||||
|
||||
// RequireMFA forces every password-authenticated member to hold a second
|
||||
// factor. A pointer for the same reason LocalLoginEnabled is: absent must
|
||||
// mean off, and a plain bool read from an old document would lock out an
|
||||
// entire instance at upgrade.
|
||||
RequireMFA *bool `bson:"require_mfa,omitempty" json:"require_mfa,omitempty"`
|
||||
|
||||
// VulnFindingRetentionDays is a pointer for the same reason
|
||||
// WorkflowLogRetentionDays is: absent must mean the default, not zero.
|
||||
// Nil is 90 days, 0 is forever. Only "fixed" findings are ever swept.
|
||||
@@ -62,6 +68,12 @@ func LocalLoginEnabled(s *Settings) bool {
|
||||
return *s.LocalLoginEnabled
|
||||
}
|
||||
|
||||
// RequireMFA reads the MFA policy with its absent-means-off default. Every
|
||||
// caller must go through this rather than dereferencing the field.
|
||||
func RequireMFA(s *Settings) bool {
|
||||
return s != nil && s.RequireMFA != nil && *s.RequireMFA
|
||||
}
|
||||
|
||||
// APITokenMaxDays reads the token lifetime cap with its absent-means-uncapped
|
||||
// default. 0 means no cap. Every caller must go through this rather than
|
||||
// dereferencing the field.
|
||||
|
||||
@@ -127,6 +127,7 @@ message PackageUpdate {
|
||||
string name = 1;
|
||||
string current_version = 2;
|
||||
string new_version = 3;
|
||||
bool phased = 4; // Ubuntu phased update this host is not yet selected for; deferred by apt
|
||||
}
|
||||
|
||||
message ReportUpdatesRequest {
|
||||
|
||||
Reference in New Issue
Block a user