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

Validate format of GoogleServiceAccount in CCC #2287

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions operator/pkg/preflight/configconnectorcontextchecker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package preflight
import (
"context"
"fmt"
"regexp"

corev1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/operator/pkg/apis/core/v1beta1"
"github.com/GoogleCloudPlatform/k8s-config-connector/operator/pkg/k8s"
Expand Down Expand Up @@ -52,5 +53,21 @@ func (c *ConfigConnectorContextChecker) Preflight(_ context.Context, o declarati
return fmt.Errorf("spec.billingProject must be set if spec.requestProjectPolicy is set to %v", k8s.BillingProjectPolicy)
}

if err := validateGSAFormat(ccc.Spec.GoogleServiceAccount); err != nil {
return err
}

return nil
}

func validateGSAFormat(gsa string) error {
if gsa == "" { // GoogleServiceAccount is a required field. We do not need to fail here.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: do you mean not a required field? Or do you mean that it's required at the schema level, so this is unreachable?

Either way, I agree that you should just return nil for empty values!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah this code should be unreachable. It is a required field, so empty string will error early during CR creation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

A readability rule of thumb is to always consider the function for its own and always handle the edge cases for itself, it would be easier to maintenance and less error prone if the other parts changed. i.e. somehow we decide to disable the required field check in the CRD level.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If we fail here, that effectively means the GoogleServiceAccount is a required field, even if we mark it as optional in the CRD level. So I think we should not fail here.

return nil
}
validGSAPattern := `^[A-Za-z0-9._%+\-]+@[a-z0-9.\-]+\.gserviceaccount.com$`
Copy link
Collaborator

@justinsb justinsb Jul 19, 2024

Choose a reason for hiding this comment

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

Where did you get this regex from? I found https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts/create which suggested a more restrictive regex for the first bit.

projectID matches description here https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects (which is good)

I'm actually more concerned about us rejecting valid gcpServiceAccount values though, so as long as our regex is too permissive, that's OK IMO!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Where did you get this regex from

I think I got the regex from a back-and-forth discussion between me and AI...

I found https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts/create which suggested a more restrictive regex for the first bit.

Good find! We could certainly make the first bit more restrictive to match Cloud IAM, but I feel leaving it less restrictive has more benefits.

  • If Cloud IAM update the regex in the future, our validation will likely still work.
  • IMO the primary goal is to reject really wrong formats, like if user forgot to replace "${GSA}" with the actual email address, it should get rejected here. (today we don't reject it, and annotate KSA with any string user write to this field)

emailRegex := regexp.MustCompile(validGSAPattern)
if !emailRegex.MatchString(gsa) {
return fmt.Errorf("invalid GoogleServiceAccount format for %q", gsa)
}
return nil
}
46 changes: 46 additions & 0 deletions operator/pkg/preflight/configconnectorcontextchecker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,49 @@ func TestConfigConnectorContextChecker(t *testing.T) {
})
}
}

func TestValidateGSAFormat(t *testing.T) {
tests := []struct {
name string
gsa string
err error
}{
{
name: "empty",
gsa: "",
err: nil,
},
{
name: "valid GSA format",
gsa: "foo@abc.gserviceaccount.com",
err: nil,
},
{
name: "valid GSA format",
gsa: "foo@abc.def.gserviceaccount.com",
err: nil,
},
{
name: "valid GSA format",
gsa: "foo@abc.def.ghi.gserviceaccount.com",
err: nil,
},
{
name: "invalid GSA format",
gsa: "abc",
err: fmt.Errorf("invalid GoogleServiceAccount format for %q", "abc"),
},
{
name: "invalid GSA format",
gsa: "foo@bar.com",
err: fmt.Errorf("invalid GoogleServiceAccount format for %q", "foo@bar.com"),
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := validateGSAFormat(tc.gsa)
asserts.AssertErrorIsExpected(t, err, tc.err)
})
}
}
Loading