Skip to content

Commit

Permalink
Merge branch 'main' into dependabot-auto-merge
Browse files Browse the repository at this point in the history
  • Loading branch information
jtschladen authored Feb 20, 2024
2 parents bdbf248 + 1d51ac7 commit 6758d0c
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 142 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Changelog

Unreleased
~~~~~~~~~~~~~~~~~~~~

1.7.0 - `2024-01-17`
~~~~~~~~~~~~~~~~~~~~
To avoid confusion, the debug app configuration property has been replaced with the standard DEBUG flask app config.
Added ability for new versions of LEMUR_TOKEN_SECRET via the LEMUR_TOKEN_SECRETS config option. This allows for
migration and rotation of the secret.
Expand All @@ -11,6 +14,8 @@ Added DIGICERT_CIS_USE_CSR_FIELDS to control the `use_csr_fields` create certifi
Added Digicert source plugin. Enable it with DIGICERT_SOURCE_ENABLED
Added AWS ACM source plugin. This plugin retreives all certificates for an account and a region.
Added AWS ACM destination plugin. This plugin uploads a certificate to AWS ACM.
Allow updating options field via authority update API.
Fixed a DoS security issue affecting Windows env via the name parameter of the certificate post endpoint.


1.6.0 - `2023-10-23`
Expand Down
1 change: 1 addition & 0 deletions lemur/authorities/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class AuthorityUpdateSchema(LemurInputSchema):
description = fields.String()
active = fields.Boolean(missing=True)
roles = fields.Nested(AssociatedRoleSchema(many=True))
options = fields.String()


class RootAuthorityCertificateOutputSchema(LemurOutputSchema):
Expand Down
5 changes: 4 additions & 1 deletion lemur/authorities/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"""

import json
from typing import Optional

from flask import current_app

Expand All @@ -24,7 +25,7 @@
from lemur.certificates.service import upload


def update(authority_id, description, owner, active, roles):
def update(authority_id, description, owner, active, roles, options: Optional[str] = None):
"""
Update an authority with new values.
Expand All @@ -38,6 +39,8 @@ def update(authority_id, description, owner, active, roles):
authority.active = active
authority.description = description
authority.owner = owner
if options:
authority.options = options

log_service.audit_log("update_authority", authority.name, "Updating authority") # check ui what can be updated
return database.update(authority)
Expand Down
1 change: 1 addition & 0 deletions lemur/authorities/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ def put(self, authority_id, data=None):
description=data["description"],
active=data["active"],
roles=data["roles"],
options=data.get("options")
)


Expand Down
5 changes: 5 additions & 0 deletions lemur/certificates/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ def handle_subject_details(self, data):
if field in data and data[field] is None:
data.pop(field)

# Earlier common_name was a required field and thus in most places it is checked not be None
# Now that it is optional, setting value as empty string instead of None for backward compatibility
if data.get("common_name") is None:
data["common_name"] = ""


