Apache License 2.0

技术解读

以下内容由 GitRead 中文社区 编辑团队基于项目 README 与官方资料整理, 结合中文开发者的常见使用场景补充技术选型建议。具体以项目官方文档为准。

Teller:把密钥从“到处找”变成“随手取”

项目背景与痛点

云原生开发者的日常工作中,密钥信息分散在各个地方:本地开发需要 .env,CI 需要注入环境变量,K8s 里是 Secret,生产环境使用 Vault,测试库的密码放在 AWS Secrets Manager 中。每条链路都有各自的凭证获取方式。为了验证一个配置,开发者往往要在终端、浏览器控制台和多个 CLI 之间来回切换。

这种摩擦的成本比想象中大。问题不在于“拿不到”,而在于“拿得太麻烦”。于是大量密钥被顺手复制进本地 .env、聊天记录甚至 Slack 收藏夹,安全边界就是这样一点点被侵蚀的。

teller 的出发点很朴素:与其再造一个密钥仓库,不如做一个密钥的统一读取前端。它不存储任何秘密,只负责把配置里声明的、散落在各个后端里的键值拉回来,再以开发者需要的形式交付:终端输出、环境变量、文件,或者直接注入子进程。这是一次干净的关注点分离:密钥的“存放”交给专业后端,密钥的“使用体验”交给 teller。

核心技术揭秘

teller 是用 Go 编写的单二进制 CLI,由 .teller.yml 驱动,执行链路可以抽象为“配置解析 → Provider 聚合 → 统一映射 → 多路输出”。

graph TD A[.teller.yml 配置] --> B[配置解析与校验] B --> C[Provider Registry 注册表] subgraph "后端 Provider 实现" D1[HashiCorp Vault] D2[AWS Secrets Manager] D3[GCP Secret Manager] D4[Azure Key Vault] D5[Dotenv / Consul / Etcd] end C --> D1 C --> D2 C --> D3 C --> D4 C --> D5 D1 --> E[统一 Secrets 映射] D2 --> E D3 --> E D4 --> E D5 --> E E --> F{"输出方式?"} F -->|"stdout"| G[终端明文或脱敏输出] F -->|"env"| H[注入子进程环境变量] F -->|"file"| I[写入 dotenv 文件] F -->|"template"| J[Go template 渲染配置]

关键设计有三层。

第一层是配置驱动。 .teller.yml 中的 providers 数组描述“从哪个后端、以什么身份、取哪些键”:kind 是后端唯一标识,opts 承载连接参数,keys 声明读取目标;export 段则决定产物形态(env / dotenv / json / yaml)、输出位置与脱敏策略。配置文件可以纳入 Git,因为它只含“寻址信息”而不含密文,这是一个很干净的约定。

第二层是 Provider 抽象与注册表。 每个后端实现同一组接口(列键、取值、连通性探测),启动时按 kind 注册进 Registry,运行时依据配置实例化。这是典型的策略模式加插件化设计:新增一个后端只需实现接口并注册,完全不用触碰主流程。teller 能同时覆盖 Vault、AWS/GCP/Azure Secret Manager、Consul、Etcd、Heroku、1Password、Gopass、Dotenv 等,靠的就是这层足够薄但稳定的接缝。

第三层是统一映射与输出器。 各后端响应结构千差万别:Vault 是 KV 嵌套,AWS 返回 JSON 字符串,GCP 是经过 base64 编码的 payload。teller 在 provider 内部完成归一化,对外只暴露扁平的 map[string]string。之后由输出器接管:写入 dotenv 文件、导出为可 source 的 shell 语句、按 Go text/template 渲染配置,或者最优雅的一种——teller run 把密钥直接塞进子进程的环境变量,进程结束即消失,磁盘不留痕迹。

值得一提的是模板能力:配置里可以引用其他已拉取的密钥做拼接派生,这意味着“把多个后端的碎片拼成一条完整连接串”可以在配置层完成,相当于一次轻量计算。

功能亮点与差异

同类工具不少,但定位差异明显。Vault CLI 只服务 Vault 一家;Doppler、Infisical 走 SaaS 中心化路线,要求你把密钥搬进它们的平台;SOPS 解决的是“加密文件怎么安全入库”。teller 选了第四条路:多后端聚合的只读前端。

杀手锏有三个。其一,跨后端聚合——一份配置里同时挂多个 provider,一条 teller show 就能看到本地 dotenv 与云端 Vault 的合流结果,后者覆盖前者,天然契合“本地默认值 + 远端覆盖”的开发习惯。其二,run 模式的不落盘注入,把密钥生命周期压缩到进程存活期内,相比“先生成 .env 再启动进程”是安全模型上实打实的改进。其三,零依赖单二进制,CI 镜像里丢一个可执行文件就能用,不需要 Python 运行时或一长串 SDK 依赖。

