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

Introduce the aws_s3 BundlePublisher plugin #4355

Merged
merged 7 commits into from
Aug 2, 2023

Conversation

amartinezfayo
Copy link
Member

@amartinezfayo amartinezfayo commented Jul 24, 2023

This PR introduces the aws_s3 BundlePublisher plugin, that puts the current trust bundle of the server in a designated
Amazon S3 bucket, keeping it updated.

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
# # object_key: The object key inside the bucket. Default: "".
# # object_key = "example.org"

# # format: Format in which the trust bundle is stored, <spiffe | jwks | pem>. Default: "".
Copy link

Choose a reason for hiding this comment

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

What format is defaulted to when unset?

Copy link
Member Author

Choose a reason for hiding this comment

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

The plugin requires that a format is explicitly configured, so there is not a default format set.

Copy link

Choose a reason for hiding this comment

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

Do you think a default would make sense here, pem for instance since it's the most well understood format?

Copy link
Member Author

Choose a reason for hiding this comment

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

If we have a default, I would probably suggest to be the spiffe format. PEM does not allow the encoding of JWT authorities, which may be needed. Also, agents can be configured to use spiffe format for the initial SPIRE Server trust bundle.
The idea behind requiring a value here is to avoid the inadvertently misconfiguration of the plugin, but we can revisit that decision.

Copy link

Choose a reason for hiding this comment

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

The idea behind requiring a value here is to avoid the inadvertently misconfiguration of the plugin

No, that's ok. I think this convinced me to be honest.

Comment on lines +53 to +54
config *Config
configMtx sync.RWMutex
Copy link

Choose a reason for hiding this comment

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

Please excuse my ignorance, but how is the config updated dynamically in a way that would require a RWLock?

Copy link
Member Author

Choose a reason for hiding this comment

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

The configuration can be updated at any time by a different thread if the Configure function is called.

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Copy link
Collaborator

@MarcosDY MarcosDY left a comment

Choose a reason for hiding this comment

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

thanks Agustin!! this looks great!

# BundlePublisher "aws_s3" {
# plugin_data {
# # region: AWS region to store the trust bundle. Default: "".
# # region = "us-east-1"
Copy link
Collaborator

Choose a reason for hiding this comment

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

in another examples we have region empty

| region | AWS region to store the trust bundle. | |
| bucket | The Amazon S3 bucket name to which the trust bundle is uploaded. | |
| object_key | The object key inside the bucket. | |
| format | Format in which the trust bundle is stored, &lt;spiffe &vert; jwks &vert; pem&gt;. See [Supported bundle formats](#supported-bundle-formats) for more details. | |
Copy link
Collaborator

Choose a reason for hiding this comment

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

may we add a (required) here?

log hclog.Logger
}

func (p *Plugin) SetLogger(log hclog.Logger) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: since another public functions has a comment, can you add a comment here too?

return &configv1.ConfigureResponse{}, nil
}

// PublishBundle puts the bundle in the specified S3 bucket and key.
Copy link
Collaborator

@MarcosDY MarcosDY Jul 27, 2023

Choose a reason for hiding this comment

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

what is that and key? may we reword this to make sure readers understand that the key is for the published bucket object?

Copy link
Member Author

Choose a reason for hiding this comment

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

The key refers to the object key of the configured bucket name. I'll update the comment.

