Appearance
Using Secrets in Services
This guide explains how to use the SecretValue feature to securely handle sensitive information like API tokens, credentials, and passwords within your services.
What is a Secret?
A Secret is a secure way to pass sensitive information to your service at runtime without exposing it, e.g., to your logs. Unlike regular input parameters that are passed through the Service API request body, secrets are protected by multiple security mechanisms.
When you use a Secret, the platform:
- Securely injects the secret value as an environment variable into your service's runtime environment
- Automatically maps the environment variable to your function parameter based on naming conventions
- Provides a
SecretValueabstraction from theplanqk-commonslibrary that prevents accidental exposure
Security Best Practices
When working with secrets, always follow these security guidelines:
- Never log or print the unwrapped secret value.
- Use secrets only once - the
unwrap()method can only be called once per secret. - Never commit secrets to version control or include them in your code.
- Use environment variables for local testing, not hardcoded values.
How to Use the SecretValue Class
To use a Secret in your service, you declare a parameter of type SecretValue in your run method. The runtime will automatically detect this, load the secret from the corresponding environment variable, and inject a SecretValue object that protects the sensitive data.
The SecretValue Object
The SecretValue object, found in planqk.commons.secret, is a secure container that provides the following features:
unwrap() -> str: Returns the actual secret value. Can only be called once - subsequent calls raise aValueError.is_locked: A property that returnsTrueif the secret has been unwrapped,Falseotherwise.- Automatic redaction: String representations always show
[redacted]orSecretValue([redacted])to prevent accidental exposure in logs. - Environment variable mapping: Automatically loads from environment variables using the pattern
SECRET_{PARAMETER_NAME}(uppercase).
Environment Variable Naming Convention
The platform automatically maps secret parameters to environment variables using this convention:
| Parameter Name | Environment Variable |
|---|---|
api_token | SECRET_API_TOKEN |
ibmToken | SECRET_IBM_TOKEN |
iqm_token | SECRET_IQM_TOKEN |
database_password | SECRET_DATABASE_PASSWORD |
The parameter name is converted to uppercase, and the SECRET_ prefix is added automatically.
Tutorial: Building a Service with Secrets
Let's walk through an example of a service that uses secrets to authenticate with an external API.
1. Initialize a New Project
If you haven't already, create a new service project. You can use the CLI to set up a new service:
bash
planqk init
cd [user_code]
uv venv
source .venv/bin/activate
uv syncFor the rest of this guide, we assume that you created your service in a directory named user_code, with the main code in user_code/src/.
2. Update the run Method
In your program.py, define a run method that accepts a SecretValue parameter. The name of the parameter (e.g., api_token) is important, as it determines which environment variable will be used.
python
# user_code/src/program.py
from planqk.commons.secret import SecretValue
from pydantic import BaseModel
import requests
class InputData(BaseModel):
endpoint: str
def run(data: InputData, api_token: SecretValue) -> dict:
"""
Makes an authenticated API request using a secret token.
"""
# Use the token for authentication
headers = {
"Authorization": f"Bearer {api_token.unwrap()}" # Unwrap the secret value (can only be done once)
}
try:
response = requests.get(data.endpoint, headers=headers)
response.raise_for_status()
return {
"status": "success",
"data": response.json()
}
except requests.RequestException as e:
return {
"status": "error",
"message": str(e)
}In this example, the run method expects a secret to be provided for the api_token parameter. The platform will automatically load this from the SECRET_API_TOKEN environment variable.
3. Local Testing with Secrets
When developing and testing your service locally, you need to provide the secret values through environment variables. There are several ways to do this.
Option 1: Set Environment Variables Directly
The simplest approach is to set the environment variable before running your service:
bash
# Set the secret environment variable
export SECRET_API_TOKEN="your-test-token-here"
# Run your service
python -m srcOption 2: Use a .env File
For better organization, you can create a .env file in your project root:
bash
SECRET_API_TOKEN=your-test-token-hereNever Commit .env Files
Add .env to your .gitignore file to prevent accidentally committing secrets to version control:
# .gitignore
.envThen load the environment variables from the file:
bash
# Load environment variables from .env file
export $(cat .env | xargs)
# Run your service
python -m src4. Using Multiple Secrets
If your service needs to authenticate with multiple external services, you can declare multiple secret parameters:
python
from planqk.commons.secret import SecretValue
from pydantic import BaseModel
import requests
class InputData(BaseModel):
fetch_weather: bool
fetch_stocks: bool
def run(data: InputData, weather_api_key: SecretValue, stock_api_key: SecretValue) -> dict:
"""
Fetches data from multiple APIs using different credentials.
"""
results = {}
if data.fetch_weather:
weather_token = weather_api_key.unwrap()
# Use weather_token for weather API...
results["weather"] = {"status": "success"}
if data.fetch_stocks:
stock_token = stock_api_key.unwrap()
# Use stock_token for stock API...
results["stocks"] = {"status": "success"}
return resultsFor local testing, you would set multiple environment variables:
bash
export SECRET_WEATHER_API_KEY="weather-token"
export SECRET_STOCK_API_KEY="stock-token"
python -m src5. Combining Secrets with Other Input Types
You can combine secrets with regular JSON input and Data Pools in the same service:
python
from typing import Dict, Any
from planqk.commons.secret import SecretValue
from planqk.commons.datapool import DataPool
from pydantic import BaseModel
class InputData(BaseModel):
model_name: str
endpoint: str
def run(
data: InputData,
api_token: SecretValue,
models: DataPool
) -> dict:
"""
Loads a model from a Data Pool and uploads it to an API using secret credentials.
"""
# Unwrap the secret
token = api_token.unwrap()
# Load the model from the Data Pool
model_path = models.list_files()[data.model_name]
with open(model_path, "rb") as f:
model_data = f.read()
# Upload the model using the authenticated API
headers = {"Authorization": f"Bearer {token}"}
# ... upload logic ...
return {"status": "success", "model": data.model_name}For local testing:
bash
# Set the secret
export SECRET_API_TOKEN="your-token"
# Run with both secret and data pool (assuming a datapool directory and files at './input/models'
python -m srcOpenAPI Specification for Secrets
When you generate an OpenAPI specification for a service that uses a SecretValue, the planqk openapi command automatically creates the correct schema for the secret parameter.
For example, given this service:
python
def run(api_token: SecretValue) -> dict:
passThe command will generate the schema for api_token in the following way:
yaml
schema:
type: object
properties:
$secrets:
type: object
properties:
api_token:
type: string
