Credential Management System in INFINI Console: Architecture and Security Implementation
The credential management system in INFINI Console is a dedicated service that encrypts, stores, and propagates authentication secrets using a derived secret key, ensuring sensitive data never persists in plain text.
The INFINI Console relies on a robust credential management system to handle sensitive authentication data across distributed Elasticsearch clusters and external services. This system provides a centralized approach to securing secrets such as database passwords, API keys, and SMTP credentials through encryption at rest and strict access controls implemented throughout the platform.
Core Architecture of the Credential Management System
The credential management system operates through five integrated components that handle the complete lifecycle of sensitive data, from initial creation to runtime propagation.
Credential Record Structure
The credential.Credential struct from infini.sh/framework/core/credential serves as the primary data model. Each record contains a name, type (such as BasicAuth), tags, timestamps, and a cryptographic payload. The payload stores usernames and passwords in a structured map, while a separate secret field manages the encryption key reference.
Keystore and Secret Key Management
The system relies on infini.sh/framework/core/keystore to persist the master credential secret. The keystore stores the credential.SecretKey value, which serves as the root of trust for all encryption operations. When the system initializes, it retrieves this secret via keystore.GetValue(credential.SecretKey) and uses it to derive encryption keys for individual credential payloads.
Change-Event Propagation System
The credential.RegisterChangeEvent mechanism allows modules to react to credential modifications in real time. When a credential is created, updated, or deleted, the system triggers registered callbacks. For example, the setup module registers a handler that automatically copies updated passwords into the runtime keystore as SYSTEM_CLUSTER_PASS, ensuring dependent services immediately use the latest credentials without manual intervention.
REST API and Permission Model
The API layer in modules/security/credential/api/credential.go exposes CRUD endpoints for credential management. Access is restricted through permission constants defined in core/security/enum/const.go, specifically system.credential:read and system.credential:write. The API handlers enforce these permissions using handler.RequirePermission during initialization.
Security Mechanisms: How Sensitive Data Is Protected
The credential management system implements multiple layers of security to ensure sensitive data remains confidential both at rest and during runtime operations.
Secret Key Derivation and Validation
During initial setup, the system validates or creates the master credential secret using MD5-based derivation. The validateCredentialSecret function in plugin/setup/setup.go hashes the user-provided secret to generate a 32-byte value:
func validateCredentialSecret(secret string) (bool, error) {
rkey, err := keystore.GetValue(credential.SecretKey)
if err != nil && err != keystore2.ErrKeyDoesntExists {
return false, err
}
// Derive a 32-byte hash from the user-provided secret
h := md5.New()
h.Write([]byte(secret))
derived := make([]byte, 32)
hex.Encode(derived, h.Sum(nil))
// If a secret already exists, compare the hashes
if err == nil {
if bytes.Compare(rkey, derived) != 0 {
return true, fmt.Errorf("invalid credential secret")
}
return true, nil
}
// No secret stored yet – create temporary credential
return false, nil
}
This derived secret never appears in application logs and persists only within the encrypted keystore.
Payload Encryption and Decryption
When saving credentials, the Encode() method encrypts the payload using the derived secret key. The encrypted blob is then persisted to the underlying Elasticsearch index. Conversely, Decode() decrypts the payload when the system needs to retrieve plaintext credentials. This ensures that usernames and passwords never touch disk in unencrypted form.
Access Control and Permission Enforcement
The system enforces strict role-based access control through the permission model defined in core/security/enum/const.go. API endpoints require specific permissions:
system.credential:read– Required to view credential metadata and configurationssystem.credential:write– Required to create, update, or delete credentials
These permissions are enforced in modules/security/credential/api/credential.go through the handler initialization process.
Implementation Examples
Creating a System Credential
The createCred function in plugin/setup/setup.go demonstrates the complete workflow for creating an encrypted credential during initial system setup:
// createCred creates a BasicAuth credential and stores it encrypted.
func createCred(name, username, password string) string {
cred := credential.Credential{
Name: name,
Type: credential.BasicAuth,
Tags: []string{"infini", "system"},
Payload: map[string]interface{}{
"basic_auth": map[string]interface{}{
"username": username,
"password": password,
},
},
}
cred.ID = util.GetUUID()
// Encrypt the payload using the secret stored in the keystore.
if err := cred.Encode(); err != nil { panic(err) }
now := time.Now()
cred.Created = &now
cred.Updated = &now
// Persist the credential.
if err := orm.Save(nil, &cred); err != nil { panic(err) }
return cred.ID
}
Validating the Credential Secret
The setup process validates the master secret against the stored hash to prevent unauthorized access:
func validateCredentialSecret(secret string) (bool, error) {
rkey, err := keystore.GetValue(credential.SecretKey)
if err != nil && err != keystore2.ErrKeyDoesntExists {
return false, err
}
// Derive a 32-byte hash from the user-provided secret
h := md5.New()
h.Write([]byte(secret))
derived := make([]byte, 32)
hex.Encode(derived, h.Sum(nil))
// If a secret already exists, compare the hashes
if err == nil {
if bytes.Compare(rkey, derived) != 0 {
return true, fmt.Errorf("invalid credential secret")
}
return true, nil
}
return false, nil
}
Handling Credential Changes
The setup module registers a change event handler to propagate updated passwords to the runtime keystore:
func (module *Module) Start() error {
// Register a global listener for any credential change.
credential.RegisterChangeEvent(func(cred *credential.Credential) {
if cred == nil { return }
// Only act when the system cluster's credential is updated.
sysID := global.MustLookupString(elastic.GlobalSystemElasticsearchID)
cfg := elastic.GetConfig(sysID)
if cfg.CredentialID != cred.ID { return }
// Decode the stored BasicAuth and push the password into the keystore.
if v, err := cred.Decode(); err == nil {
if basicAuth, ok := v.(model.BasicAuth); ok {
keystore.SetValue("SYSTEM_CLUSTER_PASS", []byte(basicAuth.Password.Get()))
}
}
})
return nil
}
REST API Operations
The credential API handler in modules/security/credential/api/credential.go implements CRUD operations with encryption and event triggering:
// POST /credential – create
func (h *APIHandler) createCredential(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
cred := credential.Credential{}
if err := h.DecodeJSON(req, &cred); err != nil {
// handle error
}
if err := cred.Validate(); err != nil {
// handle error
}
cred.ID = util.GetUUID()
if err := cred.Encode(); err != nil {
// handle error
}
orm.Create(&orm.Context{Refresh: "wait_for"}, &cred)
h.WriteCreatedOKJSON(w, cred.ID)
}
// PUT /credential/:id – update
func (h *APIHandler) updateCredential(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
// Load existing, apply changes, re-encode if needed
orm.Update(&orm.Context{Refresh: "wait_for"}, &obj)
// Notify other components.
credential.TriggerChangeEvent(&obj)
}
Summary
- The credential management system in INFINI Console provides centralized encryption and storage for authentication secrets using a master key derived from a user-supplied credential secret.
- Encryption at rest is enforced through the
Encode()andDecode()methods on thecredential.Credentialstruct, ensuring passwords never persist in plaintext. - Access control relies on permission constants (
system.credential:read,system.credential:write) enforced at the API layer inmodules/security/credential/api/credential.go. - Runtime propagation uses the
RegisterChangeEventmechanism to automatically update dependent services (such as the Elasticsearch client keystore) whenever credentials change. - Secret validation occurs at startup through
validateCredentialSecretinplugin/setup/setup.go, preventing the use of stale or mismatched encryption keys.
Frequently Asked Questions
How does the credential management system encrypt sensitive data?
The system uses a master credential secret stored in the keystore to derive an encryption key. When saving a credential, the Encode() method encrypts the payload (containing usernames and passwords) using this derived key before persisting to the Elasticsearch index. The Decode() method reverses this process when the system needs to retrieve plaintext credentials, ensuring sensitive data never touches disk unencrypted.
What happens when a credential is updated through the API?
When a credential is modified via the REST API endpoints in modules/security/credential/api/credential.go, the system first validates the input, re-encodes the payload if the secret changed, and persists the update. Immediately after the database update, the handler calls credential.TriggerChangeEvent(), which notifies all registered listeners. For example, the setup module's registered handler automatically updates the SYSTEM_CLUSTER_PASS value in the keystore, ensuring the Elasticsearch client immediately uses the new password without requiring a restart.
Where is the master credential secret stored and how is it protected?
The master credential secret is stored in the framework's keystore (infini.sh/framework/core/keystore), which persists data encrypted on disk. During initial setup, the validateCredentialSecret function in plugin/setup/setup.go hashes the user-provided secret using MD5 and stores the 32-byte derived value. This secret never appears in application logs, and the system validates it against existing credentials on every startup to detect mismatches that would indicate tampering or configuration errors.
Which permissions are required to manage credentials?
Access to credential operations is restricted through role-based permissions defined in core/security/enum/const.go. Users must possess system.credential:read to view credential metadata and configurations, and system.credential:write to create, update, or delete credentials. The API handlers in modules/security/credential/api/credential.go enforce these permissions through handler.RequirePermission during endpoint initialization, ensuring only authorized administrators can access sensitive authentication data.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →