Meet industry peers, ask questions, collaborate to find answers, and connect with Googlers who are making the products you use every day.<\/p>", "imageupload.max_uploaded_images_per_upload" : 100, "imageupload.max_uploaded_images_per_user" : 10000, "integratedprofile.connect_mode" : "", "tkb.toc_maximum_heading_level" : "2", "tkb.toc_heading_list_style" : "disc", "sharedprofile.show_hovercard_score" : true, "config.search_before_post_scope" : "community", "tkb.toc_heading_indent" : "15", "p13n.cta.recommendations_feed_dismissal_timestamp" : -1, "imageupload.max_file_size" : 10024, "layout.show_batch_checkboxes" : false, "integratedprofile.cta_connect_slim_dismissal_timestamp" : -1 }, "isAnonymous" : true, "policies" : { "image-upload.process-and-remove-exif-metadata" : true }, "registered" : false, "emailRef" : "", "id" : -1, "login" : "Former Community Member" }, "Server" : { "communityPrefix" : "/qsqph94282", "nodeChangeTimeStamp" : 1732279306521, "tapestryPrefix" : "/gc", "deviceMode" : "DESKTOP", "responsiveDeviceMode" : "DESKTOP", "membershipChangeTimeStamp" : "0", "version" : "24.7", "branch" : "24.7-release", "showTextKeys" : false }, "Config" : { "phase" : "prod", "integratedprofile.cta.reprompt.delay" : 30, "profileplus.tracking" : { "profileplus.tracking.enable" : false, "profileplus.tracking.click.enable" : false, "profileplus.tracking.impression.enable" : false }, "app.revision" : "2409051714-s8dac8f1df8-b80", "navigation.manager.community.structure.limit" : "1000" }, "Activity" : { "Results" : [ ] }, "NodeContainer" : { "viewHref" : "https://www.googlecloudcommunity.com/gc/Cloud-Forums/ct-p/cloud-forums", "description" : "Get answers to your questions and share your knowledge about the Google Cloud.", "id" : "cloud-forums", "shortTitle" : "Cloud Forums", "title" : "Cloud Forums", "nodeType" : "category" }, "Page" : { "skins" : [ "googlecloud", "theme_hermes", "responsive_peak" ], "authUrls" : { "loginUrl" : "https://www.googlecloudcommunity.com/gc/user/userloginpage?dest_url=https%3A%2F%2Fwww.googlecloudcommunity.com%2Fgc%2FData-Analytics%2Fpubsub-and-key-rotation%2Fm-p%2F836746", "loginUrlNotRegistered" : "https://www.googlecloudcommunity.com/gc/user/userloginpage?redirectreason=notregistered&dest_url=https%3A%2F%2Fwww.googlecloudcommunity.com%2Fgc%2FData-Analytics%2Fpubsub-and-key-rotation%2Fm-p%2F836746", "loginUrlNotRegisteredDestTpl" : "https://www.googlecloudcommunity.com/gc/user/userloginpage?redirectreason=notregistered&dest_url=%7B%7BdestUrl%7D%7D" }, "name" : "ForumTopicPage", "rtl" : false, "object" : { "viewHref" : "/gc/Data-Analytics/pubsub-and-key-rotation/td-p/597764", "subject" : "pubsub and key rotation", "id" : 597764, "page" : "ForumTopicPage", "type" : "Thread" } }, "WebTracking" : { "Activities" : { }, "path" : "Community:Google Cloud Community/Category:Google Cloud/Category:Cloud Forums/Board:Data Analytics/Message:Re: pubsub and key rotation" }, "Feedback" : { "targeted" : { } }, "Seo" : { "markerEscaping" : { "pathElement" : { "prefix" : "@", "match" : "^[0-9][0-9]$" }, "enabled" : false } }, "TopLevelNode" : { "viewHref" : "https://www.googlecloudcommunity.com/gc/Google-Cloud/ct-p/google-cloud", "description" : "Find answers, ask questions, and connect with our community of experts.", "id" : "google-cloud", "shortTitle" : "Google Cloud", "title" : "Google Cloud", "nodeType" : "category" }, "Community" : { "viewHref" : "https://www.googlecloudcommunity.com/", "integratedprofile.lang_code" : "en", "integratedprofile.country_code" : "US", "id" : "qsqph94282", "shortTitle" : "Google Cloud Community", "title" : "Google Cloud Community" }, "CoreNode" : { "conversationStyle" : "forum", "viewHref" : "https://www.googlecloudcommunity.com/gc/Data-Analytics/bd-p/cloud-data-analytics", "settings" : { }, "description" : "Want more from your data? Have questions about products like BigQuery, Dataflow, or Pub/Sub? Join the conversation here.", "id" : "cloud-data-analytics", "shortTitle" : "Data Analytics", "title" : "Data Analytics", "nodeType" : "Board", "ancestors" : [ { "viewHref" : "https://www.googlecloudcommunity.com/gc/Cloud-Forums/ct-p/cloud-forums", "description" : "Get answers to your questions and share your knowledge about the Google Cloud.", "id" : "cloud-forums", "shortTitle" : "Cloud Forums", "title" : "Cloud Forums", "nodeType" : "category" }, { "viewHref" : "https://www.googlecloudcommunity.com/gc/Google-Cloud/ct-p/google-cloud", "description" : "Find answers, ask questions, and connect with our community of experts.", "id" : "google-cloud", "shortTitle" : "Google Cloud", "title" : "Google Cloud", "nodeType" : "category" }, { "viewHref" : "https://www.googlecloudcommunity.com/", "description" : "The official home of Google Cloud and Workspace community forums, learning hub, and community blogs.", "id" : "qsqph94282", "shortTitle" : "Google Cloud Community", "title" : "Google Cloud Community", "nodeType" : "Community" } ] } }; LITHIUM.Components.RENDER_URL = "/gc/util/componentrenderpage/component-id/#{component-id}?render_behavior=raw"; LITHIUM.Components.ORIGINAL_PAGE_NAME = 'forums/v5/ForumTopicPage'; LITHIUM.Components.ORIGINAL_PAGE_ID = 'ForumTopicPage'; LITHIUM.Components.ORIGINAL_PAGE_CONTEXT = 'aFo9epFEXHGIlLH40J7g99gabxNkkwNIorWqJ_zrysCCilh1i2SsYm_wHND2y2G1RItbn2z4SnQl5yxxFm4FOByWKMLYdoth1YhojBIaxFuy_U8TcOiwR6Pq8qmv0VenWV9-l5krVBJS4xIutvHoW7UGMhLaf45CC6CpE9yNlxp-OBxx4V3fk_lOxZDGjvbQ5tn3d0sAxjaoGzLmCpRZal_cVGYbQtZx9kiPwen3bfreK2fgNOnX_2FoSX8RzUoLnm0zptF7QywtPIBWTvvWf1guyzeNXlY-029I1P4gcjbVX95hH-glcVD_B_GOiXv38o2CGGToGW46VT_AjGOEP89CJQFAlMLcg3bdjexIcIPYF3RtWcsXot9Bjv7iyYMx'; LITHIUM.Css = { "BASE_DEFERRED_IMAGE" : "lia-deferred-image", "BASE_BUTTON" : "lia-button", "BASE_SPOILER_CONTAINER" : "lia-spoiler-container", "BASE_TABS_INACTIVE" : "lia-tabs-inactive", "BASE_TABS_ACTIVE" : "lia-tabs-active", "BASE_AJAX_REMOVE_HIGHLIGHT" : "lia-ajax-remove-highlight", "BASE_FEEDBACK_SCROLL_TO" : "lia-feedback-scroll-to", "BASE_FORM_FIELD_VALIDATING" : "lia-form-field-validating", "BASE_FORM_ERROR_TEXT" : "lia-form-error-text", "BASE_FEEDBACK_INLINE_ALERT" : "lia-panel-feedback-inline-alert", "BASE_BUTTON_OVERLAY" : "lia-button-overlay", "BASE_TABS_STANDARD" : "lia-tabs-standard", "BASE_AJAX_INDETERMINATE_LOADER_BAR" : "lia-ajax-indeterminate-loader-bar", "BASE_AJAX_SUCCESS_HIGHLIGHT" : "lia-ajax-success-highlight", "BASE_CONTENT" : "lia-content", "BASE_JS_HIDDEN" : "lia-js-hidden", "BASE_AJAX_LOADER_CONTENT_OVERLAY" : "lia-ajax-loader-content-overlay", "BASE_FORM_FIELD_SUCCESS" : "lia-form-field-success", "BASE_FORM_WARNING_TEXT" : "lia-form-warning-text", "BASE_FORM_FIELDSET_CONTENT_WRAPPER" : "lia-form-fieldset-content-wrapper", "BASE_AJAX_LOADER_OVERLAY_TYPE" : "lia-ajax-overlay-loader", "BASE_FORM_FIELD_ERROR" : "lia-form-field-error", "BASE_SPOILER_CONTENT" : "lia-spoiler-content", "BASE_FORM_SUBMITTING" : "lia-form-submitting", "BASE_EFFECT_HIGHLIGHT_START" : "lia-effect-highlight-start", "BASE_FORM_FIELD_ERROR_NO_FOCUS" : "lia-form-field-error-no-focus", "BASE_EFFECT_HIGHLIGHT_END" : "lia-effect-highlight-end", "BASE_SPOILER_LINK" : "lia-spoiler-link", "FACEBOOK_LOGOUT" : "lia-component-users-action-logout", "BASE_DISABLED" : "lia-link-disabled", "FACEBOOK_SWITCH_USER" : "lia-component-admin-action-switch-user", "BASE_FORM_FIELD_WARNING" : "lia-form-field-warning", "BASE_AJAX_LOADER_FEEDBACK" : "lia-ajax-loader-feedback", "BASE_AJAX_LOADER_OVERLAY" : "lia-ajax-loader-overlay", "BASE_LAZY_LOAD" : "lia-lazy-load" }; LITHIUM.noConflict = true; LITHIUM.useCheckOnline = false; LITHIUM.RenderedScripts = [ "prism.js", "UserListActual.js", "KeepSessionAlive.js", "jquery.effects.slide.js", "plugin.js", "brightcove_uploader.js", "PolyfillsAll.js", "TinyMceEditor.js", "DynamicPager.js", "plugin.js", "jquery.delayToggle-1.0.js", "plugin.js", "en.js", "plugin.js", "jquery.ui.widget.js", "jquery.ajax-cache-response-1.0.js", "MessageBodyDisplay.js", "jquery.clone-position-1.0.js", "jquery.function-utils-1.0.js", "DataHandler.js", "Components.js", "MessageEditor.js", "SearchForm.js", "plugin.js", "jquery.blockui.js", "AjaxFeedback.js", "InlineMessageReplyContainer.js", "SearchAutoCompleteToggle.js", "jquery.ui.draggable.js", "DropDownMenu.js", "LiModernizr.js", "jquery.placeholder-2.0.7.js", "jquery.ui.resizable.js", "plugin.js", "jquery.tmpl-1.1.1.js", "Text.js", "Events.js", "ElementQueries.js", "jquery.css-data-1.0.js", "json2.js", "BlockEvents.js", "jquery.viewport-1.0.js", "NoConflict.js", "aws-sdk.js", "FieldSet.js", "Tooltip.js", "MessageViewDisplay.js", "ResizeSensor.js", "theme.js", "plugin.js", "LazyLoadComponent.js", "HelpIcon.js", "en.js", "plugin.js", "jquery.effects.core.js", "plugin.js", "Throttle.js", "plugin.js", "ProductsField.js", "en.js", "plugin.js", "jquery.tools.tooltip-1.2.6.js", "plugin.js", "Cache.js", "TokenInputAutoComplete.js", "PartialRenderProxy.js", "jquery.scrollTo.js", "plugin.js", "en.js", "jquery.position-toggle-1.0.js", "en.js", "jquery.hoverIntent-r6.js", "jquery.ui.position.js", "jquery.ui.dialog.js", "plugin.js", "plugin.js", "ReCaptchaV3.js", "plugin.js", "jquery.ui.mouse.js", "jquery.iframe-transport.js", "EarlyEventCapture.js", "jquery.js", "jquery.json-2.6.0.js", "jquery.ui.core.js", "Video.js", "Link.js", "DeferredImages.js", "Lithium.js", "AutoComplete.js", "CustomEvent.js", "InputEditForm.js", "ActiveCast3.js", "en.js", "en.js", "Forms.js", "InformationBox.js", "jquery.autocomplete.js", "OoyalaPlayer.js", "ForceLithiumJQuery.js", "plugin.js", "plugin.js", "plugin.js", "plugin.js", "jquery.fileupload.js", "plugin.js", "api.js", "Globals.js", "ElementMethods.js", "Namespace.js", "en.js", "jquery.appear-1.1.1.js", "Auth.js", "Placeholder.js", "jquery.lithium-toastmessage.js", "ThreadedDetailMessageList.js", "Sandbox.js", "tinymce-patched.js", "DropDownMenuVisibilityHandler.js", "plugin.js", "jquery.lithium-selector-extensions.js", "InlineMessageReplyEditor.js", "jquery.tokeninput-1.6.2.js", "jquery.iframe-shim-1.0.js", "AjaxSupport.js", "InlineMessageEditor.js", "Loader.js", "CookieBannerAlert.js", "Dialog.js", "en.js", "plugin.js", "SpoilerToggle.js", "en.js" ];(function(){LITHIUM.AngularSupport=function(){function g(a,c){a=a||{};for(var b in c)"[object object]"===Object.prototype.toString.call(c[b])?a[b]=g(a[b],c[b]):a[b]=c[b];return a}var d,f,b={coreModule:"li.community",coreModuleDeps:[],noConflict:!0,bootstrapElementSelector:".lia-page .min-width .lia-content",bootstrapApp:!0,debugEnabled:!1,useCsp:!0,useNg2:!1},k=function(){var a;return function(b){a||(a=document.createElement("a"));a.href=b;return a.href}}();LITHIUM.Angular={};return{preventGlobals:LITHIUM.Globals.preventGlobals, restoreGlobals:LITHIUM.Globals.restoreGlobals,init:function(){var a=[],c=document.querySelector(b.bootstrapElementSelector);a.push(b.coreModule);b.customerModules&&0

