Skip to content

Merge in Netflix/lemur main#271

Open
nlopez wants to merge 2354 commits into
masterfrom
wip-apr27
Open

Merge in Netflix/lemur main#271
nlopez wants to merge 2354 commits into
masterfrom
wip-apr27

Conversation

@nlopez

@nlopez nlopez commented Apr 27, 2026

Copy link
Copy Markdown

Summary

Merges upstream Netflix/lemur main (2178 commits) into the Datadog fork. Resolves 89 conflicts, recompiles pinned requirements, fixes a pre-existing missing dep, and ships a migration-tree audit doc. Tests: 864 passed, 18 skipped.

Commits (first-parent)

SHA Description
c87a48, b02878 (pre-existing) Enable ACME issuer plugin in prod Docker image + hunk-header fix
304fa2 Merge upstream/main into wip-apr27 (89 conflicts resolved)
d32e2e Lint + test env fixes (Dockerfile pip syntax, flake8 E203, blank lines, missing cachetools dep)
beb4ad Recompile requirements{,-dev,-docs,-tests}.txt + add migration audit doc

Conflict resolution policy

  • Kept ours for: requirements*.{in,txt} (FIPS pin cryptography==43.0.1), Dockerfile, Makefile, .github/workflows/*, CODEOWNERS, dependabot, local/src/lemur.conf.py, README, docs/administration.rst, tox.ini, setup.py, setup.cfg, lemur/plugins/lemur_azure_dest/plugin.py (Datadog plugin upstream removed).
  • CHANGELOG.rst: combined — Datadog entries on top, upstream below.
  • Code conflicts (~60 files): per-hunk merge preserving Datadog customizations. See "behavior changes" below for the meaningful semantic resolutions.

Behavior changes operators / on-call should know about

⚠️ Anything that runs differently after this PR lands.

CLI re-platformed: flask_scriptclick (FlaskGroup)

lemur/manage.py, lemur/certificates/cli.py, lemur/acme_providers/cli.py, lemur/notifications/cli.py, etc. moved from flask_script @manager.option to click. Invocation patterns like lemur ... <cmd> largely still work, but flag parsing and help output differ. Any runbooks or shell scripts invoking lemur ... subcommands need a smoke test before deploy. Datadog-only options preserved on lemur certificates ...: --region, source-based rotation, multi-cert-per-endpoint loop.

Auth / SSO: IDP_GROUPS_KEYS replaces hard-coded googleGroups

lemur/auth/views.py now reads group claims from a configurable list of keys (IDP_GROUPS_KEYS) rather than always looking at googleGroups. Verify the staging/commercial/government lemur.conf.py (in k8s-resources) sets IDP_GROUPS_KEYS = ["googleGroups"] (or whatever keys we use) before deploy — otherwise group-based authorization may break for SSO logins. The Datadog JWTAuthenticator Vault path is unchanged.

Private-key export: stacked validation

lemur/certificates/views.py keeps the Datadog break-glass requirement and now also runs upstream's new CERTIFICATE_EXPORT_KEY_REQUEST_VALIDATION hook if configured. Both apply (export must satisfy break-glass and the optional validator). No-op unless we set the new config.

AWS endpoint discovery: skip-region-on-failure

lemur/plugins/lemur_aws/plugin.py::get_endpoints now wraps per-region calls in try/except — a failure in one region no longer aborts the whole sync, the failed region is skipped and logged. Affects sandbox/commercial/government source-sync runs; previously an outage in one region's API would fail the entire job.

New AWS plugins available

Two new plugin classes from upstream are now registered: AWSACMSourcePlugin (ACM source) and ACMDestinationPlugin. Not enabled by default — only takes effect if listed in plugin config.

AWS STS role-scoping bug fix

lemur/plugins/lemur_aws/sts.py fixed a latent bug where role was referenced from outside the inner refresh closure. Took upstream. No expected user-visible change unless a refresh path was being silently broken.

ACME modernization

lemur/plugins/lemur_acme/acme_handlers.py switched to ClientV2.get_directory(...) and cryptography.x509.load_pem_x509_certificate for revocation. Functional parity expected; revocation paths should be smoke-tested.

New config callbacks (off unless set)

Upstream added several pluggable validators/filters. None are active without explicit config:

  • ENABLE_AUTOROTATION_FILTER, REISSUE_FILTER, DISABLE_AUTOROTATION_FILTER
  • CERTIFICATE_CREATE_REQUEST_VALIDATION, CERTIFICATE_UPDATE_REQUEST_VALIDATION, CERTIFICATE_EXPORT_KEY_REQUEST_VALIDATION
  • ENABLE_AUTO_ROTATE_ALL_AUTHORITIES
  • AWS_ELB_IGNORE_TAGS, AWS_CLOUDFRONT_IGNORE_TAGS, AWS_IAM_IGNORE_TAGS

New celery task: disable_autorotate_without_endpoint

Upstream added this. Default is no-op — its filter callback returns nothing unless we configure one. Verified the Datadog beat schedule in chart/config/lemur.conf.py (k8s-resources) does not need changes; this task is opt-in.

New API endpoint: PATCH /certificates/{id}/description

Lighter-weight description update — no need to PUT the full cert object. New endpoint; no existing endpoint changed.

Source-sync stays sequential

lemur/sources/cli.py preserved as ours — sequential iteration. Critical (per CLAUDE.md, ref commit 0ad9ef89); parallelizing floods the Celery queue. Source-sync behavior is unchanged.

Datadog SNI cert sync preserved

lemur/sources/service.py keeps the Datadog primary/SNI sync path. Upstream's get-by-name+hash fallback dropped — incompatible with our data shape.

Datadog ECCPRIME256V1 reissue preserved

lemur/certificates/service.py keeps the explicit ECCPRIME256V1 reissue block.

Dependency / runtime updates

  • cryptography==43.0.1 pin preserved (FIPS — gov build will not break).
  • requirements.txt recompiled — version bumps for acme, certbot, boto3/botocore, celery, google-cloud-*, msal, requests, werkzeug, etc. Critical security overrides preserved (urllib3>=2.5.0, jinja2>=3.1.6, python_ldap>=3.4.5, h11>=0.16.0).
  • Added cachetools to requirements-datadog.in — pre-existing missing dep used by lemur/auth/vault_jwt_auth.py since Supporting Vault Implicit OIDC Flow for User UI Login #157 (4acbeb4), never declared. Tests previously couldn't collect without it; commercial/gov runtime worked only via transitive resolution at deploy time.
  • Dockerfile: replaced deprecated pip URL syntax (pip install "file://...#egg=") with modern pip install -e ".[dev]" / [tests].
  • pyproject.toml [tool.flake8] ignores E203 (matches setup.cfg; needed for upstream's black-formatted slices).

Upstream version + Python/Postgres support matrix

  • Bumps __version__ to 1.3.dev0 (was 1.2.dev0).
  • Upstream dropped support for Python 3.9, Ubuntu 20.04, Postgres 12 and 15. Datadog runs Python 3.10 / Ubuntu 22.04 — safe. RDS engine versions in cloud-inventory should be verified to be Postgres 13/14/16.
  • Upstream gained Postgres 16 support.

Migration tree

Zero new schema migrations introduced by the merge. The previous Datadog maintainer kept the migration chain in sync with upstream pre-merge. The single Datadog-only migration (44d67c1988a2_.py, endpoints↔certificates m2m) remains the head, untouched.

Two migration-related files modified by upstream:

  • lemur/migrations/versions/5770674184de_.py: cosmetic f-string conversion only. No DDL change.
  • lemur/migrations/env.py: cleanups + drops compare_type=True for autogenerate. Only affects future lemur db migrate autogen accuracy on column-type changes — does not affect already-applied migrations or runtime. Behavior now matches upstream.

Full audit: docs/upstream-merge-migrations-2026-04-27.md.

CI / build infra

  • .github/workflows/codeql-analysis.yml renamed to codeql.yml (upstream rename).
  • setup.py / setup.cfg kept (upstream removed in favor of pure pyproject.toml); follow-up to migrate fully.
  • .github/workflows/ci.yml kept (upstream removed in favor of newer workflows; ours is wired to Datadog CI).

Test plan

  • make lint-python — clean
  • make test-python — 864 passed, 18 skipped
  • Apply lemur db upgrade against staging — should be a no-op (no new migrations)
  • Smoke test CLI: lemur certificates rotate, lemur source sync, lemur acme dnsproviders — verify click-based invocation works
  • Verify SSO login / group authorization in staging — confirm IDP_GROUPS_KEYS is set in k8s-resources lemur.conf.py
  • Smoke test ACME issue + revoke (sandbox first)
  • Verify per-region AWS source sync handles a deliberately-broken region without aborting the run
  • Verify break-glass private-key export still requires Datadog gating
  • Confirm RDS Postgres engine versions are not 12 or 15 in cloud-inventory

Follow-ups (not in this PR)

  • Migrate fully off setup.py/setup.cfg to pyproject.toml (upstream did; we kept ours).
  • Decide whether to re-add compare_type=True to env.py for stricter autogenerate (default: leave as upstream).
  • aws-sam-translator flagged a pydantic resolver warning during recompile — not blocking lemur, but worth investigating if we use SAM elsewhere.

🤖 Generated with Claude Code

github-actions Bot and others added 30 commits April 2, 2026 20:34
…on-logger-4.1.0

Build(deps): Bump python-json-logger from 4.0.0 to 4.1.0
Bumps [requests](https://github.com/psf/requests) from 2.33.0 to 2.33.1.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](psf/requests@v2.33.0...v2.33.1)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…2.33.1

Build(deps): Bump requests from 2.33.0 to 2.33.1
Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 25.2.0 to 25.3.0.
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](benoitc/gunicorn@25.2.0...25.3.0)

---
updated-dependencies:
- dependency-name: gunicorn
  dependency-version: 25.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
…25.3.0

Build(deps): Bump gunicorn from 25.2.0 to 25.3.0
Bumps [types-pytz](https://github.com/python/typeshed) from 2026.1.1.20260304 to 2026.1.1.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-pytz
  dependency-version: 2026.1.1.20260402
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Thread the endpoint parameter through send_rotation_notification()
and into send_default_notification() via **kwargs, so notification
plugins can include operational context (e.g. which load balancer
is being rotated) in their messages.

This is backwards compatible — the endpoint parameter is optional
and existing plugins that don't use it will simply ignore it via
their **kwargs signature.

Closes Netflix#5346

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After rotating a certificate on an endpoint, attempt to detach the
old certificate via the source plugin's new remove_old_certificate()
method. This prevents old certificates from accumulating on endpoints
that support multiple certificates (e.g. GCP load balancers).

The base SourcePlugin.remove_old_certificate() is a no-op by default,
which is correct for providers like AWS ELBs that handle cert swaps
atomically. Plugins for multi-cert endpoints can override this method.

The cleanup is best-effort — failures are logged as warnings but do
not fail the rotation itself.

Closes Netflix#5348

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that send_rotation_notification passes the endpoint
kwarg through to send_default_notification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests cover:
- remove_old_certificate is called with the old cert after rotation
- removal is skipped when there is no old certificate
- removal failure does not break the rotation itself

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getattr(script_info, 'config') raises AttributeError when the
script_info object doesn't have a config attribute set yet (e.g.
when running 'lemur --help' or 'lemur' without a subcommand).

Use getattr with a default of None to handle this gracefully.

Closes Netflix#4764

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…z-2026.1.1.20260402

Build(deps): Bump types-pytz from 2026.1.1.20260304 to 2026.1.1.20260402
Bumps [types-setuptools](https://github.com/python/typeshed) from 82.0.0.20260210 to 82.0.0.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-setuptools
  dependency-version: 82.0.0.20260402
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…uptools-82.0.0.20260402

Build(deps): Bump types-setuptools from 82.0.0.20260210 to 82.0.0.20260402
Bumps [types-paramiko](https://github.com/python/typeshed) from 4.0.0.20260322 to 4.0.0.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-paramiko
  dependency-version: 4.0.0.20260402
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…amiko-4.0.0.20260402

Build(deps): Bump types-paramiko from 4.0.0.20260322 to 4.0.0.20260402
Bumps [google-cloud-private-ca](https://github.com/googleapis/google-cloud-python) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/googleapis/google-cloud-python/releases)
- [Changelog](https://github.com/googleapis/google-cloud-python/blob/main/packages/google-cloud-documentai/CHANGELOG.md)
- [Commits](googleapis/google-cloud-python@google-cloud-private-ca-v1.17.0...google-cloud-private-ca-v1.18.0)

---
updated-dependencies:
- dependency-name: google-cloud-private-ca
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
…oud-private-ca-1.18.0

Build(deps): Bump google-cloud-private-ca from 1.17.0 to 1.18.0
Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 1.20.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](python/mypy@v1.19.1...v1.20.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Build(deps): Bump mypy from 1.19.1 to 1.20.0
Bumps [types-deprecated](https://github.com/python/typeshed) from 1.3.1.20260130 to 1.3.1.20260402.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-deprecated
  dependency-version: 1.3.1.20260402
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…recated-1.3.1.20260402

Build(deps): Bump types-deprecated from 1.3.1.20260130 to 1.3.1.20260402
Bumps [botocore](https://github.com/boto/botocore) from 1.42.80 to 1.42.83.
- [Commits](boto/botocore@1.42.80...1.42.83)

---
updated-dependencies:
- dependency-name: botocore
  dependency-version: 1.42.83
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…1.42.83

Build(deps): Bump botocore from 1.42.80 to 1.42.83
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.7 to 3.1.8.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](pallets/werkzeug@3.1.7...3.1.8)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-version: 3.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…3.1.8

Build(deps): Bump werkzeug from 3.1.7 to 3.1.8
Bumps [types-protobuf](https://github.com/python/typeshed) from 6.32.1.20260221 to 7.34.1.20260403.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-version: 7.34.1.20260403
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…tobuf-7.34.1.20260403

Build(deps): Bump types-protobuf from 6.32.1.20260221 to 7.34.1.20260403
Bumps [invoke](https://github.com/pyinvoke/invoke) from 2.2.1 to 3.0.0.
- [Commits](pyinvoke/invoke@2.2.1...3.0.0)

---
updated-dependencies:
- dependency-name: invoke
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
IMSterling and others added 21 commits June 10, 2026 07:53
Fix password hashing update
…ions (GHSA-54vg-pfh7-jq95)

Validate CRL and OCSP URLs against RFC1918/loopback/link-local ranges before
issuing outbound requests. Optionally enforce hostname allowlists via
LEMUR_TRUSTED_CRL_HOSTS and LEMUR_TRUSTED_OCSP_HOSTS config keys. Bound
crl_cache to 1000 entries to prevent unbounded cache poisoning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix post-authentication SSRF via CRL/OCSP URLs in uploaded certificates (GHSA-54vg-pfh7-jq95)
Require admin permission on Roles.put, mirroring the existing decorator on
Roles.delete. Previously RoleMemberPermission allowed any role member to
rewrite membership, rename, or modify the role without admin privileges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix privilege escalation in PUT /api/1/roles/<id> (GHSA-x3vf-mgxj-7785)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
 GHSA-v2wp-frmc-5q3v: ACME acme_url SSRF fix + key export audit enhancement
- test_role_put: non-admin users now get 403 (admin-only) before
  schema validation can return 400
- test_role_put_with_data_and_user: role members no longer permitted
  to update roles; expect 403 instead of 200
- test_verify_crl_unreachable: patch socket.gethostbyname so the new
  SSRF hostname validation passes, allowing the mocked Timeout to fire

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ilures-post-ghsa

fix: update tests to reflect GHSA security hardening changes
# Conflicts:
#	Dockerfile
#	lemur/common/utils.py
#	lemur/plugins/lemur_acme/acme_handlers.py
#	lemur/plugins/lemur_acme/challenge_types.py
#	lemur/plugins/lemur_aws/plugin.py
# Conflicts:
#	.github/workflows/lemur-publish-release-pypi.yml
#	CHANGELOG.rst
#	README.rst
#	docs/administration.rst
#	docs/developer/index.rst
#	lemur/certificates/verify.py
#	lemur/certificates/views.py
#	lemur/tests/conftest.py
#	requirements-dev.txt
#	requirements-docs.txt
#	requirements-tests.txt
#	requirements.in
#	requirements.txt
@maperu maperu requested a review from a team as a code owner June 12, 2026 18:29
@datadog-datadog-prod-us1

This comment has been minimized.

maperu added 7 commits June 15, 2026 12:29
# Conflicts:
#	requirements-dev.txt
#	requirements-docs.txt
#	requirements-tests.txt
#	requirements.txt
Upstream merge produced two identical csr_to_string functions in
lemur/common/utils.py (lines 269 and 548). flake8 F811 caught the
redefinition and failed the lint step.

Both definitions had byte-identical bodies, so dropping the second
keeps behavior unchanged.

Fixes the dd-gitlab/test failure on PR #271.
The upstream merge resolution kept the pre-merge requirements*.{in,txt}
files (intentionally, to preserve the cryptography==43.0.1 FIPS pin and
other Datadog overrides). Side effect: it also undid the more recent
master-only dep bumps for cloudflare, azure-keyvault,
google-cloud-compute, and cert-manager.

Master in the meantime advanced to:
- cloudflare==4.3.1  (v3+ SDK; new `from cloudflare import Cloudflare` API)
- cert-manager==3.0.0
- azure-keyvault==4.2.0
- google-cloud-compute==1.46.0

Plugin code in wip-apr27 already uses the new APIs (matching master),
so without the bumps the plugin modules ImportError at test-collection
time and break the CI test job.

Easiest correct path: take master's requirements-base.in /
requirements-datadog.in / requirements.txt verbatim onto wip-apr27.
The cryptography==43.0.1 FIPS pin and the rest of the Datadog overlay
were already pulled forward on master, so we lose nothing.
Recompiled the four requirements*.txt files via pip-compile -U against
the existing requirements*.in inputs to pull category-3 deps (the ones
the upstream merge left lagging because of "kept ours") forward.

Invariants verified post-recompile:
- cryptography==43.0.1 stays pinned (FIPS gov-deployment requirement)
- CVE overrides preserved: urllib3 2.7.0, jinja2 3.1.6, python-ldap
  3.4.7, h11 0.16.0
- cloudflare bumped 4.3.1 -> 5.4.0; the top-level package still exposes
  `Cloudflare` (the lemur_acme plugin import path stays valid)

Notable bumps:
- acme/certbot: 5.4.0 -> 5.6.0
- boto3/botocore: 1.42.73 -> 1.43.31
- gunicorn: 25.1.0 -> 26.0.0
- paramiko: 4.0.0 -> 5.0.0
- requests: 2.32.5 -> 2.34.2
- sentry-sdk: 2.55.0 -> 2.63.0
- werkzeug: 3.1.6 -> 3.1.8

The Datadog-only deps (azure-*, cert-manager, google-cloud-compute,
logmatic-python, simplejson, cachetools) are untouched in their pinning,
just resolved to their latest compatible.
The upstream merge brought in the lemur_gcs destination plugin (Netflix
PR Netflix#5210 'Add Google Cloud Storage destination plugin') but the
necessary google-cloud-storage dep wasn't added to requirements-base.in
during the merge.

lemur_gcs/plugin.py does `from google.cloud import storage` at module
load time, which means pytest's collection step ImportErrors on the
plugin's test files, breaking CI.

Adds google-cloud-storage to requirements-base.in (upstream-shaped dep,
correct file per CLAUDE.md) and recompiles. pip-compile pulled in
google-cloud-core, google-crc32c, and google-resumable-media as
transitives, all consistent with what upstream Netflix resolves to.
These plugins came in from the Netflix upstream merge but were never
present in Datadog's master and we have no use case for them:
- lemur_gcs: GCS bucket destination plugin (upstream commit a2bec2a)
- lemur_google_ca: Google Cloud Private CA issuer plugin (upstream
  commit 60f4f48)

Removes:
- lemur/plugins/lemur_gcs/ (plugin + tests, 462 lines)
- lemur/plugins/lemur_google_ca/ (plugin + tests, 391 lines)
- The two entry-point registrations in pyproject.toml
- google-cloud-storage from requirements-base.in (added in 5cc2a5d
  just to keep lemur_gcs importable; no longer needed)

Recompiles requirements.txt to drop google-cloud-storage and its
transitives (google-cloud-core, google-crc32c, google-resumable-media).

Keep the upstream merge minimal: only pull forward things that
modernize what we already use, not new plugins we don't ship.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants