provider-configuration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTerraform Provider Configuration and Authentication
Terraform Provider配置与认证
How a provider accepts connection settings and resolves credentials. Poor
authentication UX is the first thing every user of a provider hits; a
well-designed credential provider chain is what separates a production-grade
provider from a demo. The examples use a fictional provider
and the Plugin Framework.
examplecloudReferences (load when needed):
- — complete, compilable credential chain implementation (providers, chain, file profiles, Configure wiring, tests)
references/credential-chain.md - — how the AWS provider (
references/case-studies.md) and smaller providers structure real credential chainsaws-sdk-go-base
本文介绍Provider如何接收连接设置并解析凭证。糟糕的认证体验是每个Provider用户首先遇到的问题;设计良好的凭证提供链是区分生产级Provider与演示版Provider的关键。示例使用虚构的 Provider以及Plugin Framework。
examplecloud参考资料(按需查看):
- — 完整可编译的凭证链实现代码(包含Provider、链逻辑、文件配置文件、Configure关联代码、测试)
references/credential-chain.md - — AWS Provider(基于
references/case-studies.md)及小型Provider如何构建实际凭证链的案例分析aws-sdk-go-base
Provider Schema for Authentication
用于认证的Provider Schema
Every authentication attribute must be , never — a
attribute forces users to put credentials in configuration and
makes environment-variable and credentials-file resolution impossible. Mark
secrets so Terraform redacts them in plan output, and state the
environment-variable fallback in each description so publishes
the resolution rules.
OptionalRequiredRequiredSensitivetfplugindocsgo
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.",
},
},
}
}Never add a to a credential attribute, and never hardcode a
credential anywhere in the provider. Defaults belong in the resolution logic
(where environment variables and files can override them), not in the schema.
Default每个认证属性必须设置为,绝不能是——属性会强制用户将凭证写入配置文件,导致无法通过环境变量或凭证文件解析凭证。将敏感信息标记为,这样Terraform会在计划输出中自动遮蔽这些内容,并在每个属性的描述中注明环境变量回退机制,以便发布凭证解析规则。
OptionalRequiredRequiredSensitivetfplugindocsgo
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.",
},
},
}
}切勿为凭证属性添加值,也绝不能在Provider中硬编码任何凭证。默认值应放在解析逻辑中(环境变量和文件可覆盖默认值),而非Schema中。
DefaultThe Credential Provider Chain
凭证提供链
Resolve credentials by consulting an ordered list of sources and taking the
first one that produces a complete set. This is the pattern the AWS
provider uses via ,
and it generalizes to any provider. The canonical precedence, highest first:
aws-sdk-go-base- Static configuration — values set directly in the block. Explicit always wins.
provider - Environment variables — , etc. The CI-friendly path.
EXAMPLECLOUD_API_KEY - Shared credentials file — named profiles in
, for humans with multiple accounts.
~/.examplecloud/credentials - Platform identity — instance metadata, workload identity, or OIDC token exchange, where the platform offers it. Credentials nobody has to store.
Two rules make the chain predictable:
- Resolve secrets as a set, not field-by-field. If the environment supplies an API key but no secret, that source offers nothing — fall through to the next source for both values. Mixing an env-var key with a file-profile secret produces authentication failures that are nearly impossible for users to debug.
- Resolve non-secret connection settings field-by-field. ,
endpoint, orprofilecan each independently follow config > env > file > default, because a mismatch there is visible and harmless.insecure
The core abstraction is a single-method interface with a sentinel error that
distinguishes "this source has nothing to offer" (fall through) from "this
source is misconfigured" (surface it):
go
// 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
}A (itself a , so chains compose) walks the providers in
order and returns the first complete set of credentials. Every skipped
source is recorded into an aggregate whose lists each
source with the reason it was skipped, and whose method makes
true only when every source fell through
cleanly — so can tell "nothing supplied" from "something
supplied but broken" with one check. The full implementation — the chain
loop, the static, environment, and file providers, and the
constructor that owns the canonical order — lives in
.
ChainProviderChainErrorError()Iserrors.Is(err, ErrNoCredentials)ConfigureNewDefaultChainreferences/credential-chain.md通过按顺序查询一系列凭证来源,取第一个能提供完整凭证集的来源来解析凭证。这是AWS Provider通过使用的模式,可推广到任意Provider。标准优先级从高到低为:
aws-sdk-go-base- 静态配置——直接在块中设置的值。显式配置始终优先。
provider - 环境变量——等。适合CI环境使用。
EXAMPLECLOUD_API_KEY - 共享凭证文件——中的命名配置文件,适合拥有多个账户的用户。
~/.examplecloud/credentials - 平台身份——实例元数据、工作负载身份或OIDC令牌交换,适用于支持该功能的平台。无需存储的凭证方式。
以下两条规则可确保链的可预测性:
- 按完整集合解析敏感信息:如果环境变量提供了API密钥但未提供密钥秘钥,则该来源视为无效——直接跳过并尝试下一个来源获取全部所需值。混合使用环境变量的密钥和文件配置文件的秘钥会导致几乎无法调试的认证失败。
- 按字段解析非敏感连接设置:、
endpoint或profile可各自独立遵循「配置→环境变量→文件→默认值」的优先级,因为此类不匹配是可见且无害的。insecure
核心抽象是一个单方法接口,通过哨兵错误区分「该来源无可用凭证」(跳过)和「该来源配置错误」(抛出错误):
go
// 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
}ChainProviderChainErrorError()Iserrors.Is(err, ErrNoCredentials)ConfigureNewDefaultChainreferences/credential-chain.mdWiring the Chain into Configure
将凭证链关联到Configure方法
Configurego
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
}Why each step matters:
- Unknown-value guards. During planning, an attribute wired to another resource's output is unknown, not null. Without the guard the provider silently treats it as empty, falls through the chain, and authenticates as the wrong identity — or fails with a misleading "missing credentials" error. Name the environment-variable workaround in the guard message.
- The sentinel check picks the right message. "You gave me nothing" (actionable list of options) is a different failure from "you gave me something broken" (show the parse error). Collapsing them into one message is how providers end up with users pasting secrets into config to debug.
- Log the source, never the secret. Knowing which source won is the single most useful debugging fact and costs nothing to log.
Configurego
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
}各步骤的重要性:
- 未知值防护:在计划阶段,关联到其他资源输出的属性是「未知」状态而非空值。如果没有防护逻辑,Provider会将其视为空值,跳过当前链并使用错误的身份认证——或抛出误导性的「凭证缺失」错误。在防护提示中注明环境变量的替代方案。
- 哨兵错误检查选择正确提示:「未提供任何凭证」(列出可操作选项)与「提供的凭证无效」(显示解析错误)是两种不同的故障,将它们合并为一条提示会导致用户为了调试而将敏感信息粘贴到配置文件中。
- 记录凭证来源,而非凭证内容:知道哪个来源提供了凭证是最有用的调试信息,且无需记录敏感内容。
Diagnostics That Unblock Users
帮助用户解决问题的诊断信息
An authentication error message is the provider's most-read documentation.
Every credential failure diagnostic should name:
- Every source tried, in order, with why it was skipped — the
provides this.
ChainErrordoes the same with itsaws-sdk-go-base.NoValidCredentialSourcesError - The exact environment variable names and the credentials file path and profile that were consulted — not "set the appropriate environment variables".
- A documentation URL for the provider's authentication guide.
Use warnings (not errors) for conditions that are suspicious but not fatal,
naming what took precedence: a set while environment credentials
are also present (which wins?), or a credentials file with group/world-read
permissions (suggest ).
profilechmod 0600认证错误提示是Provider被阅读最多的文档。每个凭证失败的诊断信息应包含:
- 所有尝试过的来源及顺序,以及被跳过的原因——提供了这些信息。
ChainError的aws-sdk-go-base也采用了相同的方式。NoValidCredentialSourcesError - 确切的环境变量名称、凭证文件路径及查询的配置文件名称——而非模糊的「设置相应环境变量」。
- Provider认证指南的文档链接。
对于可疑但非致命的情况,使用警告(而非错误)提示,并说明优先级:例如设置了但同时存在环境变量凭证(哪个会优先?),或凭证文件具有组/全局可读权限(建议执行)。
profilechmod 0600Secret Hygiene
敏感信息安全规范
- Give the type
CredentialsandString()methods that redact secret fields, so a strayGoString(),%v, or error wrap can never leak a secret into logs or diagnostics.%+v - Never include credential values in diagnostics, log lines, or wrapped errors — log the source name and non-secret identifiers only.
- Warn when a credentials file is readable by other users
(); skip this check on Windows, where POSIX permission bits are not meaningful.
info.Mode().Perm()&0o077 != 0
- 为类型添加
Credentials和String()方法,遮蔽敏感字段,避免因意外使用GoString()、%v或错误包装导致敏感信息泄露到日志或诊断信息中。%+v - 绝不在诊断信息、日志行或包装错误中包含凭证内容——仅记录来源名称和非敏感标识符。
- 当凭证文件可被其他用户读取时发出警告();Windows系统跳过此检查,因为POSIX权限位在该系统中无意义。
info.Mode().Perm()&0o077 != 0
Configure-Time Validation
配置阶段验证
Resolve the chain eagerly in — never lazily on first resource
use — so a credentials problem fails one time, at plan, with a good message,
instead of failing in the middle of an apply. If the API has a cheap
identity endpoint (the equivalent of AWS or a
), call it after resolving credentials so invalid (not just
missing) credentials also fail at configure time. Gate it behind a
attribute for air-gapped or stubbed
environments.
Configurests:GetCallerIdentity/whoamiskip_credentials_validation在中提前解析凭证链——而非在首次使用资源时延迟解析——这样凭证问题会在计划阶段一次性失败并给出清晰提示,而非在应用过程中中途失败。如果API提供了轻量的身份验证端点(类似AWS的或),解析凭证后调用该端点,使无效(不仅是缺失)的凭证也能在配置阶段失败。为隔离环境或模拟环境添加属性,用于跳过该检查。
Configurests:GetCallerIdentity/whoamiskip_credentials_validationUnit Testing the Chain
凭证链的单元测试
The chain is pure logic — test it with unit tests ( prefix, no
), not acceptance tests. Make the environment injectable (a
field defaulting to , or use
) and point the file provider at fixtures. The
tests that matter:
TestTF_ACCgetenv func(string) stringos.Getenvt.Setenvt.TempDir()- Per-source: each provider returns its credentials when set and
when incomplete (a key with no secret is incomplete).
ErrNoCredentials - Precedence: static beats env; env beats file; chain falls through to the file when nothing above supplies a complete set.
- Failure aggregation: with all sources empty,
is true and the message names every source.
errors.Is(err, ErrNoCredentials) - Hard errors: a malformed credentials file or an explicitly requested profile that does not exist surfaces a descriptive error rather than silently falling through (a merely defaulted profile falls through).
- Redaction: and
fmt.Sprintf("%v")of a%+vvalue never contain the secret.Credentials
Full test examples are in .
references/credential-chain.md凭证链是纯逻辑——使用单元测试(前缀为,无需)而非验收测试。使环境变量可注入(例如默认使用的字段,或使用),并将文件Provider指向中的测试文件。需要覆盖的测试场景:
TestTF_ACCos.Getenvgetenv func(string) stringt.Setenvt.TempDir()- 单来源测试:每个Provider在设置完整凭证时返回凭证,凭证不完整时返回(仅提供密钥未提供秘钥视为不完整)。
ErrNoCredentials - 优先级测试:静态配置优先于环境变量;环境变量优先于文件;当上方来源无完整凭证时,链会回退到文件来源。
- 错误聚合测试:所有来源均无凭证时,返回true,且提示信息列出所有来源。
errors.Is(err, ErrNoCredentials) - 硬错误测试:格式错误的凭证文件或明确指定但不存在的配置文件会抛出描述性错误,而非静默跳过(仅默认配置文件不存在时才会跳过)。
- 脱敏测试:对值使用
Credentials或fmt.Sprintf("%v")时,绝不会显示敏感内容。%+v
完整测试示例可查看。
references/credential-chain.mdChecklist
检查清单
- All auth attributes ; secrets marked
OptionalSensitive: true - Attribute descriptions name their environment-variable fallbacks
- Unknown-value guards on every auth attribute in
Configure - Chain precedence: static config > env vars > credentials file > platform identity
- Secrets resolved as a complete set; non-secret settings field-by-field
- Sentinel distinguishes fall-through from hard failure
ErrNoCredentials - Missing-credentials diagnostic lists every source tried + docs URL
- type redacts secrets in
Credentials/String()GoString() - Credentials-file permission warning (non-Windows)
- Eager resolution in ; optional identity check with
Configureskip_credentials_validation - Unit tests cover per-source behavior, precedence, aggregation, redaction
- No credential value ever logged or embedded in an error
- 所有认证属性均为;敏感信息标记为
OptionalSensitive: true - 属性描述中注明环境变量回退机制
- 在中为每个认证属性添加未知值防护
Configure - 链优先级:静态配置 > 环境变量 > 凭证文件 > 平台身份
- 敏感信息按完整集合解析;非敏感设置按字段解析
- 使用哨兵错误区分跳过与硬错误
ErrNoCredentials - 凭证缺失诊断信息列出所有尝试过的来源及文档链接
- 类型在
Credentials/String()中遮蔽敏感信息GoString() - 凭证文件权限警告(非Windows系统)
- 在中提前解析凭证;通过
Configure可选启用身份检查skip_credentials_validation - 单元测试覆盖单来源行为、优先级、错误聚合、脱敏逻辑
- 凭证内容绝不会被记录或嵌入错误信息
Related Skills
相关技能
Use the skill (if available) to scaffold the
provider this configuration lives in, and the skill for
consuming the configured client from resources and data sources.
new-terraform-providerprovider-resources使用技能(若可用)搭建该配置所属的Provider框架,使用技能从资源和数据源中调用已配置的客户端。
new-terraform-providerprovider-resources