v0.19.0
New Features
Secure secret storage in Vault
Baseplate now includes support for fetching secrets in a secure, auditable, manner from Hashicorp Vault. A sidecar daemon manages the infrastructure-level authentication with Vault and fetches secrets to a file on disk. Helpers in Baseplate then allow your application to fetch these secrets efficiently from the sidecar daemon with some helpful conventions for versioning/key rotation. This is now the right way to get secret tokens into your application going forward. See the docs for more info.
Error reporting via Sentry
A new span observer integrates Baseplate applications with Raven, the Sentry client. This makes it easy to automatically report exceptions in the application to Sentry. Extra context like the trace ID, git revision of the running application, and context from the incoming request are automatically included in the exception event sent to Sentry. The raven client is also attached to the context object as context.sentry
for sending custom events as desired.
Changes
- Rework message signer API to take advantage of versioned secrets from Vault. The old class-based API is still supported but is now marked deprecated.
- Rename
make_metrics_client
tometrics_client_from_config
andmake_tracing_client
totracing_client_from_config
to be more consistent with other_from_config
functions used throughout Baseplate. The old names are still supported but are now marked as deprecated.
Bug fixes
- Fix exception capture in server spans for the Thrift integration.
- Suppress noisy urllib3 logs from zipkin integration.
- Fix context property attachment in local spans, this caused observers like the metrics observer to be called multiple times on the same server span when local spans were used.
- Fix Python 3 import incompatibilities in
baseplate-healthcheck3
.
Upgrading
Adding Vault integration
In your application's entry point, instantiate a SecretsStore
with secrets_store_from_config
and attach it to the context object with add_to_context
.
def make_processor(app_config):
+ secrets = secrets_store_from_config(app_config)
+ baseplate.add_to_context("secrets", secrets)
Make sure that any time you use a secret you fetch it from the secret store rather than storing fetched secrets in the application. This ensures that as secrets expire and rotate your application stays up to date.
secret = context.secrets.get_versioned("secret/my-service/signing-key")
return make_signature(secret, "This is a signed message!", max_age)
Adding Sentry Integration
Sentry integration is just a matter of configuring and registering the new observer at application startup. For example (from the activity service):
@@ -9,6 +9,7 @@
from baseplate import (
Baseplate,
config,
+ error_reporter_from_config,
metrics_client_from_config,
tracing_client_from_config,
)
@@ -122,6 +123,7 @@ def make_processor(app_config): # pragma: nocover
metrics_client = metrics_client_from_config(app_config)
tracing_client = tracing_client_from_config(app_config)
+ error_reporter = error_reporter_from_config(app_config, __name__)
redis_pool = redis.BlockingConnectionPool.from_url(
cfg.redis.url,
max_connections=cfg.redis.max_connections,
@@ -132,6 +134,7 @@ def make_processor(app_config): # pragma: nocover
baseplate.configure_logging()
baseplate.configure_metrics(metrics_client)
baseplate.configure_tracing(tracing_client)
+ baseplate.configure_error_reporting(error_reporter)
baseplate.add_to_context("redis", RedisContextFactory(redis_pool))
counter = ActivityCounter(cfg.activity.window.total_seconds())
To configure the Sentry client in your application's config file, get a DSN from Sentry (for Reddit crew, contact IO) then see the docs for error_reporter_from_config
for all the available options.
Make sure to add python-raven
or python3-raven
to your development environment.
Updating renamed functions
Update the import and call for make_metrics_client
and make_tracing_client
in your application's entry point with the new names metrics_client_from_config
and tracing_client_from_config
.
Updating usage of the MessageSigner
The previous MessageSigner
API was a class that took a static secret and then provided signature generation and validation methods. The new API is bare functions that take the secret as a parameter. This allows you to refetch the secret from the SecretsStore
immediately before use.
Before:
signer = MessageSigner(b"hunter2")
signature = signer.make_signature("message", max_age)
signer.validate_signature("message", signature)
After:
secret = context.secrets.get_versioned("secret/my-service/my-secret")
signature = signer.make_signature("message", max_age)
...
secret = context.secrets.get_versioned("secret/my-service/my-secret")
validate_signature(secret, "message", signature)
If you have not set up Vault yet, you can also use VersionedSecret.from_simple_secret
to make a fake versioned secret from a static value for use with the new API.