Get hands-on experience with 20+ free Google Cloud products and $300 in free credit for new customers.

pubsub and key rotation

Credentials to access google pubsub is rotated every month and there is an overlap of 10 days between old and new keys before the validity of old key expires.  

We have a pubsub subscriber that uses FixedCredentialsProvider to set the credentials (Code is at the bottom). The subscriber is started at the time of application boot up.  I am guessing the subscriber will fail to read the messages once the credentials expire. We dont want to restart our service every month to reinitialize the subscriber with new credentials.  What is the best way to refresh the key without having to restart the service ?  How do we go about implementing a solution that reinitializes the subscriber with new credentials without causing any interruption to messages that are in flight ? 

Subscriber subscriber = Subscriber .newBuilder(projectSubscriptionName, messageReceiver) .setCredentialsProvider(FixedCredentialsProvider.create(getKeyFromVault())).build(); subscriber.startAsync().awaitRunning();

Solved Solved
0 4 457
1 ACCEPTED SOLUTION

Your approach of flipping a flag to stop new messages from being acknowledged by the old subscriber and having them redelivered to the new subscriber can work, especially if your system can tolerate the additional latency of message redelivery. Here are a few points to consider:

  1. Message Ordering: If your system relies on the order of the messages, then redelivery might not be suitable as it might disrupt the order of messages.

  2. Message Processing Time: Also consider how long it takes to process a message. If it takes a long time, you might have messages that were delivered to the old subscriber but have not been processed yet when the flag is flipped. You need to ensure that these messages are not lost or duplicated.

  3. Redelivery Latency: Pub/Sub does not immediately redeliver unacknowledged messages. It uses an exponential backoff for redelivery, which means that it could take a significant amount of time for messages to be redelivered to the new subscriber.

