mcpField
P
provider-configuration

provider-configuration

github.com/hashicorp开发909986 次安装今天更新
代码仓库

关于 provider-configuration

面向 Terraform Plugin Framework 的 Provider 配置技能,介绍连接设置与凭据解析,涵盖凭据来源顺序、诊断信息、密钥处理和测试。

主要功能

  1. 01有序凭据来源链
  2. 02按完整集合解析密钥
  3. 03Configure 阶段验证
  4. 04诊断与日志脱敏
  5. 05单元测试指南

适用场景

  1. 01设计 Provider 身份验证
  2. 02处理环境变量和文件凭据
  3. 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 使用的模式,也适用于任何提供程序。标准优先级如下,按优先级从高到低排列:

  1. 静态配置 — 直接在 provider 块中设置的值。显式设置的值始终优先。
  2. 环境变量 — EXAMPLECLOUD_API_KEY 等。这是适合 CI 的方式。
  3. 共享凭据文件 — ~/.examplecloud/credentials 中的具名配置文件,适合拥有多个账户的用户。
  4. 平台身份 — 平台提供时使用实例元数据、工作负载身份或 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 技能从资源和数据源中 使用已配置的客户端。

评论

支持 Markdown