Setting Up the Secret Service
The Flyte Secret Service provides a gRPC API (using ConnectRPC) that allows administrators to manage secrets stored as Kubernetes Secret objects. This service centralizes secret management, ensuring that Flyte tasks can securely access credentials while maintaining a consistent naming and scoping convention.
In this tutorial, you will configure and deploy the Secret Service, initialize its Kubernetes client, and bind it to a server.
Prerequisites
Before setting up the Secret Service, ensure you have:
- A Kubernetes cluster where Flyte is deployed.
- A service account for the Secret Service with RBAC permissions to
create,update,get,delete, andlistsecrets in the target namespace. - The
flytebinary or a custom runner that imports the Flyte Secret Service packages.
Step 1: Define the Configuration
The Secret Service behavior is controlled by the secret configuration section. You must specify the Kubernetes namespace where secrets will be stored and the server binding details.
Create or update your Flyte configuration (e.g., config.yaml):
secret:
server:
host: "0.0.0.0"
port: 8093
kubernetes:
namespace: "flyte"
clusterName: "flyte-production"
qps: 100
burst: 200
timeout: "30s"
These settings map to the Config struct in secret/config/config.go:
namespace: The single namespace used for all Flyte-managed secrets.clusterName: A logical identifier used for reporting secret status.qpsandburst: Throttling parameters for the Kubernetes API client.
Step 2: Initialize the Kubernetes Client
The Secret Service requires a Kubernetes client to interact with the cluster. Flyte provides a helper function, app.InitKubernetesClient, to initialize this client using the configuration defined in Step 1.
In your application entry point (similar to secret/cmd/main.go), initialize the client:
import (
"context"
"github.com/flyteorg/flyte/v2/flytestdlib/app"
secretconfig "github.com/flyteorg/flyte/v2/secret/config"
)
func initClient(ctx context.Context) (app.K8sClient, error) {
cfg := secretconfig.GetConfig()
k8sClient, _, err := app.InitKubernetesClient(ctx, app.K8sConfig{
KubeConfig: cfg.Kubernetes.KubeConfig,
Namespace: cfg.Kubernetes.Namespace,
QPS: cfg.Kubernetes.QPS,
Burst: cfg.Kubernetes.Burst,
Timeout: cfg.Kubernetes.Timeout,
}, nil)
return k8sClient, err
}
Step 3: Register the Secret Service Handler
The secret.Setup function wires the SecretService implementation to a ConnectRPC handler and registers it with your application's HTTP multiplexer. It also sets up OpenTelemetry interceptors for observability.
As seen in secret/setup.go, the setup process looks like this:
import (
"github.com/flyteorg/flyte/v2/secret"
"github.com/flyteorg/flyte/v2/flytestdlib/app"
)
// Inside your application setup logic
func setupSecretService(ctx context.Context, sc *app.SetupContext) error {
// sc.K8sClient was initialized in Step 2
return secret.Setup(ctx, sc)
}
The Setup function performs the following:
- Instantiates
service.NewSecretService(sc.K8sClient). - Configures OpenTelemetry providers using
otelutils. - Creates a ConnectRPC handler via
secretconnect.NewSecretServiceHandler. - Mounts the handler to
sc.Mux.
Step 4: Run the Service
The Secret Service can be run as a standalone binary. The main entry point in secret/cmd/main.go uses the flytestdlib/app framework to manage the lifecycle.
func main() {
a := &app.App{
Name: "secret-service",
Short: "Secret Service for Flyte",
Setup: func(ctx context.Context, sc *app.SetupContext) error {
cfg := secretconfig.GetConfig()
sc.Host = cfg.Server.Host
sc.Port = cfg.Server.Port
k8sClient, _, err := app.InitKubernetesClient(ctx, app.K8sConfig{
KubeConfig: cfg.Kubernetes.KubeConfig,
Namespace: cfg.Kubernetes.Namespace,
QPS: cfg.Kubernetes.QPS,
Burst: cfg.Kubernetes.Burst,
Timeout: cfg.Kubernetes.Timeout,
}, nil)
if err != nil {
return err
}
sc.K8sClient = k8sClient
return secret.Setup(ctx, sc)
},
}
if err := a.Run(); err != nil {
panic(err)
}
}
Once running, the service listens on the configured port (default 8093) and provides endpoints for CreateSecret, GetSecret, UpdateSecret, DeleteSecret, and ListSecrets.
Understanding Secret Mapping
When you create a secret via the API, the Secret Service maps the Flyte SecretIdentifier (Project, Domain, Name) to a Kubernetes Secret.
Naming Convention
The service uses two levels of encoding found in flyteplugins/go/tasks/pluginmachinery/secret/:
- Data Key: The full identifier is encoded into a string using
flytesecret.EncodeSecretName. This string is used as the key inside the Kubernetes Secret'sdatamap. - Secret Name: Because Kubernetes Secret names must be DNS-compliant (no underscores), the service hashes the encoded name using MD5 via
flytesecret.EncodeK8sSecretName.
For example, a secret for project my-project and domain development with the name api-key will result in:
- K8s Secret Name: An MD5 hash of the encoded string.
- K8s Secret Key: The encoded string (e.g.,
_u_org_flyte_domain_development_project_my-project_key_api-key).
Scoping and Labels
The service enforces specific validation rules in secret/service/secret_service.go:
- Project Scope: If you provide a
project, you must also provide adomain. - Labels: Every created secret is tagged with
app.flyte.org/managed=true. This allows theListSecretsoperation to filter only those secrets managed by Flyte.
Next Steps
- RBAC Configuration: Ensure the service's Pod has a RoleBinding allowing it to manage secrets in the
flytenamespace. - Client Integration: Use the
SecretServiceClientin your Flyte plugins or custom tools to programmatically manage credentials. - Task Consumption: Flyte tasks consume these secrets via the
K8sSecretInjector, which uses the same encoding logic to mount the secrets as environment variables or files.