From f7b71cd7b779e1e65ca77bd01a879911ac641b01 Mon Sep 17 00:00:00 2001 From: Rahmat Hidayat Date: Tue, 22 Aug 2023 11:34:53 +0700 Subject: [PATCH 1/4] feat: accept base64 encoded credentials in policy.IAM config --- plugins/identities/http.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugins/identities/http.go b/plugins/identities/http.go index 8f30db100..d7066aaa8 100644 --- a/plugins/identities/http.go +++ b/plugins/identities/http.go @@ -2,6 +2,7 @@ package identities import ( "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -36,7 +37,7 @@ type HTTPAuthConfig struct { // google_idtoken Audience string `mapstructure:"audience,omitempty" json:"audience,omitempty" yaml:"audience,omitempty" validate:"required_if=Type google_idtoken"` - // TODO: allow base64 encoded credentials + // CredentialsJSON can be a JSON string or base64 encoded string of the credentials CredentialsJSON string `mapstructure:"credentials_json,omitempty" json:"credentials_json,omitempty" yaml:"credentials_json,omitempty" validate:"required_if=Type google_idtoken"` } @@ -151,8 +152,13 @@ func NewHTTPClient(config *HTTPClientConfig) (*HTTPClient, error) { } if config.Auth.Type == "google_idtoken" { + creds := []byte(config.Auth.CredentialsJSON) + if !isValidJSON(config.Auth.CredentialsJSON) { + creds, _ = base64.StdEncoding.DecodeString(config.Auth.CredentialsJSON) + } + ctx := context.Background() - ts, err := idtoken.NewTokenSource(ctx, config.Auth.Audience, idtoken.WithCredentialsJSON([]byte(config.Auth.CredentialsJSON))) + ts, err := idtoken.NewTokenSource(ctx, config.Auth.Audience, idtoken.WithCredentialsJSON(creds)) if err != nil { return nil, err } @@ -236,3 +242,8 @@ func (c *HTTPClient) setAuth(req *http.Request) { } } } + +func isValidJSON(s string) bool { + var v map[string]interface{} + return json.Unmarshal([]byte(s), &v) == nil +} From 1be97f0c11e9a1910eef4fe97eee73d4572acc7c Mon Sep 17 00:00:00 2001 From: Rahmat Hidayat Date: Tue, 22 Aug 2023 17:01:17 +0700 Subject: [PATCH 2/4] feat: introduce new field for base64 credentials --- plugins/identities/http.go | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/plugins/identities/http.go b/plugins/identities/http.go index d7066aaa8..0c848c321 100644 --- a/plugins/identities/http.go +++ b/plugins/identities/http.go @@ -37,8 +37,11 @@ type HTTPAuthConfig struct { // google_idtoken Audience string `mapstructure:"audience,omitempty" json:"audience,omitempty" yaml:"audience,omitempty" validate:"required_if=Type google_idtoken"` - // CredentialsJSON can be a JSON string or base64 encoded string of the credentials - CredentialsJSON string `mapstructure:"credentials_json,omitempty" json:"credentials_json,omitempty" yaml:"credentials_json,omitempty" validate:"required_if=Type google_idtoken"` + // CredentialsJSON accept a JSON stringified credentials + // Deprecated: CredentialsJSON is deprecated, use CredentialsJSONBase64 instead + CredentialsJSON string `mapstructure:"credentials_json,omitempty" json:"credentials_json,omitempty" yaml:"credentials_json,omitempty"` + // CredentialsJSONBase64 accept a base64 encoded JSON stringified credentials + CredentialsJSONBase64 string `mapstructure:"credentials_json_base64,omitempty" json:"credentials_json_base64,omitempty" yaml:"credentials_json_base64,omitempty"` } // HTTPClientConfig is the configuration required by iam.Client @@ -89,6 +92,13 @@ func (c *HTTPClientConfig) Encrypt() error { } c.Auth.CredentialsJSON = encryptedValue } + if c.Auth.CredentialsJSONBase64 != "" { + encryptedValue, err := c.crypto.Encrypt(c.Auth.CredentialsJSONBase64) + if err != nil { + return err + } + c.Auth.CredentialsJSONBase64 = encryptedValue + } } return nil @@ -127,6 +137,13 @@ func (c *HTTPClientConfig) Decrypt() error { } c.Auth.CredentialsJSON = decryptedValue } + if c.Auth.CredentialsJSONBase64 != "" { + decryptedValue, err := c.crypto.Decrypt(c.Auth.CredentialsJSONBase64) + if err != nil { + return err + } + c.Auth.CredentialsJSONBase64 = decryptedValue + } } return nil @@ -152,9 +169,18 @@ func NewHTTPClient(config *HTTPClientConfig) (*HTTPClient, error) { } if config.Auth.Type == "google_idtoken" { - creds := []byte(config.Auth.CredentialsJSON) - if !isValidJSON(config.Auth.CredentialsJSON) { - creds, _ = base64.StdEncoding.DecodeString(config.Auth.CredentialsJSON) + var creds []byte + switch { + case config.Auth.CredentialsJSONBase64 != "": + v, err := base64.StdEncoding.DecodeString(config.Auth.CredentialsJSONBase64) + if err != nil { + return nil, fmt.Errorf("decoding credentials_json_base64: %w", err) + } + creds = v + case config.Auth.CredentialsJSON != "": + creds = []byte(config.Auth.CredentialsJSON) + default: + return nil, fmt.Errorf("missing credentials for google_idtoken auth") } ctx := context.Background() From 0a1cc6afba5aba1ee615a00395a5135978db373c Mon Sep 17 00:00:00 2001 From: Rahmat Hidayat Date: Wed, 23 Aug 2023 10:27:45 +0700 Subject: [PATCH 3/4] chore: enhance logging --- jobs/revoke_grants_by_user_criteria.go | 84 +++++++++++++++++--------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/jobs/revoke_grants_by_user_criteria.go b/jobs/revoke_grants_by_user_criteria.go index 49d3698d8..32031e62e 100644 --- a/jobs/revoke_grants_by_user_criteria.go +++ b/jobs/revoke_grants_by_user_criteria.go @@ -18,6 +18,7 @@ type RevokeGrantsByUserCriteriaConfig struct { func (h *handler) RevokeGrantsByUserCriteria(ctx context.Context, c Config) error { h.logger.Info(fmt.Sprintf("starting %q job", TypeRevokeGrantsByUserCriteria)) + defer h.logger.Info(fmt.Sprintf("finished %q job", TypeRevokeGrantsByUserCriteria)) var cfg RevokeGrantsByUserCriteriaConfig if err := c.Decode(&cfg); err != nil { @@ -34,12 +35,19 @@ func (h *handler) RevokeGrantsByUserCriteria(ctx context.Context, c Config) erro return fmt.Errorf("initializing IAM client: %w", err) } + h.logger.Info("getting active grants") activeGrants, err := h.grantService.List(ctx, domain.ListGrantsFilter{ Statuses: []string{string(domain.GrantStatusActive)}, }) if err != nil { return fmt.Errorf("listing active grants: %w", err) } + if len(activeGrants) == 0 { + h.logger.Info("no active grants found") + return nil + } + grantIDs := getGrantIDs(activeGrants) + h.logger.Info(fmt.Sprintf("found %d active grants", len(activeGrants)), "grant_ids", grantIDs) grantsForUser := map[string][]*domain.Grant{} // map[account_id][]grant grantsOwnedByUser := map[string][]*domain.Grant{} // map[owner][]grant @@ -54,52 +62,66 @@ func (h *handler) RevokeGrantsByUserCriteria(ctx context.Context, c Config) erro grantsOwnedByUser[g.Owner] = append(grantsOwnedByUser[g.AccountID], &g) } } + h.logger.Info(fmt.Sprintf("found %d unique users", len(uniqueUserEmails)), "emails", uniqueUserEmails) + counter := 0 for email := range uniqueUserEmails { + counter++ + fmt.Println("") + h.logger.Info(fmt.Sprintf("processing user %d/%d", counter, len(uniqueUserEmails)), "email", email) + + h.logger.Info("fetching user details", "email", email) userDetails, err := fetchUserDetails(iamClient, email) if err != nil { - h.logger.Debug("fetching user details", "email", email, "error", err) + h.logger.Error("failed to fetch user details", "email", email, "error", err) continue } + h.logger.Info("checking criteria against user", "email", email, "criteria", cfg.UserCriteria.String()) if criteriaSatisfied, err := evaluateCriteria(cfg.UserCriteria, userDetails); err != nil { - h.logger.Error("checking criteria against user", "email", email, "criteria", cfg.UserCriteria.String(), "error", err) + h.logger.Error("failed to check criteria", "email", email, "error", err) } else if !criteriaSatisfied { - h.logger.Debug("criteria not satisfied", "email", email) + h.logger.Info("criteria not satisfied", "email", email) continue } - // revoking grants with account_id == email - if revokedGrants, err := h.revokeUserGrants(ctx, email); err != nil { - h.logger.Error("revoking user grants", "account_id", email, "error", err) - } else { - revokedGrantIDs := []string{} - for _, g := range revokedGrants { - revokedGrantIDs = append(revokedGrantIDs, g.ID) - } - h.logger.Info("grant revocation successful", "count", len(revokedGrantIDs), "grant_ids", revokedGrantIDs) - } - - // reassigning grants owned by the user to the new owner + h.logger.Info("evaluating new owner", "email", email, "expression", cfg.ReassignOwnershipTo.String()) newOwner, err := h.evaluateNewOwner(cfg.ReassignOwnershipTo, userDetails) if err != nil { h.logger.Error("evaluating new owner", "email", email, "error", err) continue } - successfulGrants, failedGrants := h.reassignGrantsOwnership(ctx, grantsOwnedByUser[email], newOwner) - if len(successfulGrants) > 0 { - successfulGrantIDs := []string{} - for _, g := range successfulGrants { - successfulGrantIDs = append(successfulGrantIDs, g.ID) + h.logger.Info(fmt.Sprintf("evaluated new owner: %q", newOwner), "email", email) + + if !cfg.DryRun { + // revoking grants with account_id == email + h.logger.Info("revoking user active grants", "email", email) + if revokedGrants, err := h.revokeUserGrants(ctx, email); err != nil { + h.logger.Error("failed to reovke grants", "email", email, "error", err) + } else { + revokedGrantIDs := []string{} + for _, g := range revokedGrants { + revokedGrantIDs = append(revokedGrantIDs, g.ID) + } + h.logger.Info("grant revocation successful", "count", len(revokedGrantIDs), "grant_ids", revokedGrantIDs) } - h.logger.Info("grant ownership reassignment successful", "count", len(successfulGrantIDs), "grant_ids", successfulGrantIDs) - } - if len(failedGrants) > 0 { - failedGrantIDs := []string{} - for _, g := range failedGrants { - failedGrantIDs = append(failedGrantIDs, g.ID) + + // reassigning grants owned by the user to the new owner + successfulGrants, failedGrants := h.reassignGrantsOwnership(ctx, grantsOwnedByUser[email], newOwner) + if len(successfulGrants) > 0 { + successfulGrantIDs := []string{} + for _, g := range successfulGrants { + successfulGrantIDs = append(successfulGrantIDs, g.ID) + } + h.logger.Info("grant ownership reassignment successful", "count", len(successfulGrantIDs), "grant_ids", successfulGrantIDs) + } + if len(failedGrants) > 0 { + failedGrantIDs := []string{} + for _, g := range failedGrants { + failedGrantIDs = append(failedGrantIDs, g.ID) + } + h.logger.Error("grant ownership reassignment failed", "count", len(failedGrantIDs), "grant_ids", failedGrantIDs) } - h.logger.Error("grant ownership reassignment failed", "count", len(failedGrantIDs), "grant_ids", failedGrantIDs) } } @@ -181,3 +203,11 @@ func (h *handler) reassignGrantsOwnership(ctx context.Context, ownedGrants []*do return successfulGrants, failedGrants } + +func getGrantIDs(grants []domain.Grant) []string { + var ids []string + for _, g := range grants { + ids = append(ids, g.ID) + } + return ids +} From a05c7f119d26d1064fbfdd177d4abfcf061f118a Mon Sep 17 00:00:00 2001 From: Rahmat Hidayat Date: Wed, 23 Aug 2023 10:53:29 +0700 Subject: [PATCH 4/4] fix: add json validation for credentials --- plugins/identities/http.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/plugins/identities/http.go b/plugins/identities/http.go index 0c848c321..5391cff31 100644 --- a/plugins/identities/http.go +++ b/plugins/identities/http.go @@ -56,7 +56,30 @@ type HTTPClientConfig struct { } func (c *HTTPClientConfig) Validate() error { - return c.validator.Struct(c) + if err := c.validator.Struct(c); err != nil { + return err + } + + if c.Auth.Type == "google_idtoken" { + switch { + case c.Auth.CredentialsJSONBase64 != "": + v, err := base64.StdEncoding.DecodeString(c.Auth.CredentialsJSONBase64) + if err != nil { + return fmt.Errorf("invalid base64 value on credentials_json_base64: %w", err) + } + if !isValidJSON(string(v)) { + return fmt.Errorf("invalid json value on credentials_json_base64") + } + case c.Auth.CredentialsJSON != "": + if !isValidJSON(c.Auth.CredentialsJSON) { + return fmt.Errorf("invalid json value on credentials_json") + } + default: + return fmt.Errorf("missing credentials for google_idtoken auth") + } + } + + return nil } func (c *HTTPClientConfig) Encrypt() error { @@ -270,6 +293,6 @@ func (c *HTTPClient) setAuth(req *http.Request) { } func isValidJSON(s string) bool { - var v map[string]interface{} + var v interface{} return json.Unmarshal([]byte(s), &v) == nil }