应用场景与落地建议

最合适的场景是“多云的开发与 CI 前端”:本地开发者用统一的 .teller.yml 拉取预发配置;CI 流水线用 run 模式注入构建期密钥;K8s 的 initContainer 里跑一次 teller 生成挂载配置。它扮演的是胶水层,而非密钥管理系统。

生产落地有几条硬性注意。其一,teller 不做加密,出口就是明文,所有落盘的 .env 必须纳入 .gitignore,并优先使用临时路径或 run 模式。其二,鉴权完全依赖底层后端的凭证链(AWS IAM Role、Vault Token、GCP ADC),务必按最小权限授予只读策略,不要用一把管理级 Token 打通所有环境。其三,密钥轮换、审计、吊销仍然要回后端完成,teller 只读不写,不提供生命周期管理。其四,把“谁能读到 .teller.yml”和“谁持有后端凭证”当作同一件事治理——配置文件虽无密文,但它就是一张藏宝图。

综合评价

优点很清晰:设计克制、抽象干净、单二进制易分发,run 模式与多后端聚合在真实工作流里确实省事。它没试图成为平台,只把一件事做好——让开发者不必离开命令行。

短板同样明确。项目社区活跃度不算高,新后端的合并与维护节奏依赖少数维护者,选型时要评估长期可用性;没有常驻进程,也就谈不上动态刷新与密钥推送,配置变更基本靠重启;模板能力停留在字符串插值层面,复杂编排仍需脚本兜底;对密钥的治理能力近乎为零,安全方面的保障上限完全取决于使用者如何约束后端权限与输出路径。

一句话总结:teller 不是密钥保险箱,而是一把好用的钥匙串——它不保证你的钥匙绝对安全,但能让你不必为了找一把钥匙翻遍整个房间。

关于

GitHub 原始描述(英文)

Cloud native secrets management for developers - never leave your command line for secrets.

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
3672
价值分
2003
活跃状态
活跃

语言占比

JavaScript
PowerShell
Raku
Rust
Shell

Release

README






:computer: Never leave your terminal for secrets
:pager: Create easy and clean workflows for working with cloud environments
:mag_right: Scan for secrets and fight secret sprawl


Teller - the open-source universal secret manager for developers

Never leave your terminal to use secrets while developing, testing, and building your apps.

Instead of custom scripts, tokens in your .zshrc files, visible EXPORTs in your bash history, misplaced .env.production files and more around your workstation – just use teller and connect it to any vault, key store, or cloud service you like (Teller support Hashicorp Vault, AWS Secrets Manager, Google Secret Manager, and many more).

You can use Teller to tidy your own environment or for your team as a process and best practice.

Quick Start with teller

Download a binary Grab a binary from releases

Build from source Using this method will allow you to eye-ball the source code, review it, and build a copy yourself.

This will install the binary locally on your machine:

$ cd teller-cli
$ cargo install --path .

Create a new configuration

$ teller new
? Select your secret providers ›
⬚ hashicorp_consul
⬚ aws_secretsmanager
⬚ ssm
⬚ dotenv
⬚ hashicorp
⬚ google_secretmanager

Then, edit the newly created .teller.yml to set the maps and keys that you need for your providers.

A look at teller.yml

The teller YAML describes your providers and within each provider a map that describes:

  • What is the root path to fetch key-values from
  • For each such map, its unique id which will serve you for operations later
  • For each map, an optional specific key name mapping - you can rename keys that you will fetch from the source provider

Here’s an example configuration file. Note that it also include templating constructs – such as fetching environment variables while loading the configuration:

providers:
  hashi_1:
    kind: hashicorp
    maps:
      - id: test-load
        path: /{{ get_env(name="TEST_LOAD_1", default="test") }}/users/user1
        # if empty, map everything
        # == means map to same key name
        # otherwise key on left becomes right
        # in the future: key_transform: camelize, snake_case for automapping the keys
        keys:
          GITHUB_TOKEN: ==
          mg: FOO_BAR
  dot_1:
    kind: dotenv
    maps:
      - id: stg
        path: VAR_{{ get_env(name="STAGE", default="development") }}

You can now address these providers as hashi_1 or dot_1. Teller pulls the specified data from all providers by default.

Features

:running: Running subprocesses

Manually exporting and setting up environment variables for running a process with demo-like / production-like set up?

Got bitten by using .env.production and exposing it in the local project itself?

Using teller and a .teller.yml file that exposes nothing to the prying eyes, you can work fluently and seamlessly with zero risk, also no need for quotes:

$ teller run --reset --shell -- node index.js

:mag_right: Inspecting variables

