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

feat(remote/aws): support AWS Secrets Manager as remote component #718

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

Conversation

hainenber
Copy link
Contributor

@hainenber hainenber commented Apr 30, 2024

PR Description

Which issue(s) this PR fixes

Closes #689

Notes to the Reviewer

  • Should I poll the Secrets Manager API continuously to fetch latest secret. Pricing per API calls for Secrets Manager is much expensive compared to AWS SSM Parameter Store and AWS S3 API calls so I'm hesitant to implement poller. Would like to hear everyone thoughts about it.
  • I expose the inner component.Component in componenttest in order to verify the component's health status. Is this A-OK?

PR Checklist

  • CHANGELOG.md updated
  • Documentation added
  • Tests added

@hainenber hainenber changed the title Support aws secret manager as remote component Support AWS Secrets Manager as remote component Apr 30, 2024
@mattdurham
Copy link
Collaborator

I think it would be nice if it could poll but set the default to not poll. Most of our pulling mechanisms support it so it would be odd if this one did not have that capability.

@hainenber
Copy link
Contributor Author

Thanks Matt! I'll implement the poller and fix whatever makes the tests failed.

Maybe in the long run, we can have a reusable implementation. But that's for another day, I suppose :D

@hainenber hainenber changed the title Support AWS Secrets Manager as remote component feat(remote/aws): support AWS Secrets Manager as remote component May 1, 2024
@@ -106,11 +100,11 @@ func (s *Component) Run(ctx context.Context) error {
func (s *Component) Update(args component.Arguments) error {
newArgs := args.(Arguments)

s3cfg, err := generateS3Config(newArgs)
awsCfg, err := aws_common_config.GenerateAWSConfig(newArgs.Options.Client)
if err != nil {
return nil
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rfratto pinging 'cause you authored this in the past. Do you know why this returns nil in the present of non-nil error? 👀

Copy link
Member

Choose a reason for hiding this comment

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

I think remote.s3 is originally from @mattdurham; do you remember if this is intentional? It does look a little like a typo to silently ignore the error here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ooof, I cant think of a good reason why I did that.

@clayton-cornell clayton-cornell added the type/docs Docs Squad label across all Grafana Labs repos label May 2, 2024
@rfratto rfratto added the backport-to-agent:no PR should NOT be backported to the agent repo. label May 3, 2024
@clayton-cornell clayton-cornell requested a review from a team May 13, 2024 16:42
@clayton-cornell
Copy link
Contributor

Docs look ok.

@hainenber hainenber force-pushed the support-aws-secret-manager-as-remote-component branch from 68ec808 to e453b57 Compare May 14, 2024 15:02
@hainenber hainenber force-pushed the support-aws-secret-manager-as-remote-component branch from e453b57 to 20a34d0 Compare May 16, 2024 15:07
Copy link
Contributor

@clayton-cornell clayton-cornell left a comment

Choose a reason for hiding this comment

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

Approving for docs. Still needs a code review fom @grafana/grafana-alloy-maintainers

@rfratto
Copy link
Member

rfratto commented Jul 10, 2024

I think it would be nice if it could poll but set the default to not poll. Most of our pulling mechanisms support it so it would be odd if this one did not have that capability.

@mattdurham Can you help me understand setting the default to not poll for updates? Don't all of our components default to polling in the background?

Copy link
Member

@rfratto rfratto left a comment

Choose a reason for hiding this comment

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

Nice! Few questions (for both you and @mattdurham) but this is coming along nicely :)

UsePathStyle bool `alloy:"use_path_style,attr,optional"`
Region string `alloy:"region,attr,optional"`
SigningRegion string `alloy:"signing_region,attr,optional"`
aws_common_config.Client
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a test for decoding this? I don't think alloy/syntax supports anonymous fields like this, and they have to be named like

type Client struct {
  CommonConfig aws_common_config.Client `alloy:",squash"` 
  UsePathStyle bool                     `alloy:"use_path_style,attr,optional"`
}

// The S3 compatible system used for testing with does not require signing region, so it's fine to be blank
// but when using a proxy to real S3 it needs to be injected.
return aws.Endpoint{
PartitionID: "aws",
Copy link
Member

Choose a reason for hiding this comment

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

This wasn't set before in the old code; does this break anything for remote.s3?

Comment on lines +55 to +57
if o.Region != "" {
configOptions = append(configOptions, aws_config.WithRegion(o.Region))
}
Copy link
Member

Choose a reason for hiding this comment

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

This wasn't set before in the old code; does this break anything for remote.s3?

Comment on lines +51 to +60

// DefaultArguments holds default settings for Arguments.
var DefaultArguments = Arguments{
SecretVersion: "AWSCURRENT",
}

// SetToDefault implements syntax.Defaulter.
func (a *Arguments) SetToDefault() {
*a = DefaultArguments
}
Copy link
Member

Choose a reason for hiding this comment

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

We've started to move away from the pattern of having default arguments as a variable because in some cases (like a default map or slice) memory gets accidentally shared and the defaults get overridden.

Suggested change
// DefaultArguments holds default settings for Arguments.
var DefaultArguments = Arguments{
SecretVersion: "AWSCURRENT",
}
// SetToDefault implements syntax.Defaulter.
func (a *Arguments) SetToDefault() {
*a = DefaultArguments
}
// SetToDefault implements syntax.Defaulter.
func (a *Arguments) SetToDefault() {
*a = Arguments{
SecretVersion: "AWSCURRENT",
}
}

Comment on lines +111 to +114
if s.pollFrequency > 0 {
go s.handleSecretUpdate(ctx)
go s.watcher.run(ctx)
}
Copy link
Member

Choose a reason for hiding this comment

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

There's a bug here: If the poll frequency is set to non-zero after the component was already constructed, then it will never poll.

}
}

func (w *watcher) getSecretAsynchronously(ctx context.Context) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: technically this method gets the secret synchronously. A more fitting name might be getAndSendSecret, as the method gets the secret and then writes the result to a channel.

w.mut.Lock()
defer w.mut.Unlock()

res := w.getSecret(context.Background())
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we're not propagating the context here?

@mattdurham
Copy link
Collaborator

My thought on the default to not polling is related to this comment Pricing per API calls for Secrets Manager is much expensive compared to AWS SSM Parameter Store and AWS S3 API. if it is expensive default to no but allow it to be poll. Also fine with the other direction, default to a low poll rate.

@rfratto
Copy link
Member

rfratto commented Jul 10, 2024

Comparing the pricing of AWS Secrets Manager to S3 does show that secrets manager is more expensive, but perhaps not significantly:

  • AWS Secrets manager: $0.05 per 10,000 API calls
  • S3: $0.004 per 10,000 GET calls

It will take 200,000 polls of secrets for it to cost $1 USD. If we set the polling interval to hourly, and you had 100 Alloy instances all running the same component, that would still only be 73,000 polls per month ($0.36).

It seems like the cost is reasonable enough if we used a 1h polling interval by default. If you're running Alloy in an environment where you need AWS secrets manager (likely Enterprise-like), running Alloy itself probably costs orders of magnitude more money than the secrets manager bill would be.

WDYT @mattdurham?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-to-agent:no PR should NOT be backported to the agent repo. type/docs Docs Squad label across all Grafana Labs repos
Projects
None yet
Development

Successfully merging this pull request may close these issues.

support AWS secret manager
4 participants