How secrets work in multi-service OSC stacks (and one mistake we helped a customer avoid)
How secrets work in multi-service OSC stacks (and one mistake we helped a customer avoid)
OSC 多服务架构中的密钥管理机制(以及我们帮助客户避免的一个错误)
Last month a customer came to us confused about why their credentials kept leaking between services in their OSC stack. They had put an API key into the parameter store, wired up three service instances, and couldn’t figure out why a service that had no business seeing those credentials could read them anyway. The confusion is understandable. The parameter store UI looks a lot like a .env file. But it isn’t one — and that mental model leads directly to the mistake this customer made. Here’s how the two secret mechanisms in OSC actually work, why they’re different, and how to pick the right one.
上个月,一位客户向我们咨询,困惑于为什么他们的凭据在 OSC 堆栈的不同服务之间不断泄露。他们将一个 API 密钥放入了参数存储(Parameter Store)中,并连接了三个服务实例,却无法理解为什么一个本不该接触这些凭据的服务依然能够读取它们。这种困惑是可以理解的。参数存储的 UI 看起来非常像一个 .env 文件,但它并不是——这种思维模型直接导致了该客户所犯的错误。以下是 OSC 中两种密钥机制的实际工作原理、它们的区别以及如何选择正确的方法。
The parameter store: workspace-wide config with an encryption option
参数存储:具备加密选项的工作区级配置
The parameter store in OSC (backed by app-config-svc, which uses Valkey under the hood) is a key-value store for environment variables that get passed to your service instances and MyApp deployments. You add keys in the UI, and they show up as env vars at runtime. The critical word is shared. The parameter store is scoped to your workspace, not to individual service instances. Any service running in that workspace can read any key in the store.
OSC 中的参数存储(由 app-config-svc 提供支持,底层使用 Valkey)是一个用于环境变量的键值存储,这些变量会被传递给你的服务实例和 MyApp 部署。你在 UI 中添加键,它们就会在运行时显示为环境变量。关键点在于“共享”。参数存储的作用域是你的整个工作区,而不是单个服务实例。在该工作区中运行的任何服务都可以读取存储中的任何键。
OSC now supports marking individual variables as secure. When you flag a variable as secure, the value is encrypted at rest. But to read a secure variable, the caller needs a param store API key. When you create a MyApp, you provide this key and it gets stored as a service secret on that MyApp instance. That keeps the API key itself protected. So the parameter store gives you two modes:
OSC 现在支持将单个变量标记为“安全”。当你将变量标记为安全时,该值会在静态存储时进行加密。但要读取安全变量,调用者需要一个参数存储 API 密钥。当你创建一个 MyApp 时,你会提供此密钥,它会作为服务密钥存储在该 MyApp 实例上。这保护了 API 密钥本身。因此,参数存储提供了两种模式:
- Non-secure variables: plaintext, available to any service in the workspace. Good for feature flags, timeouts, base URLs — configuration you’d be comfortable putting in a docker-compose.yml on a dev machine.
- 非安全变量: 明文存储,工作区内的任何服务均可访问。适用于功能开关、超时设置、基础 URL 等——即那些你愿意放在开发机
docker-compose.yml文件中的配置。 - Secure variables: encrypted at rest, readable only with the param store API key. Better for values that need protection but are genuinely shared across multiple services in the workspace.
- 安全变量: 静态加密,仅能通过参数存储 API 密钥读取。适用于需要保护但确实需要在工作区内多个服务间共享的值。
The key thing both modes share: they are workspace-scoped. Scoping is not per-service. If five services are running in your workspace and they all have (or are given) the param store API key, all five can read all secure variables.
这两种模式的共同点在于:它们都是工作区作用域的。作用域并非针对单个服务。如果你的工作区中运行着五个服务,且它们都拥有(或被授予)参数存储 API 密钥,那么这五个服务都可以读取所有的安全变量。
Service secrets: instance-scoped and encrypted
服务密钥:实例作用域且加密
Service secrets work differently. They are scoped to a specific service instance, encrypted at rest, masked in logs, and audited. When you bind a secret to a service, only that instance can use it. The canonical use case is service-to-service wiring. Say you have a transcoder service and an auth service running in the same workspace. The transcoder needs a bearer token to call the auth service. You create a secret binding on the transcoder, referencing the credential from the auth service. The transcoder gets the token; nothing else in the workspace does. That isolation is the point. If a service instance is compromised or misconfigured, the blast radius of a leaked credential is bounded to that instance, not your entire workspace.
服务密钥的工作方式则不同。它们的作用域限定于特定的服务实例,在静态存储时加密,在日志中脱敏,并受到审计。当你将一个密钥绑定到服务时,只有该实例可以使用它。典型的用例是服务间的连接。假设你在同一个工作区中运行着转码服务和认证服务。转码服务需要一个 Bearer Token 来调用认证服务。你在转码服务上创建一个密钥绑定,引用来自认证服务的凭据。转码服务获得了该令牌,而工作区中的其他任何服务都无法获取。这种隔离正是其核心所在。如果某个服务实例被入侵或配置错误,凭据泄露的影响范围仅限于该实例,而不会波及整个工作区。
The param store API key itself is a good example of this pattern in action: OSC stores it as a service secret on your MyApp instance, not as a plain variable, precisely because it should be scoped to that app.
参数存储 API 密钥本身就是这种模式的一个很好的例子:OSC 将其作为服务密钥存储在你的 MyApp 实例上,而不是作为普通变量,正是因为它应该被限定在该应用的作用域内。
The mistake: wrong tool for the threat model
错误:针对威胁模型使用了错误的工具
Back to the customer. They were running a multi-service media stack: an ingest service, a packaging service, and a third-party CDN integration. They needed the CDN API key in the ingest service. They opened the parameter store, added CDN_API_KEY, and moved on. It worked, so they didn’t think twice about it. The problem: the packaging service and every other service in the workspace also had access to that key. It was workspace-scoped with no restriction on which instance could read it. From their perspective the parameter store looked like a .env — keys in, env vars out — so they treated it like one.
回到那位客户的问题。他们运行着一个多服务媒体堆栈:一个采集服务、一个打包服务和一个第三方 CDN 集成。他们需要在采集服务中使用 CDN API 密钥。他们打开参数存储,添加了 CDN_API_KEY,然后就没再管了。由于功能正常,他们没有多想。问题在于:打包服务以及工作区中的所有其他服务也都能访问该密钥。它是工作区作用域的,没有任何限制来规定哪些实例可以读取它。在他们看来,参数存储就像一个 .env 文件——输入键,输出环境变量——所以他们就把它当成了 .env 来使用。
The fix was to move the CDN key to a service secret scoped to the ingest instance. If they had used a secure parameter store variable instead, the value would be encrypted — but still readable by any service in the workspace with the param store API key. That might be acceptable in some setups, but it wasn’t the right call when only one service needed that credential.
解决方法是将 CDN 密钥迁移到仅限于采集实例的服务密钥中。如果他们改用安全参数存储变量,虽然值会被加密,但任何拥有参数存储 API 密钥的工作区内服务仍然可以读取它。在某些设置中这或许可以接受,但当只有一个服务需要该凭据时,这并不是正确的选择。
The mental model in one picture
一图看懂思维模型
The parameter store sits at the workspace level — every service in the workspace can reach non-secure variables, and secure variables are readable by anyone who has the param store API key. Service secrets live inside a specific instance and never leave it.
参数存储位于工作区级别——工作区中的每个服务都可以访问非安全变量,而安全变量则可被任何拥有参数存储 API 密钥的服务读取。服务密钥则存在于特定实例内部,绝不会离开该实例。
How to decide which to use
如何决定使用哪种方式
The decision comes down to two questions: does this value need to be encrypted, and how tightly do you need to control which services can see it?
决策归结为两个问题:该值是否需要加密?以及你需要多严格地控制哪些服务可以查看它?
| What you’re storing | Where it goes |
|---|---|
| 存储内容 | 存储位置 |
| Feature flags, timeouts, base URLs, non-sensitive config | Parameter store (non-secure) |
| 功能开关、超时、基础 URL、非敏感配置 | 参数存储(非安全) |
| Credentials for a MyApp deployment or agent task | Parameter store (secure variable) |
| MyApp 部署或代理任务的凭据 | 参数存储(安全变量) |
| Shared config that needs encryption, readable by multiple services | Parameter store (secure variable) |
| 需要加密且可被多个服务读取的共享配置 | 参数存储(安全变量) |
| Credentials scoped to one specific service instance | Service secrets |
| 限定于特定服务实例的凭据 | 服务密钥 |
| Credentials passed between two service instances | Service secrets |
| 在两个服务实例间传递的凭据 | 服务密钥 |
| The param store API key itself | Service secret (MyApp stores it automatically) |
| 参数存储 API 密钥本身 | 服务密钥(MyApp 自动存储) |
If a value is consumed by MyApp or an agent task, the secure parameter store is the right fit. If a value needs to be scoped strictly to one service instance, use a service secret — the param store is workspace-wide regardless of the secure flag.
如果一个值是由 MyApp 或代理任务使用的,那么安全参数存储是合适的选择。如果一个值需要严格限定在单个服务实例中,请使用服务密钥——无论是否标记为安全,参数存储始终是工作区级别的。
Try it
立即尝试
If you’re running multi-service stacks on OSC and want to review how your credentials are currently stored, the parameter store and service secrets are both available in the OSC dashboard. If you hit a question the UI doesn’t answer, reach us via the community Slack at slack.osaas.io or use the chat bubble in the web console — one of our human handlers will get back to you. We’d rather help you get it right before there’s a problem than after.
如果你正在 OSC 上运行多服务堆栈,并希望检查当前的凭据存储方式,可以在 OSC 控制面板中找到参数存储和服务密钥。如果你遇到 UI 无法解答的问题,请通过社区 Slack (slack.osaas.io) 联系我们,或使用 Web 控制台中的聊天气泡——我们的工作人员会为你提供帮助。我们更希望在问题发生之前就帮你解决,而不是事后补救。