Embedded Secret Management
Flyte implements an Embedded Secret Manager to provide a lightweight, sidecar-less mechanism for injecting secrets into task pods. Unlike traditional sidecar approaches that run a separate container for the duration of a task, the embedded manager fetches secrets during the Kubernetes admission phase via the Flyte Pod Webhook and injects them directly into the pod specification.
Design Philosophy
The "Embedded" design is built to minimize resource overhead and simplify secret lifecycle management. By performing secret retrieval at pod creation time, Flyte avoids the need for long-running sidecar containers that would otherwise consume CPU and memory throughout the task's execution.
The implementation relies on the EmbeddedSecretManagerInjector class in flyteplugins/go/tasks/pluginmachinery/secret/embedded_secret_manager.go, which coordinates with various backend-specific fetchers to resolve and inject secret values.
Secret Retrieval and Fetchers
The core of the retrieval logic is the SecretFetcher interface, which abstracts the underlying secret store (e.g., AWS Secrets Manager, GCP Secret Manager).
type SecretFetcher interface {
GetSecretValue(ctx context.Context, secretID string) (*SecretValue, error)
}
Flyte provides several implementations of this interface:
- AWSSecretFetcher: Communicates with AWS Secrets Manager.
- GCPSecretFetcher: Communicates with Google Cloud Secret Manager.
- AzureSecretFetcher: Communicates with Azure Key Vault.
- K8sSecretFetcher: Retrieves secrets from Kubernetes
Secretobjects.
When a secret is requested, the SecretValue struct holds the retrieved data as either a string or raw bytes:
type SecretValue struct {
StringValue string
BinaryValue []byte
}
Hierarchical Secret Resolution
The EmbeddedSecretManagerInjector uses a hierarchical lookup strategy to find the most specific secret available. It relies on the project, domain, and organization labels present on the Flyte Pod to construct potential secret IDs.
The lookUpSecret method in embedded_secret_manager.go searches in the following order of priority:
- Project + Domain:
org/domain/project/secret_key - Domain:
org/domain//secret_key - Organization:
org///secret_key
This allows administrators to define default secrets at the organization or domain level while allowing specific projects to override them.
Injection Strategies
Depending on the MountRequirement specified in the secret request, Flyte employs different injection techniques.
Environment Variables
For core.Secret_ENV_VAR, the injector directly modifies the Env field of both the task's Containers and InitContainers. By default, environment variables are prefixed (e.g., _UNION_), but this can be overridden by the secret's configuration.
func (i *EmbeddedSecretManagerInjector) injectAsEnvVar(secret *core.Secret, secretValue string, pod *corev1.Pod) {
valueEnvVarName := i.parentCfg.SecretEnvVarPrefix + strings.ToUpper(secret.GetKey())
if secret.GetEnvVar() != "" {
valueEnvVarName = secret.EnvVar
}
// ... adds to pod.Spec.Containers and pod.Spec.InitContainers
}
File Mounts via Init Container
For core.Secret_FILE, Flyte uses a clever "busybox" init container approach to avoid long-running sidecars. The process works as follows:
- An
emptyDirvolume (memory-backed) is added to the Pod. - A lightweight init container (configured via
FileMountInitContainerConfig) is added. - The secret values are base64-encoded and passed to this init container via a single environment variable named
SECRETS. - The init container runs a shell script that decodes these values and writes them to the shared volume.
The shell script used by the init container is defined in getOrAppendFileMountInitContainer:
fmt.Sprintf(`
printf "%%s" "$%s" \
| awk '/^.+=/ {
i = index($0, "=");
name = substr($0, 0, i - 1);
value = substr($0, i + 1);
output_file = "%s/" name;
print value | "base64 -d > " output_file;
}'
`,
EmbeddedSecretsFileMountInitContainerEnvVariableName,
EmbeddedSecretsFileMountPath)
This ensures that by the time the main task container starts, the secrets are already present as files in /etc/flyte/secrets/.
Configuration
The behavior of the embedded manager is controlled via the EmbeddedSecretManagerConfig struct in flyteplugins/go/tasks/pluginmachinery/secret/config/config.go. Key configuration points include:
- Type: Determines which backend fetcher to initialize (AWS, GCP, etc.).
- FileMountInitContainer: Configures the image (defaulting to
busybox) and resource requirements for the secret-mounting init container. - ImagePullSecrets: If enabled, the manager can also mirror secrets into the pod's namespace to be used as
ImagePullSecrets.
type FileMountInitContainerConfig struct {
Image string `json:"image" pflag:",Specifies init container image to use for mounting secrets as files."`
Resources corev1.ResourceRequirements `json:"resources" pflag:"-,Specifies resource requirements for the init container."`
ContainerName string `json:"containerName" pflag:",Specifies the name of the init container that mounts secrets as files."`
}
Constraints and Considerations
- Label Dependency: The injector requires the Pod to have
organization,project, anddomainlabels. If these are missing, secret resolution will fail. - Binary Secrets in Env Vars: If a binary secret (common in GCP) is requested as an environment variable, Flyte validates that it is a valid UTF-8 string. If it is not, the injection fails, and the user is prompted to mount it as a file instead.
- Memory Usage: Since file-mounted secrets use a memory-backed
emptyDir, very large secrets will consume the Pod's memory quota.