As for your question about GCS, creating a new Storage object for every interaction with GCS would work but it's not very efficient. Creating a new object involves creating a new connection which can be time-consuming.

A better approach would be to keep a cache of Storage objects with different credentials and switch between them when necessary, similar to how you would switch between Subscribers. This would allow you to reuse connections and avoid the overhead of creating a new object for every operation.

Remember to ensure that your system can handle any errors that might occur during the transition period. For example, if you attempt to use an expired Storage object, the operation will fail, so your system needs to be able to handle that error, possibly by retrying the operation with the new credentials.

Also, be aware of Google Cloud's rate limits. If you are creating new connections too frequently, you might hit these rate limits which would cause your requests to be throttled or denied.

View solution in original post

4 REPLIES 4

The Google Pub/Sub client libraries do not provide a built-in way to change the credentials of a running Subscriber. The FixedCredentialsProvider is designed to be, as the name suggests, fixed. Once the credentials are set, they cannot be changed. This means that the Subscriber must be recreated with new credentials if they are changed.

However, you can accomplish the refreshing of credentials without restarting the entire service. Here is a high level approach to achieve this:

  1. Track the Expiration Date: Keep track of when the credentials are going to expire, possibly by saving the expiration date along with the credentials when you get them from the vault.

  2. Create a New Subscriber: Before the old credentials expire (for example, when you're within the 10-day overlap period), create a new Subscriber using the new credentials. It can exist alongside the old Subscriber.

  3. Transition Traffic: Gradually transition traffic from the old Subscriber to the new Subscriber. You can do this by having some kind of flag or switch that determines which Subscriber new incoming messages are directed to. Start by directing a small percentage of messages to the new Subscriber, then gradually increase the percentage over time.

  4. Stop the Old Subscriber: Once all traffic has been successfully transitioned to the new Subscriber, and all in-flight messages have been processed by the old Subscriber, you can stop the old Subscriber.

  5. Cleanup: Finally, cleanup any resources associated with the old Subscriber.

Remember that during this process, both the old and new Subscriber can exist at the same time and process messages independently. This means that your service can continue to process messages without interruption while the credentials are being refreshed.

Note: This is a high level approach. The actual implementation will depend on the specifics of your architecture and infrastructure. You might need to handle things like error handling, logging, and monitoring in your own way.

Also, always ensure that you are adhering to best practices when it comes to managing and rotating secrets. Avoid exposing them in logs or other outputs, and always transport them securely.

Lastly, you may want to consider using Google Cloud's Secret Manager, which is designed for this kind of use case. Secret Manager provides a secure and convenient method for storing API keys, passwords, certificates, and other sensitive data. It supports versioning and automated rotation, which might simplify your credential rotation process.

For the third step Transition traffic, my understanding is that we will not have a control over which subscriber can receive a message.  When you have multiple subscribers ( one with old key and the other one with new key) for the same subscription, incoming messages are randomly distributed to both the subscribers.  How exactly we can direct the traffic to the new subscriber ? Can you please elaborate on this ?  You mentioned about the switch but can you clarify where would the switch be located - in the subscriber or a receiver or somewhere else ? 

Thanks for the detailed explanation. It is very helpful. 

>Start by directing a small percentage of messages to the new Subscriber, then gradually increase the percentage over time.

Any reason why the transition has to be gradual ?  Why cant we have the old subscribers stop accepting the new messages once the scheduled one month is elapsed. Here is the flow I am thinking:

  1. Scheduler that goes off 1st of every month
  2. As soon as the scheduled event is received, bring up the new subscribers using the rotated key.
  3. Flip the flag on the old scheduler to n'ack new messages and those messages will be redelivered to new subscribers eventually
  4. Stop the old subscriber.

Do you see any flaw with this approach ?

We also make use of cloud storage. Here is what we have:

 

this.storage = StorageOptions.newBuilder().setCredentials(keyManager.getGoogleCredentials()).build()
                    .getService();

 

storage object is cached and it will stop working when the credentials expire.  Is it advisable to build a new storage object with the latest credentials for every interaction with GCS.

 

 

Your approach of flipping a flag to stop new messages from being acknowledged by the old subscriber and having them redelivered to the new subscriber can work, especially if your system can tolerate the additional latency of message redelivery. Here are a few points to consider:

  1. Message Ordering: If your system relies on the order of the messages, then redelivery might not be suitable as it might disrupt the order of messages.

  2. Message Processing Time: Also consider how long it takes to process a message. If it takes a long time, you might have messages that were delivered to the old subscriber but have not been processed yet when the flag is flipped. You need to ensure that these messages are not lost or duplicated.

  3. Redelivery Latency: Pub/Sub does not immediately redeliver unacknowledged messages. It uses an exponential backoff for redelivery, which means that it could take a significant amount of time for messages to be redelivered to the new subscriber.

As for your question about GCS, creating a new Storage object for every interaction with GCS would work but it's not very efficient. Creating a new object involves creating a new connection which can be time-consuming.

A better approach would be to keep a cache of Storage objects with different credentials and switch between them when necessary, similar to how you would switch between Subscribers. This would allow you to reuse connections and avoid the overhead of creating a new object for every operation.

Remember to ensure that your system can handle any errors that might occur during the transition period. For example, if you attempt to use an expired Storage object, the operation will fail, so your system needs to be able to handle that error, possibly by retrying the operation with the new credentials.

Also, be aware of Google Cloud's rate limits. If you are creating new connections too frequently, you might hit these rate limits which would cause your requests to be throttled or denied.