class CertificateShortOutputSchema(LemurOutputSchema):
id = fields.Integer()
Expand Down
2 changes: 2 additions & 0 deletions lemur/common/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def text_to_slug(value, joiner="-"):
Normalize a string to a "slug" value, stripping character accents and removing non-alphanum characters.
A series of non-alphanumeric characters is replaced with the joiner character.
"""
if len(value) > 10_000:
raise ValueError("Input value is too long.")

# Strip all character accents: decompose Unicode characters and then drop combining chars.
value = "".join(
Expand Down
4 changes: 2 additions & 2 deletions lemur/plugins/lemur_acme/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ACMEIssuerPlugin(IssuerPlugin):
"name": "acme_url",
"type": "str",
"required": True,
"validation": check_validation(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"),
"validation": check_validation(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$_@.&+-]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"),
"helpMessage": "ACME resource URI. Must be a valid web url starting with http[s]://",
},
{
Expand Down Expand Up @@ -342,7 +342,7 @@ class ACMEHttpIssuerPlugin(IssuerPlugin):
"name": "acme_url",
"type": "str",
"required": True,
"validation": check_validation(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"),
"validation": check_validation(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$_@.&+-]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"),
"helpMessage": "Must be a valid web url starting with http[s]://",
},
{
Expand Down
2 changes: 1 addition & 1 deletion lemur/plugins/lemur_azure_dest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class AzureDestinationPlugin(DestinationPlugin):
"name": "azurePassword",
"type": "str",
"required": True,
"validation": check_validation("[0-9a-zA-Z.:_-~]+"),
"validation": check_validation("[0-9a-zA-Z.:_~-]+"),
"helpMessage": "Tenant password for the Azure Key Vault",
}
]
Expand Down
16 changes: 9 additions & 7 deletions lemur/plugins/lemur_vault_dest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class VaultSourcePlugin(SourcePlugin):
"name": "tokenFileOrVaultRole",
"type": "str",
"required": True,
"validation": check_validation("^([a-zA-Z0-9/._-]+/?)+$"),
"helpMessage": "Must be vaild file path for token based auth and valid role if k8s based auth",
"validation": check_validation("^[a-zA-Z0-9/._-]+/?$"),
"helpMessage": "Must be valid file path for token based auth and valid role if k8s based auth",
},
{
"name": "vaultMount",
Expand Down Expand Up @@ -189,7 +189,7 @@ class VaultDestinationPlugin(DestinationPlugin):
"name": "tokenFileOrVaultRole",
"type": "str",
"required": True,
"validation": check_validation("^([a-zA-Z0-9/._-]+/?)+$"),
"validation": check_validation("^[a-zA-Z0-9/._-]+/?$"),
"helpMessage": "Must be vaild file path for token based auth and valid role if k8s based auth",
},
{
Expand All @@ -203,15 +203,17 @@ class VaultDestinationPlugin(DestinationPlugin):
"name": "vaultPath",
"type": "str",
"required": True,
"validation": check_validation("^(([a-zA-Z0-9._-]+|{(CN|OU|O|L|S|C)})+/?)+$"),
"helpMessage": "Must be a valid Vault secrets path. Support vars: {CN|OU|O|L|S|C}",
"validation": check_validation(
"^([a-zA-Z0-9._-]+|{CN}|{OU}|{O}|{L}|{S}|{C})(/?([a-zA-Z0-9._-]+|{CN}|{OU}|{O}|{L}|{S}|{C}))*$"),
"helpMessage": "Must be a valid Vault secrets path. Support vars: {CN}|{OU}|{O}|{L}|{S}|{C}",
},
{
"name": "objectName",
"type": "str",
"required": False,
"validation": check_validation("^([0-9a-zA-Z.:_-]+|{(CN|OU|O|L|S|C)})+$"),
"helpMessage": "Name to bundle certs under, if blank use {CN}. Support vars: {CN|OU|O|L|S|C}",
"validation": check_validation(
"^([a-zA-Z0-9:._-]+|{CN}|{OU}|{O}|{L}|{S}|{C})(/?([a-zA-Z0-9._-]+|{CN}|{OU}|{O}|{L}|{S}|{C}))*$"),
"helpMessage": "Name to bundle certs under, if blank use {CN}. Support vars: {CN}|{OU}|{O}|{L}|{S}|{C}",
},
{
"name": "bundleChain",
Expand Down
48 changes: 48 additions & 0 deletions lemur/plugins/lemur_vault_dest/tests/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from lemur.plugins.bases import SourcePlugin
import pytest

from lemur.plugins.lemur_vault_dest.plugin import VaultSourcePlugin, VaultDestinationPlugin


class TestSourcePlugin(SourcePlugin):
title = "Test"
Expand Down Expand Up @@ -94,3 +96,49 @@ def test_vault_plugin_input_schema(session):
assert not errors
assert data
assert "plugin_object" in data


@pytest.mark.parametrize("option, value, valid", [
("tokenFileOrVaultRole", "", False),
("vaultPath", "", False),
("tokenFileOrVaultRole", "/leading/slash", True),
("vaultPath", "/leading/slash", False),
("tokenFileOrVaultRole", "{CN}/subs", False),
("vaultPath", "{CN}/subs", False),
("tokenFileOrVaultRole", "/leading/slash", True),
("vaultPath", "/leading/slash", False),
("tokenFileOrVaultRole", "noslash", True),
("vaultPath", "noslash", True),
("tokenFileOrVaultRole", "some/random/file.json", True),
("vaultPath", "some/random/file.json", True),
])
def test_source_options(option, value, valid):
plugin = VaultSourcePlugin()
if valid:
plugin.validate_option_value(option, value)
else:
with pytest.raises(ValueError):
plugin.validate_option_value(option, value)


@pytest.mark.parametrize("option, value, valid", [
("tokenFileOrVaultRole", "", False),
("vaultPath", "", False),
("tokenFileOrVaultRole", "/leading/slash", True),
("vaultPath", "/leading/slash", False),
("tokenFileOrVaultRole", "{CN}/subs", False),
("vaultPath", "{CN}/subs", True),
("tokenFileOrVaultRole", "/leading/slash", True),
("vaultPath", "/leading/slash", False),
("tokenFileOrVaultRole", "noslash", True),
("vaultPath", "noslash", True),
("tokenFileOrVaultRole", "some/random/file.json", True),
("vaultPath", "some/random/file.json", True),
])
def test_dest_options(option, value, valid):
plugin = VaultDestinationPlugin()
if valid:
plugin.validate_option_value(option, value)
else:
with pytest.raises(ValueError):
plugin.validate_option_value(option, value)
34 changes: 34 additions & 0 deletions lemur/tests/test_authorities.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,37 @@ def test_authority_roles(client, session, issuer_plugin):
)
assert resp.status_code == 200
assert len(resp.json["roles"]) == 0


@pytest.mark.parametrize(
"token,authority_number,status",
[
(VALID_ADMIN_HEADER_TOKEN, 100, 200),
],
)
def test_authorities_put_update_options(client, authority_number, token, status):
"""
This test relies on the configuration option ADMIN_ONLY_AUTHORITY_CREATION = True, set in conf.py
"""
data = {'name': f'testauthority{authority_number}', 'owner': '[email protected]',
'common_name': 'testauthority1.example.com', "serial_number": 1,
"validityStart": "2023-07-12T07:00:00.000Z",
"validityEnd": "2050-07-13T07:00:00.000Z",
'plugin': {'slug': 'cryptography-issuer'}}
response = client.post(
api.url_for(AuthoritiesList),
data=json.dumps(data),
headers=token
)
assert response.status_code == status, f"expected code {status}, but actual code was {response.status_code}; error: {response.json}"
response = json.loads(
client.put(
api.url_for(Authorities, authority_id=1), data=json.dumps(
{'owner': '[email protected]',
'description': 'updated',
'roles': [],
'options': json.dumps([{'updated': 'bar'}])}), headers=token
).text
)
for field in ['owner', 'description', 'options']:
assert 'updated' in json.dumps(response[field])
Loading

0 comments on commit 6758d0c

Please sign in to comment.