跳到主要内容
知仓学习社ZHICANG

provider-ephemeral-resources

>-

不碰外部(只输出文字)无严重或高危命中hashicorp/agent-skills

它会碰到什么

扫了多少1 个文本文件,9 KB
它会碰到什么不碰外部(只输出文字)
命中总数0 处
命中统计严重 0 · 高 0 · 中 0 · 低 0

这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。

技能内容

Terraform Provider Ephemeral Resources

Ephemeral resources (Terraform 1.10+) produce values that are **never

persisted to state or plan**. They exist for exactly one job: handing

secrets — tokens, generated passwords, short-lived certificates, decrypted

values — to the parts of a configuration that need them, without writing

them to disk. Any data source that returns a sensitive value is a candidate

to be (or to also exist as) an ephemeral resource.

Official docs: Ephemeral Resources.

When to Use One

| Situation | Use |

|---|---|

| Read-only lookup of non-sensitive data | Data source |

| Value is sensitive and only needed at apply time (DB password for a provider block, token for a write-only attribute) | Ephemeral resource |

| Sensitive value that downstream managed resources must store (e.g. as an attribute) | Regular resource/data source — but pair with write-only attributes where possible |

| Credential that expires mid-operation (STS-style tokens, short-TTL leases) | Ephemeral resource with Renew |

Ephemeral results can be used in provider configuration, write-only

attributes, provisioner configuration, and other ephemeral contexts — but

not in regular attributes, because those persist to state.

Lifecycle

Terraform calls up to three methods per operation:

  • Open (required) — fetch or create the value; runs during plan

and/or apply whenever the result is needed. There is no state to refresh

and nothing to import.

  • Renew (optional) — called when the wall clock passes the

RenewAt returned by Open/Renew, for values that expire while

Terraform is still running. Renew cannot return a new result — it can

only extend/refresh what Open produced (e.g. re-lease the same

credential); if the value itself changes on renewal, the API is not

renewable in this sense and Open must return a longer-lived value.

  • Close (optional) — called when Terraform is done with the value;

revoke leases or delete temporary credentials here.

Open can pass bytes forward via resp.Private; Renew and Close

receive them — use this for lease IDs needed to renew/revoke.

Implementation

var (
    _ ephemeral.EphemeralResource              = &tokenEphemeralResource{}
    _ ephemeral.EphemeralResourceWithConfigure = &tokenEphemeralResource{}
    _ ephemeral.EphemeralResourceWithRenew     = &tokenEphemeralResource{}
    _ ephemeral.EphemeralResourceWithClose     = &tokenEphemeralResource{}
)

func NewTokenEphemeralResource() ephemeral.EphemeralResource {
    return &tokenEphemeralResource{}
}

type tokenEphemeralResource struct {
    client *examplecloud.Client
}

type tokenEphemeralResourceModel struct {
    RoleName types.String `tfsdk:"role_name"`
    Token    types.String `tfsdk:"token"`
    LeaseID  types.String `tfsdk:"lease_id"`
}

func (r *tokenEphemeralResource) Metadata(_ context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
    resp.TypeName = req.ProviderTypeName + "_token"
}

func (r *tokenEphemeralResource) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "role_name": schema.StringAttribute{
                Required:            true,
                MarkdownDescription: "Role to obtain a token for.",
            },
            "token": schema.StringAttribute{
                Computed:            true,
                Sensitive:           true,
                MarkdownDescription: "The issued token. Never persisted to state.",
            },
            "lease_id": schema.StringAttribute{
                Computed:            true,
                MarkdownDescription: "Identifier of the token lease.",
            },
        },
    }
}

func (r *tokenEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
    var data tokenEphemeralResourceModel
    resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
    if resp.Diagnostics.HasError() {
        return
    }

    lease, err := r.client.IssueToken(ctx, data.RoleName.ValueString())
    if err != nil {
        resp.Diagnostics.AddError(
            "Error opening Token",
            fmt.Sprintf("issuing token for role (%s): %s", data.RoleName.ValueString(), err),
        )
        return
    }

    data.Token = types.StringValue(lease.Token)
    data.LeaseID = types.StringValue(lease.ID)

    resp.RenewAt = lease.ExpiresAt.Add(-2 * time.Minute) // renew with margin
    resp.Private.SetKey(ctx, "lease_id", []byte(lease.ID))
    resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
}

func (r *tokenEphemeralResource) Renew(ctx context.Context, req ephemeral.RenewRequest, resp *ephemeral.RenewResponse) {
    leaseID, diags := req.Private.GetKey(ctx, "lease_id")
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }

    lease, err := r.client.RenewLease(ctx, string(leaseID))
    if err != nil {
        resp.Diagnostics.AddError("Error renewing Token", err.Error())
        return
    }
    resp.RenewAt = lease.ExpiresAt.Add(-2 * time.Minute)
}

func (r *tokenEphemeralResource) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) {
    leaseID, diags := req.Private.GetKey(ctx, "lease_id")
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }

    if err := r.client.RevokeLease(ctx, string(leaseID)); err != nil {
        resp.Diagnostics.AddError("Error closing Token", err.Error())
    }
}

Configure follows the same ProviderData-cast pattern as resources (the

provider-resources skill, if available, shows it); the client comes from

resp.EphemeralResourceData set in the provider's Configure.

Registration

The provider opts in via provider.ProviderWithEphemeralResources:

var _ provider.ProviderWithEphemeralResources = &examplecloudProvider{}

func (p *examplecloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource {
    return []func() ephemeral.EphemeralResource{
        NewTokenEphemeralResource(),
    }
}

Set resp.EphemeralResourceData = client in the provider's Configure

alongside ResourceData/DataSourceData.

Design Rules

  • Never log the value, never put it in a diagnostic. The whole point is

non-persistence; an error message containing the token defeats it.

  • Mark the secret attribute Sensitive: true anyway — it guards rendering

in the ephemeral value's own lifecycle output.

  • No plan modifiers, no import, no id convention — there is no state for

any of them to act on.

  • Schema inputs follow the same rules as data source arguments; expose the

API's identifiers (role_name), not invented ones.

  • Set RenewAt with a safety margin before the real expiry; Terraform

renews lazily, not on a precise timer.

  • If the upstream value cannot be revoked, skip Close rather than

implementing a no-op that suggests revocation happens.

Testing

Ephemeral results never reach state, so tests assert them indirectly — the

standard pattern echoes the ephemeral value through the echoprovider into

a regular resource the test can inspect. Minimum coverage: a basic

open-and-use test and per-attribute tests alongside required fields. Use

the provider-test-patterns skill (if available) — its ephemeral testing

reference covers the echoprovider setup, version gating

(tfversion.SkipBelow(tfversion.Version1_10_0)), and multi-step patterns.

Documentation

Registry docs live at docs/ephemeral-resources/<name>.md, generated by

tfplugindocs like every other page type. Use the provider-docs skill

(if available) for the workflow; document the renewal/revocation behavior

explicitly — users need to know whether closing their Terraform run revokes

the credential.

Checklist

  • [ ] Value genuinely must not persist (otherwise a data source is simpler)
  • [ ] Open implemented; Renew/Close only where the API supports them
  • [ ] Secret attributes Sensitive: true; value never logged or in diagnostics
  • [ ] Lease/handle passed via Private, not via the result
  • [ ] RenewAt set with margin for expiring credentials
  • [ ] Registered in EphemeralResources(); EphemeralResourceData set in provider Configure
  • [ ] Echo-provider acceptance tests, version-gated to Terraform >= 1.10
  • [ ] Docs page explains lifetime, renewal, and revocation behavior

想直接用这个技能?

本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。