Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider rotation #432

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

provider rotation #432

wants to merge 13 commits into from

Conversation

nyobe
Copy link

@nyobe nyobe commented Jan 15, 2025

Introduces a new provider type and verb to support rotating static credentials.

Rotator providers have an additional state input which is used as a write-back target. The result of invoking Rotate is persisted back into the environment to this key. This provides a stable location to write to without accidentally clobbering interpolations that might be used for other inputs.

The new fn::rotate function type is used to invoke this new type of provider. It behaves similarly to existing providers during Open, but during rotation the evaluator will invoke the Rotate and collect their outputs, and the caller is expected to persist these updates back into the environment as a new revision.

# long form
fn::rotate:
  provider: provider
  inputs:
    # any config the provider needs to use during rotation
  state:
    # persisted state

# short form
fn::rotate::provider:
  # inputs
  state:
    # the special `state` input is hoisted out of inputs for the short form

Closes https://github.com/pulumi/pulumi-service/issues/24986

@nyobe nyobe force-pushed the claire/provider-rotation branch from be5df41 to 4e22ea2 Compare January 16, 2025 00:18

// ApplyPatches applies a set of patches values to an environment definition.
// If patch values contain secret values, they will be wrapped with fn::secret.
func ApplyPatches(source []byte, patches []*Patch) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking just more of a thought - I wonder if we would ever want to have this be generic enough to apply any patch instead of limiting this to just values. I know for our current use case this isn't in scope but if a customer wants to use this to apply a patch for automating a migration of which requires import changes it could be helpful.

If we stick with just values here we may want to capture that in the function name

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fair, it could drop the hardcoded assumption of the values toplevel key.
the name is also a bit of a misnomer since it's not doing a json patch, it's just doing a replacement. Perhaps BulkSetValues or ApplyReplacements or something like that? idk

Schema() (inputs, state, outputs *schema.Schema)

// Open retrieves the provider's secrets.
Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error)
Copy link
Author

@nyobe nyobe Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly Open should only take state, not inputs, because longer term we don't want to necessarily evaluate managing credential inputs during Open, just during Rotate?

eval/eval.go Outdated
@@ -98,7 +105,28 @@ func CheckEnvironment(
execContext *esc.ExecContext,
showSecrets bool,
) (*esc.Environment, syntax.Diagnostics) {
return evalEnvironment(ctx, true, name, env, decrypter, providers, environments, execContext, showSecrets)
checked, _, diags := evalEnvironment(ctx, true, name, env, decrypter, providers, nil, environments, execContext, showSecrets, nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll need to accept a rotatorLoader in CheckEnvironment to pass to the eval

Copy link
Author

@nyobe nyobe Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are totally right.. this is partly why I'm unsure about the Provider and Rotator interfaces diverging for Open() and Schema(), which is why RotatorLoader is even required :(

@nyobe nyobe marked this pull request as ready for review January 16, 2025 23:34
@nyobe nyobe requested a review from pgavlin January 16, 2025 23:35
eval/eval.go Outdated Show resolved Hide resolved
Schema() (inputs, state, outputs *schema.Schema)

// Open retrieves the provider's secrets.
Open(ctx context.Context, inputs, state map[string]Value, executionContext EnvExecContext) (Value, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I would drop the inputs from open since we don't really need the inputs to perform the open. Also, it encourages best practice of not having the inputs be considered part of the open permissions

envs EnvironmentLoader,
execContext *esc.ExecContext,
showSecrets bool,
) (*esc.Environment, syntax.Diagnostics) {
rotatePaths map[string]bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pgavlin wdyt about removing this? is there a strong use case for rotating specific paths within an environment?

@@ -165,6 +198,9 @@ type evalContext struct {
root *expr // the root expression
base *value // the base value

rotatePaths map[string]bool // when non-nil, specifies providers to rotate. if empty, the full environment is rotated.
Copy link
Author

@nyobe nyobe Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be too overloaded, maybe we should have an explicit rotating boolean instead of this nil check

}
}()
// wrap secret values
if v.Secret {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this only wrap the top most value as a secret instead of the potentially nested values that are secrets? (I could be wrong about this but adding a test should help determine this)

// Rotators persist internal state by writing back to the environment.
type Rotator interface {
// Schema returns the provider's input, state, and output schemata.
Schema() (inputs, state, outputs *schema.Schema)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about it being confusing that open doesn't necessarily return the full state, so perhaps this can be simplified so that the output schema is used for state schema?

(This would also help heal the divergence of the Rotator and Provider interfaces)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants