Skip to content

v0.24.0

Compare
Choose a tag to compare
@spladug spladug released this 18 Jan 00:18

New Features

EdgeRequestContext/AuthenticationToken unification

This isn't a new addition, but a breaking rework of authentication context in Baseplate. Authentication token propagation and access is now fully integrated into the edge request context. Authentication tokens are propagated inside the edge context header and the API for applications built on Baseplate is unified. See below for details on how to use this.

For context, here are the original release notes on the authentication system:

Authentication tokens provided by the authentication service can now be automatically propagated between services when making Thrift calls. This allows internal services to securely and accurately understand on whose behalf a given request is being made so they can decide if the requester is authorized for a particular action. The context is passed implicitly, in request headers, so no extra parameters need be added to service IDLs. Baseplate provides APIs for validating and accessing the tokens from within request context and will automatically pass upstream credentials to downstream services without extra work.

This should now be stable and ready for wide use.

Kubernetes authentication backend for Vault secrets fetcher

Baseplate's secret fetcher daemon can now authenticate to Vault using Kubernetes as its proof of identity. This allows Baseplate's Vault sidecar to be used inside Kubernetes pods.

Histogram metrics

You can now use Baseplate's metrics API to send arbitrary integers to StatsD to be collected into Histograms. This is useful for monitoring the distribution of a metric over a time interval.

thrift_pool_from_config

A new helper for creating ThriftConnectionPool objects from configuration without the boilerplate. See "Upgrading" below for details of how to switch over.

Changes

  • Baseplate now emits a new ServerSpanInitialized event in Pyramid applications after it is done initializing a request's span context but before request handler starts. This allows you to hook in to the request lifecycle but have access to all the context attributes you have registered with Baseplate.
  • Baseplate CLI tools now use /usr/bin/env python for compatibility with environments like virtualenv.
  • The setup script (setup.py) now triggers the thrift build process. This makes python setup.py sdist and descendants work cleanly.

Bug Fixes

  • Don't retry publishing forever when v2 event fails to validate.
  • Use correct path for authentication token public key in Vault.

Upgrading

EdgeRequestContext

Most of the workings of this system are under the hood in Baseplate, but you'll need to configure it at application startup. The way this looks is a little different depending on where in the call graph your service sits.

For "landlocked" services inside the cluster that don't interact directly with clients, we assume that upstream services will propagate edge request context (including authentication) to us and all we need to do is verify this. Create an EdgeRequestContextFactory passing in the secrets store for your application and then pass that along to your application's Baseplate integration. For an example Thrift service:

--- a
+++ b/
@@ -90,6 +90,7

+from baseplate.core import EdgeRequestContextFactory
 from baseplate.secrets import secrets_store_from_config

--- a
+++ b
@@ -115,9 +115,15 @@ def make_wsgi_app(app_config):
+    edge_context_factory = EdgeRequestContextFactory(secrets)
+
     handler = Handler()
     processor = {{ cookiecutter.service_name }}.ContextProcessor(handler)
-    event_handler = BaseplateProcessorEventHandler(logger, baseplate)
+    event_handler = BaseplateProcessorEventHandler(
+        logger,
+        baseplate,
+        edge_context_factory=edge_context_factory,
+    )
     processor.setEventHandler(event_handler)
     return processor

Now you can use context.request_context (EdgeRequestContext) and all of its properties to access the context provided by upstream edge services.

For services at the edge, that is ones that interact with external clients directly, we need to collect the right information from the external request and put it into the context to be propagated to downstream services. Create an EdgeRequestContextFactory and use it to make fresh context on each request. For a Pyramid service, the new ServerSpanInitialized event is particularly useful here because we can use any services managed through Baseplate in the event handler.

--- a
+++ b/
@@ -90,6 +90,7

+from baseplate.core import EdgeRequestContextFactory
+from baseplate.integration.pyramid import ServerSpanInitialized
 from baseplate.secrets import secrets_store_from_config

--- a
+++ b
@@ -115,9 +115,15 @@ def make_wsgi_app(app_config):

+    edge_context_factory = EdgeRequestContextFactory(secrets)
+
+    def add_edge_context(event):
+        bearer_token = event.request.headers[...]
+        authn_response = event.request.authn_service.authenticate_oauth2_bearer_token(bearer_token)
+        edge_context = edge_context_factory.new(
+            ... fill in details from the external request ...
+        )
+        edge_context.attach_context(event.request)
+
+    configurator.add_subscriber(add_edge_context, ServerSpanInitialized)

All of this context will be available in your application immediately and will also show up automatically in downstream services.

Both types of services will need access to the authentication service's public key to be able to validate authentication tokens. This is as simple as adding secret/authentication/public-key to the list of secrets managed by your service's secret fetcher daemon.

See the documentation for more details on all this.

thrift_pool_from_config

If your application was doing its own configuration parsing to build a Thrift connection pool, you can now use this function instead.

Before:

cfg = config.parse_config(app_config, {
    "example_service": {
        "endpoint": config.Endpoint,
        "timeout": config.Timespan,
    },
})

...

example_pool = ThriftConnectionPool(
    cfg.example_service.endpoint,
    timeout=cfg.example_service.timeout.total_seconds(),
)

...

After:

example_pool = thrift_pool_from_config(app_config, prefix="example_service.")