This document outlines how Tensor9 manages sensitive data (secrets) within your Infrastructure as Code (IaC) to ensure security and support various ownership models for the services your product relies on.
Secret ownership models
Tensor9 distinguishes between two core models for managing secrets, based on who owns the external service or resource:
| Secret Type | Definition | Managed By | Visibility | Example Use Case |
|---|
| Shared Secrets | Used for external services the vendor owns and needs for the product to function. | The vendor (configured on the vendor’s side and “tunneled” into the customer’s appliance). | Customer may be able to view these secrets. | Sentry.io token for vendor-owned monitoring. |
| Customer-Supplied Secrets | Used for external services the customer fully controls and is providing. | The customer (configured within the appliance during setup). | Vendor has no visibility into the actual secret value. | MongoDB Atlas connection string for a customer-owned database. |
Vendors and customers must agree on the ownership of all external services and their associated secrets.
Vendor Recommendation: While Tensor9 supports customer-supplied secrets at
the infrastructure (Terraform) level, we strongly encourage migrating to
a runtime secret model where the vendor’s application exposes an admin setting
page for the customer to provide optional secrets after initial setup. This
avoids requiring a new deployment for every secret change and makes secrets
easier to manage as optional.
Tensor9 supports the following common industry patterns for integrating secrets into your Terraform configuration:
1. Runtime fetching via data source (recommended for external secrets)
This is the most common and recommended pattern for external services. Secrets are stored in an external secret store and retrieved during Terraform execution using a data block.
| Action | Vendor Workflow |
|---|
| Define | Create a data block in your Terraform to reference the secret by name. |
| Reference | Reference the data source’s output (e.g., data.aws_secretsmanager_secret_version.sentry.secret_string) in your compute resource definition. |
| Storage | Secrets are stored in the respective external system (e.g., AWS Secrets Manager, AWS SSM Parameter Store). |
Important constraints:
- Do not use ARNs: Tensor9 does not support referencing secrets by Amazon Resource Name (ARN). Use name-based identifiers instead, as ARNs are absolute and may not be accessible from the customer side of the appliance.
- Parameterization for Shared Secrets: For shared secrets (vendor-supplied), you must “parameterize” the secret path by including the
instance_id variable in the path (e.g., ${var.instance_id}/prod/sentry/token). Tensor9 automatically injects the instance_id variable into every deployment - it uniquely identifies each appliance instance. Using it in your secret paths ensures each customer appliance references an isolated copy of the secret, preventing conflicts when the same origin stack is deployed to multiple customers.
2. Variable injection
Secrets are passed into the Terraform configuration as variables, typically injected via a CI/CD pipeline or .tfvars file.
| Action | Vendor Workflow |
|---|
| Define | Define a variable block with sensitive = true and ephemeral = true. |
| Reference | Reference the variable (e.g., var.db_password) in your resource definition. |
| Storage | The secret is passed directly as a value into the pipeline. |
Important Constraint: Tensor9 requires that injected variables contain the actual secret value, not a reference (like a Secrets Manager secret ID). If you need to pass a reference, use the Runtime Fetching (Data Source) pattern instead.
This pattern is for secrets that are automatically generated and managed by the cloud provider or Terraform itself (e.g., a database master password created by AWS RDS). Since the secret is fully encapsulated within the IaC, no special Tensor9 annotation or transfer logic is required.
Secret annotation and configuration
For the Runtime Fetching and Variable Injection patterns, you must explicitly tell Tensor9 which variables and data sources represent secrets and who owns them using a configuration file (via the tuning document or a separate JSON file).
Without this annotation, Tensor9 defaults all secret-holding data sources/variables to be Vendor-owned (Shared).
Secret lifecycle and rotation
| Secret Type | When Collected | Vendor Access |
|---|
| Shared Secrets | When the vendor executes tofu apply on their compiled stack. | The vendor places the secret in their external secret store; Tensor9 fetches and copies it to the appliance. |
| Customer Secrets | During the customer’s onboarding/setup process via the UI. | The vendor never has access to the secret value. |
Before deployment, Tensor9 performs a pre-flight check to validate that all configured secrets meet the requirements.
Secret rotation
The process for rotating secrets depends on how the secret is referenced in your Terraform:
- If passing by Value: If the secret value is directly embedded in a resource definition (e.g.,
password = var.db_password), an update to the secret will be detected by Terraform, and a normal build and apply (deployment) will update the resource.
- If passing by Reference: If the infrastructure retrieves the secret by reference (e.g., a data source fetching from Secrets Manager), the infrastructure itself will not detect a change to the secret value.
- Vendor Action Required: The vendor must trigger a refresh or a new deployment.
- Application Logic Required: The application consuming the secret must have refresh logic built-in to periodically check for updated values.
For secrets managed via AWS Secrets Manager, Tensor9 supports the use of
rotation Lambda functions. The vendor’s controller will copy the rotation Lambda
function to the appliance side. Crucially, this Lambda must use the ARN passed
into it at runtime and should not rely on hardcoded ARNs or other references
that point to the vendor’s original stack resources.
Secrets by origin stack type
The approach to managing secrets varies by origin stack type. All approaches share the same principle: store secrets in AWS Secrets Manager or SSM Parameter Store, then pass them to your application as environment variables.
Define secrets in AWS Secrets Manager or SSM Parameter Store, then inject them into your compute resources as environment variables.
Defining the secret:
resource "aws_secretsmanager_secret" "db_password" {
name = "${var.instance_id}/prod/db/password"
tags = {
"instance-id" = var.instance_id
}
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = var.db_password
}
Injecting into ECS Fargate:
resource "aws_ecs_task_definition" "app" {
family = "myapp-${var.instance_id}"
container_definitions = jsonencode([
{
name = "app"
image = "myapp:latest"
# Inject secret as environment variable
secrets = [
{
name = "DB_PASSWORD"
valueFrom = aws_secretsmanager_secret.db_password.arn
}
]
}
])
}
Injecting into Lambda:
resource "aws_lambda_function" "api" {
function_name = "myapp-api-${var.instance_id}"
environment {
variables = {
DB_PASSWORD = data.aws_secretsmanager_secret_version.db_password.secret_string
}
}
}
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
}
See Terraform Origin Stacks for complete documentation.
Use CloudFormation dynamic references to fetch secrets from Secrets Manager during stack deployment, then pass them to your resources.
Parameters:
InstanceId:
Type: String
Resources:
# Secret in Secrets Manager
DBPasswordSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub '${InstanceId}/prod/db/password'
SecretString: !Ref DBPassword
# ECS Task Definition with secret injection
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub 'myapp-${InstanceId}'
ContainerDefinitions:
- Name: app
Image: myapp:latest
Secrets:
- Name: DB_PASSWORD
ValueFrom: !Ref DBPasswordSecret
See CloudFormation Origin Stacks for complete documentation.
Docker Compose origin stacks
Define secrets in the tuning document, then reference them as environment variables in your compose file.
docker-compose.yml:
services:
api:
image: myapp/api:latest
environment:
- DB_PASSWORD=${DB_PASSWORD}
- API_KEY=${API_KEY}
tuning.json:
{
"version": "V1",
"secrets": {
"db_password": {
"source": "aws_secretsmanager",
"secretId": "${instance_id}/prod/db/password",
"environmentVariable": "DB_PASSWORD"
},
"api_key": {
"source": "aws_ssm_parameter",
"parameter": "/${instance_id}/prod/api/key",
"environmentVariable": "API_KEY"
}
}
}
Create release with tuning document:
tensor9 stack release create \
-appName my-app \
-customerName acme-corp \
-vendorVersion "1.0.0" \
-tuningDoc tuning.json
See Docker Compose Origin Stacks for complete documentation.
Docker Container origin stacks
Similar to Docker Compose, define secrets in the tuning document:
tuning.json:
{
"version": "V1",
"containerResources": {
"cpu": "2",
"memory": "4Gi"
},
"secrets": {
"db_password": {
"source": "aws_secretsmanager",
"secretId": "${instance_id}/prod/db/password",
"environmentVariable": "DB_PASSWORD"
}
}
}
The secrets are automatically injected as environment variables into your container.
See Docker Container Origin Stacks for complete documentation.
Kubernetes origin stacks
Define secrets in AWS Secrets Manager within your Terraform/CloudFormation wrapper, then reference them in Kubernetes Deployment environment variables:
# Define secret in AWS Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
name = "${var.instance_id}/prod/db/password"
}
# Reference in Kubernetes Deployment
resource "kubernetes_deployment" "app" {
spec {
template {
spec {
container {
env {
name = "DB_PASSWORD"
value_from {
secret_key_ref {
name = aws_secretsmanager_secret.db_password.name
key = "password"
}
}
}
}
}
}
}
}
Avoid using Kubernetes Secrets directly for sensitive data. Use AWS Secrets Manager and inject values as environment variables.
See Kubernetes Origin Stacks for complete documentation.
Application code (all origin stack types)
Regardless of origin stack type, your application code reads secrets from environment variables:
import os
# Read secrets from environment variables
db_password = os.environ['DB_PASSWORD']
api_key = os.environ['API_KEY']
Do not use runtime SDK calls: If your application dynamically fetches secrets using AWS SDK calls (e.g., boto3.client('secretsmanager').get_secret_value()), those calls will NOT be automatically mapped by Tensor9 across different cloud environments. Always pass secrets as environment variables for consistent behavior across all deployment targets (AWS, Google Cloud, DigitalOcean).