Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize the awx.main.redact SCM URL sanitizer regex #6254

Merged

Conversation

ryanpetrello
Copy link
Contributor

No description provided.

@softwarefactory-project-zuul
Copy link
Contributor

Build failed.

@ryanpetrello ryanpetrello changed the title WIP: place awx.main.redact with something simpler (and more performant) replace awx.main.redact with something simpler (and more performant) Mar 11, 2020
@ryanpetrello ryanpetrello changed the title replace awx.main.redact with something simpler (and more performant) optimize the awx.main.redact SCM URL sanitizer regex Mar 11, 2020
@ryanpetrello
Copy link
Contributor Author

ryanpetrello commented Mar 11, 2020

@chrismeyersfsu @AlanCoding this is very, very slow if the event_data is really large and contains no actual URLs:

>>> def _x():
...  t = time.time()
...  UriCleaner.remove_sensitive('x' * 150000)
...  print(time.time() - t)
...
>>> _x()
105.74454760551453

We very recently started doing this filtering in the callback receiver to address a bug (though as far as I can tell, this has always been slow for large JSON blobs, and we've always paid this high cost in various API endpoints):

#5812

@softwarefactory-project-zuul
Copy link
Contributor

Build succeeded.

@@ -8,7 +8,7 @@

class UriCleaner(object):
REPLACE_STR = REPLACE_STR
SENSITIVE_URI_PATTERN = re.compile(r'(\w+:(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA
SENSITIVE_URI_PATTERN = re.compile(r'((http|https|ssh):(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA
Copy link
Contributor Author

@ryanpetrello ryanpetrello Mar 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\w+ is too greedy for really large strings that don't contain URLs; if we're parsing basic auth out of URLs, and we're talking about project updates, there's only so many protocols we care about here in practice.

even worse is a really long string that happens to have a <word>: followed by lots of characters that aren't spaces.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this also be git:?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.git-scm.com/docs/git-clone#_git_urls_a_id_urls_a

From what I can tell, the only transports that actually allow username/pass as part of the netloc are http/s and ssh.

Copy link
Contributor

@jakemcdermott jakemcdermott Mar 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would accidentally using a username and password in a git netloc be a common enough mistake? If so, would not including it in this match pattern prevent it from being redacted from error logs, etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's a fair point - I guess somebody could put in something that doesn't work, like:

git://user:pass@host

I'll add it.

@AlanCoding
Copy link
Member

How fast is _x() with this patch?

@ryanpetrello
Copy link
Contributor Author

@AlanCoding it gets much slower the larger the target string is.

Before:

>>> timeit.timeit("import re; re.compile('(\w+:(\/?\/?)[^\s]+)', re.MULTILINE).search('x'*150000)", number=1)
94.14911386510357

After:

timeit.timeit("import re; re.compile('((http|https|ssh):(\/?\/?)[^\s]+)', re.MULTILINE).search('x'*150000)", number=1)
0.0009962848853319883

def test_large_string_performance():
length = 100000
redacted = UriCleaner.remove_sensitive('x' * length)
assert len(redacted) == length
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this really test anything? If it unreasonably takes 100 seconds, doesn't that just mean that the test runs for that long?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@softwarefactory-project-zuul
Copy link
Contributor

Build succeeded.

@ghjm
Copy link
Contributor

ghjm commented Mar 11, 2020

I would suggest using (\w{1,20}:(\/?\/?)[^\s]+).

The reason the performance is bad is that \w+ is not limited in length, so it has to consider all possible lengths up to and including the length of the whole string. (What if the string was a million x's followed by ://foo - it would match.)

If you limit the length that you think a scheme could possibly be (and I'm just arbitrarily picking 20 here), you eliminate the performance problem while maintaining generality with regard to schemes.

timeit.timeit("import re; re.compile('(\w{1,20}:(\/?\/?)[^\s]+)', re.MULTILINE).search('x'*150000)", number=1)
0.014931650017388165

@ryanpetrello
Copy link
Contributor Author

@ghjm that's a great point, and I like it much better. Thanks for the input - I'll adjust this PR.

\w+ is too greedy for large strings that don't contain URLs
@softwarefactory-project-zuul
Copy link
Contributor

Build succeeded.

@softwarefactory-project-zuul
Copy link
Contributor

Build succeeded (gate pipeline).

@softwarefactory-project-zuul softwarefactory-project-zuul bot merged commit 0fd9153 into ansible:devel Mar 11, 2020
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.

5 participants