Skip to content

Commit 034b93d

Browse files
sn: keyring+verifier
1 parent 70629a0 commit 034b93d

2 files changed

Lines changed: 86 additions & 4 deletions

File tree

pkg/keyring/keyring.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ func InitKeyring(cfg config.KeyringConfig) (sdkkeyring.Keyring, error) {
4242
if err != nil {
4343
return nil, err
4444
}
45+
// Validate non-interactive passphrase sources early so we can emit
46+
// clear errors for misconfigured environment variables or files.
47+
if err := validatePassphraseConfig(cfg, backend); err != nil {
48+
return nil, err
49+
}
4550
// Use the directory as-is, it should already be resolved by the config
4651
dir := cfg.Dir
4752

@@ -112,6 +117,39 @@ func selectPassphrase(cfg config.KeyringConfig) string {
112117
return ""
113118
}
114119

120+
// validatePassphraseConfig ensures that configured non-interactive passphrase
121+
// sources are usable. It does not enforce that a passphrase is provided at all
122+
// (interactive mode is still allowed); it only catches obvious misconfigurations
123+
// such as an empty environment variable or unreadable/empty file.
124+
func validatePassphraseConfig(cfg config.KeyringConfig, backend string) error {
125+
backend = strings.ToLower(backend)
126+
// The "test" backend never uses a passphrase.
127+
if backend == "test" {
128+
return nil
129+
}
130+
131+
// If an environment variable is configured, it must be set and non-empty.
132+
if cfg.PassEnv != "" {
133+
val, ok := os.LookupEnv(cfg.PassEnv)
134+
if !ok || strings.TrimSpace(val) == "" {
135+
return fmt.Errorf("keyring passphrase environment variable %q is not set or is empty", cfg.PassEnv)
136+
}
137+
}
138+
139+
// If a passphrase file is configured, it must be readable and non-empty.
140+
if cfg.PassFile != "" {
141+
b, err := os.ReadFile(cfg.PassFile)
142+
if err != nil {
143+
return fmt.Errorf("failed to read keyring passphrase file %q: %w", cfg.PassFile, err)
144+
}
145+
if strings.TrimSpace(string(b)) == "" {
146+
return fmt.Errorf("keyring passphrase file %q is empty", cfg.PassFile)
147+
}
148+
}
149+
150+
return nil
151+
}
152+
115153
func normaliseBackend(b string) (string, error) {
116154
switch strings.ToLower(b) {
117155
case "file":

supernode/verifier/verifier.go

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,28 @@ func (cv *ConfigVerifier) VerifyConfig(ctx context.Context) (*VerificationResult
5050

5151
func (cv *ConfigVerifier) checkKeyExists(result *VerificationResult) error {
5252
_, err := cv.keyring.Key(cv.config.SupernodeConfig.KeyName)
53-
if err != nil {
54-
result.Valid = false
55-
result.Errors = append(result.Errors, ConfigError{Field: "key_name", Actual: cv.config.SupernodeConfig.KeyName, Message: fmt.Sprintf("Key '%s' not found in keyring", cv.config.SupernodeConfig.KeyName)})
53+
if err == nil {
54+
return nil
5655
}
56+
57+
result.Valid = false
58+
59+
// Provide a more actionable error message that includes keyring backend and
60+
// directory, and preserve the original error for debugging.
61+
msg := fmt.Sprintf(
62+
"failed to load key %q from keyring (backend=%s, dir=%s): %v. "+
63+
"Ensure the key exists and that your keyring passphrase configuration is correct.",
64+
cv.config.SupernodeConfig.KeyName,
65+
cv.config.KeyringConfig.Backend,
66+
cv.config.GetKeyringDir(),
67+
err,
68+
)
69+
70+
result.Errors = append(result.Errors, ConfigError{
71+
Field: "key_name",
72+
Actual: cv.config.SupernodeConfig.KeyName,
73+
Message: msg,
74+
})
5775
return nil
5876
}
5977

@@ -78,9 +96,35 @@ func (cv *ConfigVerifier) checkSupernodeExists(ctx context.Context, result *Veri
7896
sn, err := cv.lumeraClient.SuperNode().GetSupernodeWithLatestAddress(ctx, cv.config.SupernodeConfig.Identity)
7997
if err != nil {
8098
result.Valid = false
81-
result.Errors = append(result.Errors, ConfigError{Field: "registration", Actual: "error", Message: err.Error()})
99+
100+
msg := fmt.Sprintf(
101+
"failed to fetch supernode registration from chain (identity=%s, grpc=%s): %v",
102+
cv.config.SupernodeConfig.Identity,
103+
cv.config.LumeraClientConfig.GRPCAddr,
104+
err,
105+
)
106+
107+
result.Errors = append(result.Errors, ConfigError{
108+
Field: "registration",
109+
Actual: "error",
110+
Message: msg,
111+
})
82112
return nil, err
83113
}
114+
if sn == nil {
115+
result.Valid = false
116+
msg := fmt.Sprintf(
117+
"supernode identity %s is not registered on chain (grpc=%s)",
118+
cv.config.SupernodeConfig.Identity,
119+
cv.config.LumeraClientConfig.GRPCAddr,
120+
)
121+
result.Errors = append(result.Errors, ConfigError{
122+
Field: "registration",
123+
Actual: "not_found",
124+
Message: msg,
125+
})
126+
return nil, nil
127+
}
84128
return sn, nil
85129
}
86130

0 commit comments

Comments
 (0)