Skip to main content
Version: 8.3

Secrets

A configuration setting that is a secret, such as a password, should be modeled with SecretConfig. It is critical to treat secrets as dynamic values, as they may be stored in an external secret provider and may be rotated at any time. The best practice is to fetch the value of the secret on demand each time it is needed. This approach ensures you always have the latest value and reduces the attack surface by minimizing the amount of time the plaintext secret is held in memory.

How to Reference Secrets Dynamically

  1. Model with SecretConfig: Your configuration class must contain a field of type SecretConfig.

    import com.inductiveautomation.ignition.gateway.secrets.SecretConfig;
    ...
    SecretConfig passwordSecretConfig = config.getPassword();
  2. Create a Secret Instance: Given the SecretConfig and a GatewayContext, create a Secret<?> instance. This instance must be held onto and reused for the lifetime of the managing object, as long as the associated SecretConfig has not changed.

    import com.inductiveautomation.ignition.gateway.model.GatewayContext;
    import com.inductiveautomation.ignition.gateway.secrets.Secret;
    import com.inductiveautomation.ignition.gateway.secrets.SecretConfig;
    ...
    SecretConfig passwordSecretConfig = config.getPassword();
    Secret<?> passwordSecret = Secret.create(gatewayContext, passwordSecretConfig);
  3. Fetch Plaintext On Demand: Grab a fresh Plaintext value from the Secret instance every time the secret is needed, using a try-with-resources block to ensure the plaintext value is immediately cleared from memory.

    import com.inductiveautomation.ignition.gateway.secrets.Plaintext;
    import com.inductiveautomation.ignition.gateway.secrets.SecretException;

    // Inside a method that needs the password...
    String password;
    try (Plaintext plaintext = passwordSecret.getPlaintext()) {
    password = plaintext.getAsString();
    // Use the password here, e.g., service.login(username, password)
    } catch (SecretException e) {
    throw new IllegalStateException("Unable to get plaintext password from secret", e);
    }

Dynamic Secret Manager Example

Below are the recommended class structures for a service manager and its factory that correctly handle dynamic secrets by holding onto the Secret<?> instance and fetching the password on demand.

Service Manager

Below is an example of a custom service manager that will handle dynamic secret fetching.

import com.inductiveautomation.ignition.gateway.secrets.Plaintext;
import com.inductiveautomation.ignition.gateway.secrets.Secret;
import com.inductiveautomation.ignition.gateway.secrets.SecretException;

class FooServiceManager {

final FooService service;
final String username;
final Secret<?> password; // Holds the Secret instance

FooServiceManager(FooService service, String username, Secret<?> password) {
this.service = service;
this.username = username;
this.password = password;
}

String login() {
// Fetches the Plaintext on demand every time login is called
try (Plaintext plaintext = password.getPlaintext()) {
return service.login(username, plaintext.getAsString());
} catch (SecretException e) {
throw new IllegalStateException("Unable to get plaintext password from secret", e);
}
}
}

Service Manager Factory

With the service manager defined in the previous example, define a service manager factory to pass the Secret instance.

import com.inductiveautomation.ignition.gateway.model.GatewayContext;
import com.inductiveautomation.ignition.gateway.secrets.Secret;
import com.inductiveautomation.ignition.gateway.secrets.SecretConfig;

class FooServiceManagerFactory {

final GatewayContext gatewayContext;
final FooService service;

FooServiceManagerFactory(GatewayContext gatewayContext, FooService service) {
this.gatewayContext = gatewayContext;
this.service = service;
}

FooServiceManager create(FooServiceManagerConfig config) {
String username = config.username();
SecretConfig passwordSecretConfig = config.password();
Secret<?> passwordSecret = Secret.create(gatewayContext, passwordSecretConfig);
// Returns the manager with the Secret instance, not the Plaintext
return new FooServiceManager(service, username, passwordSecret);
}
}

Takeaways for Dynamic Secrets

  • Do hold onto and reuse the instance of Secret for the lifetime of the managing object as long as the associated SecretConfig has not changed.
  • Do grab a fresh Plaintext value from the Secret on demand every time it is needed.
  • Do not grab the Plaintext value from the Secret once at the beginning of the lifetime of the managing object and reuse the same value, as the secret may be dynamic.

Keep in mind that a SecretException could be thrown every time you try to grab a fresh Plaintext value from the Secret instance. Design your systems to be resilient to secrets-related failures, such as a network issue communicating with an external SecretProvider.

Third Party Libraries and Static Secrets

There may be situations where you are using a third party library that treats secrets as static configuration values. For example, when a new JDBC connection in the database connections system fails due to an exception, the system is designed to re-create the static configuration properties and will grab a fresh value of the password from the Secret. This allows the system to recover from rotated passwords thanks to existing retry mechanisms.

ResourceReference and SecretConfig

Referenced secrets are instances of the SecretConfig.Referenced type that contain the name of the Secret Provider and the name of the secret in that provider. As the Secret Provider name is a reference to a SecretProvider extension point resource, it is a great candidate for a ResourceReference. Implementing ResourceReferenceDelegate to return ResourceReferences instances for these properties ensures that referenced secrets prevent accidental deletion or renaming of the providers they reference, and are updated in the event of a rename if the user chooses.

You can use `SecretReferenceProperty` API to make this easier to construct:
addReferenceProperty(
"voipGateway.password",
SecretReferenceProperty.<SipNotificationProfileConfig>builder()
.setGetSecretConfigFunction(SipNotificationProfileConfig::getPassword)
.setUpdateSecretConfigFunction(SipNotificationProfileConfig::setPassword)
.build());