关于 provider-configuration
面向 Terraform Plugin Framework 的 Provider 配置技能,介绍连接设置与凭据解析,涵盖凭据来源顺序、诊断信息、密钥处理和测试。
主要功能
- 01有序凭据来源链
- 02按完整集合解析密钥
- 03Configure 阶段验证
- 04诊断与日志脱敏
- 05单元测试指南
适用场景
- 01设计 Provider 身份验证
- 02处理环境变量和文件凭据
- 03排查凭据解析故障
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hashicorp/ agent-skills --skill provider-configuration安装后在 Claude Code 中输入/provider-configuration确认已加载。
附带文件references/case-studies.mdreferences/credential-chain.md
详细介绍
Terraform 提供程序配置与身份验证
提供程序如何接受连接设置并解析凭据。糟糕的身份验证体验是每位提供程序用户首先遇到的问题;设计良好的凭据提供程序链,才是生产级提供程序与演示示例的区别。示例使用虚构的 examplecloud 提供程序和 Plugin Framework。
参考资料(按需加载):
references/credential-chain.md— 完整且可编译的凭据链实现(提供程序、链、文件配置文件、Configure 接线、测试)references/case-studies.md— AWS 提供程序(aws-sdk-go-base)和较小型提供程序如何构建真实的凭据链
身份验证的提供程序架构
每个身份验证属性都必须为 Optional,不能为 Required——Required 属性会迫使用户将凭据写入配置,并使环境变量和凭据文件解析无法实现。将机密标记为 Sensitive,以便 Terraform 在计划输出中进行脱敏;并在每个描述中说明环境变量回退方式,以便 tfplugindocs 发布解析规则。
func (p *examplecloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"endpoint": schema.StringAttribute{
Optional: true,
MarkdownDescription: "API endpoint. May also be set via the `EXAMPLECLOUD_ENDPOINT` environment variable.",
},
"api_key": schema.StringAttribute{
Optional: true,
MarkdownDescription: "API key. May also be set via the `EXAMPLECLOUD_API_KEY` environment variable, or in a shared credentials file.",
},
"api_secret": schema.StringAttribute{
Optional: true,
Sensitive: true,
MarkdownDescription: "API secret. May also be set via the `EXAMPLECLOUD_API_SECRET` environment variable, or in a shared credentials file.",
},
"profile": schema.StringAttribute{
Optional: true,
MarkdownDescription: "Named profile in the shared credentials file. May also be set via the `EXAMPLECLOUD_PROFILE` environment variable. Defaults to `default`.",
},
"skip_credentials_validation": schema.BoolAttribute{
Optional: true,
MarkdownDescription: "Skip the identity check normally performed during provider configuration.",
},
},
}
}
绝不要为凭据属性添加 Default,也绝不要在提供程序的任何位置硬编码凭据。默认值应放在解析逻辑中(环境变量和文件可覆盖这些默认值),而不是架构中。
凭据提供程序链
按顺序检查来源,并使用第一个能提供完整凭据集的来源来解析凭据。这是 AWS 提供程序通过 aws-sdk-go-base 使用的模式,也适用于任何提供程序。标准优先级如下,按优先级从高到低排列:
- 静态配置 — 直接在
provider块中设置的值。显式设置的值始终优先。 - 环境变量 —
EXAMPLECLOUD_API_KEY等。这是适合 CI 的方式。 - 共享凭据文件 —
~/.examplecloud/credentials中的具名配置文件,适合拥有多个账户的用户。 - 平台身份 — 平台提供时使用实例元数据、工作负载身份或 OIDC 令牌交换。无需任何人存储凭据。
两条规则可让凭据链行为可预测:
- 将机密作为一个整体解析,而不是逐字段解析。 如果环境提供了 API 密钥,但没有提供机密值,那么该来源就不提供任何凭据——应对这两个值都回退到下一个来源。将环境变量中的密钥与文件配置文件中的机密值混用,会导致身份验证失败,而且用户几乎无法排查。
- 逐字段解析非机密连接设置。
endpoint、profile或insecure均可各自独立遵循配置 > 环境 > 文件 > 默认值的优先级,因为此类设置不匹配时显而易见,也不会造成危害。
核心抽象是一个只包含单个方法的接口,以及一个哨兵错误,用于区分“此来源无可提供的内容”(继续回退)和“此来源配置有误”(报告错误):
// ErrNoCredentials signals a source had nothing to offer. The chain falls
// through to the next source. Any other error means the source was
// configured but unusable (e.g. malformed credentials file) and is
// preserved so the final diagnostics can surface it.
var ErrNoCredentials = errors.New("no credentials found")
type Credentials struct {
APIKey string
APISecret string
Source string // which provider supplied them, for logging
}
func (c Credentials) Complete() bool {
return c.APIKey != "" && c.APISecret != ""
}
type Provider interface {
Retrieve(ctx context.Context) (Credentials, error)
Name() string
}
Chain 本身也是一个 Provider,因此可以组合多个链。它会按顺序遍历各个提供程序,并返回第一个完整的凭据集。每个被跳过的来源都会记录到聚合的 ChainError 中;其 Error() 会列出每个来源及其被跳过的原因,而其 Is 方法仅当所有来源都正常回退时才会使 errors.Is(err, ErrNoCredentials) 返回 true。这样,Configure 只需进行一次检查,就能区分“没有提供任何凭据”和“提供了凭据但配置有误”。完整实现——包括链循环、静态、环境和文件提供程序,以及负责定义标准顺序的 NewDefaultChain 构造函数——位于 references/credential-chain.md。
在 Configure 中接入凭证链
每次 Terraform 操作都会在任何资源 CRUD 之前运行一次 Configure。
结构如下:
func (p *examplecloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var config examplecloudProviderModel
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}
// 1. Guard against unknown values (e.g. api_key = some_resource.output).
if config.APIKey.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("api_key"),
"Unknown API Key",
"The provider cannot connect because api_key depends on a value known only after apply. "+
"Set a static value, or use the EXAMPLECLOUD_API_KEY environment variable.",
)
}
// ... repeat for each auth attribute, then:
if resp.Diagnostics.HasError() {
return
}
// 2. Resolve credentials through the chain.
chain := credentials.NewDefaultChain(
config.APIKey.ValueString(),
config.APISecret.ValueString(),
credentials.Options{Profile: config.Profile.ValueString()},
)
creds, err := chain.Retrieve(ctx)
if err != nil {
if errors.Is(err, credentials.ErrNoCredentials) {
resp.Diagnostics.AddError(
"No Valid Credential Sources Found",
"No examplecloud credentials were found. Sources tried, in order:\n\n"+err.Error()+
"\n\nSet api_key and api_secret in the provider block, export "+
"EXAMPLECLOUD_API_KEY and EXAMPLECLOUD_API_SECRET, or add a profile to "+
"~/.examplecloud/credentials. See https://example.com/docs/auth.",
)
} else {
resp.Diagnostics.AddError("Failed to Resolve Credentials", err.Error())
}
return
}
tflog.Debug(ctx, "resolved credentials", map[string]any{"source": creds.Source})
// 3. Build the client once; share it with every resource and data source.
client := examplecloud.NewClient(endpoint, creds.APIKey, creds.APISecret)
resp.DataSourceData = client
resp.ResourceData = client
}
每个步骤都很重要,原因如下:
- 未知值保护。 在规划期间,连接到另一个资源输出的属性是未知,而非 null。没有这项保护时,提供程序会悄悄将其视为空值,转而使用凭证链中的后续来源,并以错误的身份进行身份验证——或者因误导性的“缺少凭证”错误而失败。请在保护提示中说明环境变量这一变通方法。
- 哨兵值检查能选择正确的消息。“你什么都没提供”(给出可操作的选项列表)与“你提供了内容,但内容有问题”(显示解析错误)是两种不同的失败。将两者合并成一条消息,会导致用户为了排查问题而把密钥粘贴到配置中。
- 记录凭证来源,绝不记录密钥。 知道哪个来源生效,是最有用的调试信息,而且无需付出任何代价。
能帮助用户解决问题的诊断信息
身份验证错误消息是用户最常查看的提供程序文档。 每条凭证失败诊断都应说明:
- 尝试过的所有来源、尝试顺序及跳过原因——
ChainError会提供这些信息。aws-sdk-go-base也采用相同做法,其错误类型为NoValidCredentialSourcesError。 - 确切的环境变量名称,以及已查询的凭证文件路径和配置文件名称——不要只说“设置相应的环境变量”。
- 提供程序身份验证指南的文档 URL。
对于可疑但不会导致失败的情况,应使用警告(而非错误),并指出优先采用了什么:例如,同时存在 profile 和环境凭证时,哪一项优先?或者凭证文件具有组/其他用户可读权限时,建议运行 chmod 0600。
密钥卫生
- 为
Credentials类型添加String()和GoString()方法,对密钥字段进行脱敏,这样即使意外使用%v、%+v或错误包装,也绝不会在日志或诊断信息中泄露密钥。 - 绝不在诊断信息、日志行或包装后的错误中包含凭证值——只记录来源名称和非敏感标识符。
- 当其他用户可以读取凭证文件时发出警告(
info.Mode().Perm()&0o077 != 0);在 Windows 上跳过此检查,因为 POSIX 权限位在该系统上没有实际意义。
Configure 阶段验证
在 Configure 中立即解析凭证链——绝不要等到首次使用资源时才延迟解析——这样凭证问题会在规划阶段一次性失败,并提供清晰的消息,而不是在应用过程中途失败。如果 API 提供成本低廉的身份端点(相当于 AWS 的 sts:GetCallerIdentity 或 /whoami),则在解析凭证后调用它,这样无效凭证(而不只是缺失的凭证)也会在配置阶段失败。为隔离网络或使用存根的环境提供 skip_credentials_validation 属性,以便跳过该检查。
凭证链的单元测试
凭证链是纯逻辑——使用单元测试(以 Test 为前缀,不使用 TF_ACC)进行测试,而不是使用验收测试。让环境可注入(添加一个默认值为 os.Getenv 的 getenv func(string) string 字段,或使用 t.Setenv),并将文件提供程序指向 t.TempDir() 中的测试文件。重要的测试包括:
- 逐个来源测试: 每个提供程序在凭证已设置时都应返回凭证,凭证不完整时则返回
ErrNoCredentials(只有密钥、没有密钥对应的密码也属于不完整)。 - 优先级: 静态凭证优先于环境变量;环境变量优先于文件;如果上游来源都无法提供完整凭证,凭证链应继续尝试文件。
- 失败聚合: 所有来源都为空时,
errors.Is(err, ErrNoCredentials)应为 true,且错误消息应列出所有来源。 - 硬错误: 格式错误的凭证文件或明确请求的不存在的配置文件,应返回清晰的错误,而不是悄悄继续尝试后续来源(仅作为默认值使用的配置文件则应继续尝试后续来源)。
- 脱敏: 对
Credentials值使用fmt.Sprintf("%v")和%+v格式化时,结果绝不包含密钥。
完整的测试示例见 references/credential-chain.md。
检查清单
- 所有身份验证属性均为
Optional;机密标记为Sensitive: true - 属性描述注明其环境变量回退项
-
Configure中的每个身份验证属性都有未知值保护 - 链式优先级:静态配置 > 环境变量 > 凭据文件 > 平台身份
- 将机密作为完整集合解析;非机密设置逐字段解析
- 哨兵
ErrNoCredentials用于区分回退与硬性失败 - 缺少凭据的诊断信息列出尝试过的所有来源及文档 URL
-
Credentials类型会在String()/GoString()中对机密进行脱敏 - 凭据文件权限警告(非 Windows)
- 在
Configure中即时解析;可选身份检查通过skip_credentials_validation控制 - 单元测试覆盖各来源的行为、优先级、汇总和脱敏
- 绝不记录或在错误中嵌入任何凭据值
相关技能
使用 new-terraform-provider 技能(如果可用)搭建此配置所在的
provider,并使用 provider-resources 技能从资源和数据源中
使用已配置的客户端。