This will output the current variables teller picks up. Only first 2 letters will be shown from each, of course.

$ teller show

:tv: Local shell population

Hardcoding secrets into your shell scripts and dotfiles?

In some cases it makes sense to eval variables into your current shell. For example in your .zshrc it makes much more sense to use teller, and not hardcode all those into the .zshrc file itself.

In this case, this is what you should add:

eval "$(teller sh)"

:whale: Easy Docker environment

Tired of grabbing all kinds of variables, setting those up, and worried about these appearing in your shell history as well?

Use this one liner from now on:

$ docker run --rm -it --env-file <(teller env) alpine sh

:warning: Scan for secrets

Teller can help you fight secret sprawl and hard coded secrets, as well as be the best productivity tool for working with your vault.

It can also integrate into your CI and serve as a shift-left security tool for your DevSecOps pipeline.

Look for your vault-kept secrets in your code by running:

$ teller scan

You can run it as a linter in your CI like so:

run: teller scan --error-if-found

It will break your build if it finds something (returns exit code 1).

You can also export results as JSON with --json and scan binary files with -b.

:recycle: Redact secrets from process outputs, logs, and files

You can use teller as a redaction tool across your infrastructure, and run processes while redacting their output as well as clean up logs and live tails of logs.

Pipe any process output, tail or logs into teller to redact those, live:

$ cat some.log | teller redact

It should also work with tail -f:

$ tail -f /var/log/apache.log | teller redact

Finally, if you’ve got some files you want to redact, you can do that too:

$ teller redact --in dirty.csv --out clean.csv

If you omit --in Teller will take stdin, and if you omit --out Teller will output to stdout.

:scroll: Populate templates

You can populate custom templates:

$ teller template --in config-templ.t

Template format is Tera which is very similar to liquid or handlebars.

Here is an example template:

production_var: {{ key(name="PRINT_NAME")}}
production_mood: {{ key(name="PRINT_MOOD")}}

:arrows_counterclockwise: Copy/sync data between providers

In cases where you want to sync between providers, you can do that with teller copy.

Specific mapping key sync

You can use the <provider name>/<map id> format to copy a mapping from a provider to another provider:

$ teller copy --from source/dev --to target/prod,<...>

In this simplistic example, we use the following configuration file

providers:
  dot1:
    kind: dotenv
    maps:
      - id: one
        path: one.env
  dot2:
    kind: dotenv
    maps:
      - id: two
        path: two.env

This will:

  1. Grab all mapped values from source mapping
  2. For each target provider, find the matching mapping, and copy the values from source into it

By default copying will update target mapping (upsert data), if you want to replace you can use --replace.

:bike: Write and multi-write to providers

Teller providers supporting write use cases which allow writing values into providers.

Remember, for this feature it still revolves around definitions in your teller.yml file:

$ teller put --providers new --map-id one NEW_VAR=s33kret

In this example, this configuration is being used:

providers:
  new:
    kind: dotenv
    maps:
      - id: one
        path: new.env

A few notes:

  • Values are key-value pair in the format: key=value and you can specify multiple pairs at once
  • When you’re specifying a literal sensitive value, make sure to use an ENV variable so that nothing sensitive is recorded in your history
  • The flag --providers lets you push to one or more providers at once

:x: Delete and multi-delete from providers

Teller providers support deleting values from providers.

$ teller delete --providers new --map-id one DELETE_ME

A few notes:

  • You can specify multiple keys to delete, for example:
  • The flag --providers lets you push to one or more providers at once

YAML Export in YAML format

XXX TODO: rewrite how the command export works

You can export in a YAML format, suitable for GCloud:

$ teller export yaml

Example format:

FOO: "1"
KEY: VALUE

JSON Export in JSON format

You can export in a JSON format, suitable for piping through jq or other workflows:

$ teller export json

Example format:

{
  "FOO": "1"
}

Providers

You can get a list of the providers and their described configuration values in the documentation.

Testing check list:

  • docker on windows: if you have a container based test that uses Docker, make sure to exclude it on Windows using #[cfg(not(windows))]

  • resource semantics: while building providers, align with the semantics of empty and not found as two different semantics: if a provider supports an explicit “not found” semantic (404, NotFound, etc.), use Error::NotFound. Otherwise when a provider signals a “not found” semantic as an empty data bag, return an empty KV[] (i.e. do not translate a sematic of “empty” into “not found”).

Testing

Testing is done with:

$ cargo test --all --all-features

And requires Docker (or equivalent) on your machine.

Thanks:

To all Contributors - you make this happen, thanks!

Code of conduct

Teller follows CNCF Code of Conduct

Copyright © 2024 @jondot. See LICENSE for further details.

评论

暂无评论