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. 01Provider 認証の設計
  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 プロバイダーの設定と認証

プロバイダーが接続設定を受け取り、認証情報を解決する方法です。プロバイダーを利用するすべてのユーザーが最初に直面するのは、使いにくい認証 UX です。適切に設計された認証情報プロバイダーチェーンこそが、本番品質のプロバイダーとデモ用のプロバイダーを分けます。例では、架空の examplecloud プロバイダーと Plugin Framework を使用します。

参考資料(必要に応じて読み込んでください):

  • references/credential-chain.md — コンパイル可能な完全な認証情報チェーンの実装(プロバイダー、チェーン、ファイルプロファイル、Configure の配線、テスト)
  • references/case-studies.md — AWS プロバイダー(aws-sdk-go-base)と小規模なプロバイダーにおける、実際の認証情報チェーンの構成方法

認証用プロバイダーのスキーマ

認証属性はすべて Optional にし、Required にはしないでください。Required 属性にすると、ユーザーは認証情報を設定に記述せざるを得なくなり、環境変数や認証情報ファイルからの解決ができなくなります。Terraform のプラン出力でシークレットが非表示になるように Sensitive を指定し、各属性の説明に環境変数へのフォールバックを記載して、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. プラットフォーム ID — プラットフォームが対応している場合の、インスタンスメタデータ、ワークロード ID、または OIDC トークン交換です。認証情報を保存する必要がありません。

次の 2 つのルールに従うと、チェーンの動作が予測しやすくなります。

  • シークレットはフィールドごとではなく、セットとして解決します。 環境変数で API キーが指定されていても、シークレットが指定されていなければ、そのソースは何も提供しません。両方の値について、次のソースにフォールスルーします。環境変数のキーとファイルプロファイルのシークレットを混在させると、ユーザーがデバッグするのはほぼ不可能な認証エラーが発生します。
  • シークレットではない接続設定は、フィールドごとに解決します。 endpoint、profile、insecure はそれぞれ独立して config > env > file > default の順に解決できます。不一致があっても、確認できて実害がないためです。

中核となる抽象化は、単一メソッドのインターフェースと、次のどちらに該当するかを区別するセンチネルエラーです。「このソースには提供できるものがない」(フォールスルー)か、「このソースの設定に誤りがある」(エラーを表面化)かを判別します。

// 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 であるため、チェーンを組み合わせることができます。Chain はプロバイダーを順番に調べ、最初に完全な認証情報セットを返したソースを採用します。スキップした各ソースは集約された ChainError に記録されます。その Error() は、各ソースとスキップした理由を列挙します。また、その Is メソッドにより、すべてのソースが問題なくフォールスルーした場合に限り errors.Is(err, ErrNoCredentials) が true になります。これにより、Configure は 1 回のチェックで「何も提供されていない」場合と「何か提供されたが壊れている」場合を判別できます。チェーンループ、静的・環境変数・ファイルの各プロバイダー、標準的な順序を管理する NewDefaultChain コンストラクターを含む完全な実装は、references/credential-chain.md にあります。

Configure へのチェーンの組み込み

Configure は、Terraform の各操作につき、リソースの CRUD が実行される前に一度実行されます。 構成は次のとおりです。

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 ではなく、不明です。ガードがないと、プロバイダーは値を空として黙って扱い、チェーンの次の認証情報へ進み、誤った ID で認証するか、誤解を招く「認証情報がありません」というエラーで失敗します。ガードのメッセージには、環境変数による回避策を明記してください。
  • センチネルのチェックで適切なメッセージを選びます。 「何も指定されていません」(実行可能な選択肢のリストを提示)は、「何か指定されていますが、壊れています」(解析エラーを表示)とは異なるエラーです。これらを一つのメッセージにまとめると、デバッグのためにユーザーが設定にシークレットを貼り付けるような事態につながります。
  • ソースを記録し、シークレットは記録しないでください。 どのソースが選択されたかを知ることは、最も役立つデバッグ情報であり、記録にコストもかかりません。

ユーザーの問題を解決する診断メッセージ

認証エラーメッセージは、プロバイダーのドキュメントの中で最もよく読まれるものです。 すべての認証情報エラー診断では、次の内容を明記してください。

  • 試行したすべてのソースと、スキップした理由(試行順) — これらは ChainError が提供します。aws-sdk-go-base も NoValidCredentialSourcesError で同様の情報を提供します。
  • 正確な環境変数名、および参照した認証情報ファイルのパスとプロファイル — 「適切な環境変数を設定してください」では不十分です。
  • プロバイダーの認証ガイドのドキュメント URL。

致命的ではないものの不審な状態には、エラーではなく警告を使用し、どちらが優先されたかを明記してください。たとえば、環境変数の認証情報も存在する状態で profile が設定されている場合(どちらが優先されますか?)、または認証情報ファイルがグループや他のユーザーから読み取り可能な場合(chmod 0600 を推奨します)です。

シークレットの取り扱い

  • Credentials 型に、シークレットフィールドをマスキングする String() メソッドと GoString() メソッドを実装してください。これにより、誤って %v、%+v を使用したり、エラーをラップしたりしても、シークレットがログや診断メッセージに漏れることはありません。
  • 診断メッセージ、ログ行、ラップされたエラーに認証情報の値を含めないでください。記録するのはソース名と、シークレットではない識別情報だけにしてください。
  • 認証情報ファイルが他のユーザーから読み取り可能な場合は警告してください(info.Mode().Perm()&0o077 != 0)。POSIX の権限ビットが意味を持たない Windows では、このチェックをスキップしてください。

Configure 時の検証

チェーンは Configure でただちに解決してください。初めてリソースを使用するときまで遅延させてはいけません。そうすれば、認証情報の問題が適切なメッセージとともにプラン時に一度だけ発生し、apply の途中で失敗することを防げます。API に低コストの ID エンドポイント(AWS の sts:GetCallerIdentity や /whoami に相当するもの)がある場合は、認証情報の解決後に呼び出してください。これにより、認証情報が不足している場合だけでなく、無効な場合も Configure 時に失敗させられます。エアギャップ環境やスタブ環境向けに、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 内で、すべての認証属性に未知の値に対するガードがある
  • 優先順位:静的設定 > 環境変数 > 認証情報ファイル > プラットフォーム ID
  • シークレットは一式として解決し、シークレット以外の設定はフィールドごとに解決する
  • フォールスルーと致命的なエラーを区別するセンチネル ErrNoCredentials
  • 認証情報が不足している場合の診断メッセージに、試行したすべてのソースとドキュメント URL が記載されている
  • Credentials 型で String()/GoString() 内のシークレットがマスキングされる
  • 認証情報ファイルの権限に関する警告がある(Windows 以外)
  • Configure 内で即時に解決し、skip_credentials_validation による ID チェックは任意
  • 単体テストで、ソースごとの動作、優先順位、集約、マスキングを網羅している
  • 認証情報の値がログに記録されたり、エラーに埋め込まれたりすることがない

関連スキル

この設定を含むプロバイダーのひな形を作成するには new-terraform-provider スキル(利用可能な場合)を使用し、リソースおよびデータソースから設定済みクライアントを利用するには provider-resources スキルを使用します。

コメント