# Credential Management System in INFINI Console: Architecture and Security Implementation

> Explore the credential management system in INFINI Console. Learn its architecture and how it securely encrypts and stores sensitive data using derived secret keys, keeping secrets safe from plain text.

- Repository: [INFINI Labs/console](https://github.com/infinilabs/console)
- Tags: architecture
- Published: 2026-03-04

---

**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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/plugin/setup/setup.go) hashes the user-provided secret to generate a 32-byte value:

```go
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`](https://github.com/infinilabs/console/blob/main/core/security/enum/const.go). API endpoints require specific permissions:

- **`system.credential:read`** – Required to view credential metadata and configurations
- **`system.credential:write`** – Required to create, update, or delete credentials

These permissions are enforced in [`modules/security/credential/api/credential.go`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/plugin/setup/setup.go) demonstrates the complete workflow for creating an encrypted credential during initial system setup:

```go
// 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:

```go
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:

```go
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`](https://github.com/infinilabs/console/blob/main/modules/security/credential/api/credential.go) implements CRUD operations with encryption and event triggering:

```go
// 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()` and `Decode()` methods on the `credential.Credential` struct, ensuring passwords never persist in plaintext.
- **Access control** relies on permission constants (`system.credential:read`, `system.credential:write`) enforced at the API layer in [`modules/security/credential/api/credential.go`](https://github.com/infinilabs/console/blob/main/modules/security/credential/api/credential.go).
- **Runtime propagation** uses the `RegisterChangeEvent` mechanism to automatically update dependent services (such as the Elasticsearch client keystore) whenever credentials change.
- **Secret validation** occurs at startup through `validateCredentialSecret` in [`plugin/setup/setup.go`](https://github.com/infinilabs/console/blob/main/plugin/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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/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`](https://github.com/infinilabs/console/blob/main/modules/security/credential/api/credential.go) enforce these permissions through `handler.RequirePermission` during endpoint initialization, ensuring only authorized administrators can access sensitive authentication data.