Secret Service gRPC API
When your Flyte tasks require sensitive credentials like API keys or database passwords, the Secret Service provides a programmatic gRPC interface to manage these secrets as Kubernetes resources. This service ensures that secrets are stored securely and are discoverable by the Flyte executor's secret fetcher during task execution.
Managing Secrets via Connect API
The Secret Service is implemented using the Connect protocol, providing a modern gRPC-compatible interface. You interact with it using the SecretService class (defined in secret/service/secret_service.go), which handles CRUD operations and listing.
Creating and Retrieving Secrets
To store a secret, you define a SecretIdentifier and a SecretSpec. The identifier specifies the scope and name, while the spec contains the actual value (either string or binary).
import (
"context"
"connectrpc.com/connect"
"github.com/flyteorg/flyte/v2/gen/go/flyteidl2/secret"
)
func CreateMySecret(ctx context.Context, client secretconnect.SecretServiceClient) error {
id := &secret.SecretIdentifier{
Project: "flytesnacks",
Domain: "development",
Name: "my-api-key",
}
_, err := client.CreateSecret(ctx, connect.NewRequest(&secret.CreateSecretRequest{
Id: id,
SecretSpec: &secret.SecretSpec{
Value: &secret.SecretSpec_BinaryValue{BinaryValue: []byte("super-secret-token")},
},
}))
return err
}
Internally, CreateSecret calls buildK8sSecret to transform the request into a Kubernetes corev1.Secret. The service uses a single Kubernetes namespace, configured via secret.kubernetes.namespace, to house all managed secrets.
Secret Scoping and Validation
Flyte supports a hierarchical scoping mechanism for secrets. This allows you to define secrets that are available globally, restricted to a specific domain, or isolated to a specific project within a domain.
The SecretService enforces the following scoping rules in its validateScope function:
| Scope | Project | Domain | Description |
|---|---|---|---|
| Global | Empty | Empty | Available across the entire Flyte installation. |
| Domain | Empty | Set | Available to all projects within the specified domain (e.g., production). |
| Project+Domain | Set | Set | Restricted to a specific project and domain combination. |
Note: You cannot create a project-scoped secret without also specifying a domain. If you attempt to pass a Project while leaving the Domain empty, the service returns an InvalidArgument error:
// From secret/service/secret_service.go
func validateScope(domain, project string) error {
if domain == "" && project != "" {
return fmt.Errorf("project-scoped secrets must also specify a domain, got project=%q domain=%q", project, domain)
}
return nil
}
Internal Kubernetes Mapping
The Secret Service does not store secrets in a database; instead, it maps them directly to Kubernetes Secret resources. To prevent naming collisions across different scopes and to ensure compatibility with the Flyte executor, it uses a specific encoding and hashing strategy.
Name Encoding and Hashing
When you create a secret, the service generates two distinct strings using the flyteplugins secret machinery:
- Encoded Name: A string representing the full identity of the secret, formatted as
flyte:<domain>:<project>:<name>. The organization is hardcoded toflyteto maintain compatibility with Flyte OSS v2. - Kubernetes Resource Name: A hashed version of the encoded name that complies with Kubernetes resource naming restrictions.
In secret/service/secret_service.go, the getK8sSecretName function handles this transformation:
encoded := flytesecret.EncodeSecretName(defaultOrganization, id.GetDomain(), id.GetProject(), id.GetName())
k8sSecretName := flytesecret.EncodeK8sSecretName(encoded)
The encoded name is used as the key inside the Kubernetes Secret's Data map, while the k8sSecretName becomes the actual name of the Kubernetes resource.
Label-Based Filtering
The service applies metadata labels to every Kubernetes Secret it creates. These labels allow ListSecrets to perform efficient filtering without retrieving every secret in the namespace.
app.flyte.org/managed: Always set totrue.app.flyte.org/project: Set if the secret is project-scoped.app.flyte.org/domain: Set if the secret is domain-scoped.
When you call ListSecrets with a specific domain, the service uses a client.MatchingLabels selector to find matching resources:
// From secret/service/secret_service.go:222
matchLabels := client.MatchingLabels{managedSecretLabelKey: managedSecretLabelValue}
if reqDomain != "" {
matchLabels[domainLabelKey] = reqDomain
}
Configuration
The behavior of the Secret Service is controlled by the secret configuration block. Key settings include:
secret.server.port: The port the gRPC/Connect server listens on (default:8093).secret.kubernetes.namespace: The Kubernetes namespace where secrets are stored (default:flyte).secret.kubernetes.clusterName: The logical name of the cluster, used when reporting secret status inGetSecretresponses.
Implementation Details and Constraints
- Single Key Restriction: The
ListSecretsimplementation expects each managed Kubernetes Secret to contain exactly one data key (the encoded secret ID). If a secret in the namespace contains multiple keys, it is logged as an error and skipped during listing. - Update Concurrency:
UpdateSecretperforms a "get-then-update" flow. It retrieves the existing Kubernetes Secret to obtain theResourceVersion, ensuring that updates do not accidentally overwrite concurrent changes. - Status Reporting: The
GetSecretresponse includes aSecretStatus. In the current implementation, this is hardcoded toFULLY_PRESENTif the secret exists in the local cluster, as the service operates within a single-cluster context.