-
Notifications
You must be signed in to change notification settings - Fork 458
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
API Refactor: BatchCreateFederatedBundle RPC #1606
API Refactor: BatchCreateFederatedBundle RPC #1606
Conversation
pkg/server/api/bundle/v1/service.go
Outdated
for _, b := range req.Bundle { | ||
td, err := spiffeid.TrustDomainFromString(b.TrustDomain) | ||
if err != nil { | ||
log.Errorf("Trust domain argument is not a valid SPIFFE ID: %q", b.TrustDomain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trust domain string could be either the host part (e.g. example.org) or the SPIFFE ID URI (e.g. spiffe://example.org). We actually refer to "the name of the trust domain the bundle belongs to": https://github.com/spiffe/spire/blob/master/proto/spire-next/types/bundle.proto#L7
So I would just say in the error that the trust domain argument is not valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and in this king of logs it is better to add a field. with trust domain, instead of be part of error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency in logs we are adding Invalid request: $ERROR_MESSAGE
pkg/server/api/bundle/v1/service.go
Outdated
if err != nil { | ||
log.Errorf("Trust domain argument is not a valid SPIFFE ID: %q", b.TrustDomain) | ||
results = append(results, &bundle.BatchCreateFederatedBundleResponse_Result{ | ||
Status: CreateStatus(codes.InvalidArgument, "trust domain argument is not a valid SPIFFE ID: %q", b.TrustDomain), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same comment about the trust domain string applies here.
pkg/server/api/bundle/v1/service.go
Outdated
for _, b := range req.Bundle { | ||
td, err := spiffeid.TrustDomainFromString(b.TrustDomain) | ||
if err != nil { | ||
log.Errorf("Trust domain argument is not a valid SPIFFE ID: %q", b.TrustDomain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and in this king of logs it is better to add a field. with trust domain, instead of be part of error message.
pkg/server/api/bundle/v1/service.go
Outdated
for _, b := range req.Bundle { | ||
td, err := spiffeid.TrustDomainFromString(b.TrustDomain) | ||
if err != nil { | ||
log.Errorf("Trust domain argument is not a valid SPIFFE ID: %q", b.TrustDomain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency in logs we are adding Invalid request: $ERROR_MESSAGE
pkg/server/api/bundle/v1/service.go
Outdated
results = append(results, &bundle.BatchCreateFederatedBundleResponse_Result{ | ||
Status: CreateStatus(codes.InvalidArgument, "trust domain argument is not a valid SPIFFE ID: %q", b.TrustDomain), | ||
}) | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: instead to have too many continue, you can create a method to createFederatedBundles
annd return errors,
then in Batch method you can basically parse that error into status
and create result, that will make it cleaner
pkg/server/api/bundle/v1/service.go
Outdated
} | ||
|
||
if s.td.Compare(td) == 0 { | ||
log.Errorf("%q is this server own trust domain", td.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from here that we have a td, we may add it as a field to all logs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log.Errorf("%q is this server own trust domain", td.String()) | |
log.Error("Creating a federated bundle for the server's own trust domain is not allowed") |
pkg/server/api/bundle/v1/service.go
Outdated
} | ||
applyBundleMask(protoBundle, req.OutputMask) | ||
|
||
log.Infof("Bundle created successfully for trust domain: %q", td.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure we want an info log here, it can spam if we add several bundles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is probably ok? It shouldn't be too noisy considering how often this is expected to happen.
pkg/server/api/bundle/v1/service.go
Outdated
|
||
return out, nil | ||
// CreateStatus creates a proto Status | ||
func CreateStatus(code codes.Code, format string, a ...interface{}) *types.Status { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont think here is a good place for this method you have status methods on api: https://github.com/spiffe/spire/blob/master/pkg/server/api/status.go#L12
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right! I forgot to remove it after it got merged
expectedLogMsgs []spiretest.LogEntry | ||
dsError error | ||
}{ | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can add a test case where you create more than one bundle and someone fails and another not, and verify that we always keep order, and expected logs+results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another test case to add is a mask with all false, what happens in that case? and when it is nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another test case when bundles is empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can add a test case where you create more than one bundle and someone fails and another not, and verify that we always keep order, and expected logs+results
This scenario is covered by "Create fails if bundle already exists"
another test case to add is a mask with all false, what happens in that case? and when it is nil?
Will do
pkg/server/api/bundle/v1/service.go
Outdated
return nil, status.Errorf(codes.Unimplemented, "method BatchCreateFederatedBundle not implemented") | ||
log := rpccontext.Logger(ctx) | ||
|
||
results := []*bundle.BatchCreateFederatedBundleResponse_Result{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens when req is emtpy? must we do nothing? or return an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, good question. I think I'd lean towards returning an empty response instead of failing.
Also, I'd probably recommend either initializing this slice to nil, or preallocating the slice to the number of incoming bundles.
require.NotNil(t, resp) | ||
spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectedLogMsgs) | ||
|
||
require.Equal(t, len(tt.expectedResults), len(resp.Results)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of this you can use spiretest.RequireProtoEqual
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and you have have an expected where you play with mask, to be sure response has exactly what we want
pkg/server/api/bundle/v1/service.go
Outdated
}) | ||
continue | ||
} | ||
resp, err := s.ds.CreateBundle(ctx, &datastore.CreateBundleRequest{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think before we call create bundle we must validate that rootca and public keys are valid
pkg/server/api/bundle.go
Outdated
rootCas := []*common.Certificate{} | ||
for _, certificate := range b.X509Authorities { | ||
rootCas = append(rootCas, &common.Certificate{ | ||
DerBytes: certificate.Asn1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be validating that the incoming ASN.1 certificate data is well formed. See the helpers added in the AppendBundle PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I'm thinking about moving the helpers added in AppendBundle PR to this file. And then, modify AppendBundle to use ProtoToBundle
instead of the individual helpers.
Sounds good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, although the api
package is where we've been sticking the *ToProto
and ProtoTo*
methods.
pkg/server/api/bundle.go
Outdated
|
||
jwtSigningKeys := []*common.PublicKey{} | ||
for _, key := range b.JwtAuthorities { | ||
jwtSigningKeys = append(jwtSigningKeys, &common.PublicKey{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be validating that the incoming key id and pkix bytes. See the helpers added in the AppendBundle PR.
pkg/server/api/bundle/v1/service.go
Outdated
return nil, status.Errorf(codes.Unimplemented, "method BatchCreateFederatedBundle not implemented") | ||
log := rpccontext.Logger(ctx) | ||
|
||
results := []*bundle.BatchCreateFederatedBundleResponse_Result{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, good question. I think I'd lean towards returning an empty response instead of failing.
Also, I'd probably recommend either initializing this slice to nil, or preallocating the slice to the number of incoming bundles.
pkg/server/api/bundle/v1/service.go
Outdated
} | ||
|
||
if s.td.Compare(td) == 0 { | ||
log.Errorf("%q is this server own trust domain", td.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log.Errorf("%q is this server own trust domain", td.String()) | |
log.Error("Creating a federated bundle for the server's own trust domain is not allowed") |
pkg/server/api/bundle/v1/service.go
Outdated
if s.td.Compare(td) == 0 { | ||
log.Errorf("%q is this server own trust domain", td.String()) | ||
results = append(results, &bundle.BatchCreateFederatedBundleResponse_Result{ | ||
Status: CreateStatus(codes.InvalidArgument, "%q is this server own trust domain", td.String()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Status: CreateStatus(codes.InvalidArgument, "%q is this server own trust domain", td.String()), | |
Status: CreateStatus(codes.InvalidArgument, "creating a federated bundle for the server's own trust domain (%s) s not allowed", td.String()), |
pkg/server/api/bundle/v1/service.go
Outdated
if status.Code(err) == codes.AlreadyExists { | ||
log.Error("Bundle already exists") | ||
results = append(results, &bundle.BatchCreateFederatedBundleResponse_Result{ | ||
Status: CreateStatus(codes.AlreadyExists, "bundle already exists"), | ||
}) | ||
continue | ||
} | ||
if err != nil { | ||
log.WithError(err).Error("Unable to create bundle") | ||
results = append(results, &bundle.BatchCreateFederatedBundleResponse_Result{ | ||
Status: CreateStatus(codes.Internal, "unable to create bundle: %v", err), | ||
}) | ||
continue | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: switches are a nice way to group these to related conditions
pkg/server/api/bundle/v1/service.go
Outdated
} | ||
applyBundleMask(protoBundle, req.OutputMask) | ||
|
||
log.Infof("Bundle created successfully for trust domain: %q", td.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is probably ok? It shouldn't be too noisy considering how often this is expected to happen.
@@ -451,3 +572,37 @@ func setupServiceTest(t *testing.T) *serviceTest { | |||
|
|||
return test | |||
} | |||
|
|||
func getValidBundle(td spiffeid.TrustDomain) *types.Bundle { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: get
implies you are obtaining something that already exists
func getValidBundle(td spiffeid.TrustDomain) *types.Bundle { | |
func makeBundle(td spiffeid.TrustDomain) *types.Bundle { |
} | ||
} | ||
|
||
func getValidCommonBundle(t *testing.T, td spiffeid.TrustDomain) *common.Bundle { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: get implies you are obtaining something that already exists
func getValidCommonBundle(t *testing.T, td spiffeid.TrustDomain) *common.Bundle { | |
func makeCommonBundle(t *testing.T, td spiffeid.TrustDomain) *common.Bundle { |
Thank you all for the feedback! I think I fixed all the comments. Please let me know if I missed any of them 🙂 |
pkg/server/api/bundle.go
Outdated
} | ||
|
||
td, err := spiffeid.TrustDomainFromString(b.TrustDomain) | ||
if err != nil && b.TrustDomain != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TrustDomain should be a required field on the bundle. I'm curious what case you hit where there wasn't one set that wasn't an error condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the AppendBundle RPC the InputMask
is applied before calling ProtoToBundle(...)
. So, it could be a case where TrustDomain
flag is not set in the input mask and it is passed as empty to ProtoToBundle
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can apply mask after parsing? but here I got a question...
In actual scenary we does not validate fields that are not going to be updated, that make sense because if you dont want to update something you will just nil it on proto when sending it.
So maybe actuall appr calling mask before parsing make sense, but is that waht we want? (I'm scarred about issues because we get too permisive on our parsing)
pkg/server/api/bundle.go
Outdated
} | ||
|
||
if key.KeyId == "" { | ||
return nil, errors.New("missing KeyId") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, errors.New("missing KeyId") | |
return nil, errors.New("missing key ID") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Thanks, @marcosy !
Signed-off-by: Marcos Yedro <marcosyedro@gmail.com>
7628207
to
69aa401
Compare
Pull Request check list
Affected functionality
API Refactor
Description of change
Implements
BatchCreateFederatedBundle()
RPCWhich issue this PR fixes
Fixes: #1553