currentBundle := p.getBundle()
if proto.Equal(req.Bundle, currentBundle) {
// Bundle not changed. No need to publish.
return &bundlepublisherv1.PublishBundleResponse{}, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

is it possible that the bundle does not changed, but someone changed the format? (we are not reloading configs yet but maybe in a future)

Copy link
Member Author

Choose a reason for hiding this comment

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

That's an interesting scenario, good catch. I'll clean the current bundle when Configure() is called, to make sure that the content is refreshed.

func parseAndValidateConfig(c string) (*Config, error) {
config := new(Config)

if err := hcl.Decode(config, c); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

is not region a required field?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes! Thanks, I'll add that check.

if tt.config != nil {
plugintest.Load(t, builtin(p), nil, options...)
require.NoError(t, err)
p.s3Client.(*fakeClient).expectBucket = aws.String(tt.config.Bucket)
Copy link
Collaborator

Choose a reason for hiding this comment

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

may we use newClient directly?

Copy link
Member Author

Choose a reason for hiding this comment

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

newClient is a function. I'll reduce the type assertions and check that succeeded.

}

func testMultiplePuts(t *testing.T, p *Plugin) {
// Test multiple put operations, and check that onlt a call to PutObject is
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Test multiple put operations, and check that onlt a call to PutObject is
// Test multiple put operations, and check that onlt a call to PutObject is
Suggested change
// Test multiple put operations, and check that onlt a call to PutObject is
// Test multiple put operations, and check that only a call to PutObject is

require.NotNil(t, resp)

if tt.testMultiplePuts {
testMultiplePuts(t, p)
Copy link
Collaborator

Choose a reason for hiding this comment

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

it looks to me, like a good option to have another unit test, not a test case inside this functions


func getTestBundle(t *testing.T) *types.Bundle {
const (
certPEM = `-----BEGIN CERTIFICATE-----
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: may we use test fixture here?

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Comment on lines 176 to 188
{
name: "multiple times",
bundle: testBundle,
config: &Config{
AccessKeyID: "access-key-id",
SecretAccessKey: "secret-access-key",
Region: "region",
Bucket: "bucket",
ObjectKey: "object-key",
Format: "spiffe",
},
testMultiplePuts: true,
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

looks like this test case must be removed? since testMutiplePuts is never used

Copy link
Member Author

Choose a reason for hiding this comment

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

Forgot to remove it, thanks!

return &s3.PutObjectOutput{}, nil
}

func TestPublishMultiple(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you move this test before fakeClient declaration?

if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create client configuration: %v", err)
}
s3Client, err := p.hooks.newS3ClientFunc(awsCfg)
Copy link
Collaborator

Choose a reason for hiding this comment

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

looks like env variables are never readed? can you add a test case to validate that we are able to use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESSKEY ?

Copy link
Member Author

Choose a reason for hiding this comment

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

We have pretty much the same situation in all the plugins where we rely on the AWS SDK to use the creds from the env vars if they are available, so it looks like this would be a test of the AWS SDK itself rather than a test of this plugin.

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>

// Configure configures the plugin.
func (p *Plugin) Configure(ctx context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) {
if req == nil {
Copy link
Member

Choose a reason for hiding this comment

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

nit: this check isn't practical since a nil request here would imply the gRPC library is broken

return nil, err
}

if req == nil {
Copy link
Member

Choose a reason for hiding this comment

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

nit: same thing here. gRPC will always pass a request.

return &bundlepublisherv1.PublishBundleResponse{}, nil
}

bundle := bundleformat.NewFormatter(req.Bundle)
Copy link
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
bundle := bundleformat.NewFormatter(req.Bundle)
formatter := bundleformat.NewFormatter(req.Bundle)

if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "could not parse bundle format from configuration: %v", err)
}
// The bundleformat package may support formats that this plugin do not
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// The bundleformat package may support formats that this plugin do not
// The bundleformat package may support formats that this plugin does not

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
@achaurasiaConfluent
Copy link
Contributor

Thank you for doing this, we were looking at implemented this plugin as well.
Can't wait for his plugin to be released.

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Copy link
Collaborator

@MarcosDY MarcosDY left a comment

Choose a reason for hiding this comment

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

LGTM!

@amartinezfayo amartinezfayo merged commit 867a000 into spiffe:main Aug 2, 2023
@amartinezfayo amartinezfayo added this to the 1.7.2 milestone Aug 3, 2023
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.

5 participants