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

[SECURITY-2053] Prevent plain text credentials of Basic/Digest authentication #105

Merged
merged 13 commits into from
Aug 1, 2022

Conversation

offa
Copy link
Contributor

@offa offa commented Jul 28, 2022

Removes Basic / Digest authentication in order to fix SECURITY-2053.

It has been deprecated and marked for removal in 1.8.19+ anyway (we are 1.15 now).

⚠️ There's no migration done for deprecated basic / digest authentications.

Here are my steps used to reproduce the issue and verify my fix:

Reproduce

  1. Install the current version (1.15 or master)
  2. Global settings --> Http request --> Basic/Digest Authentication
  3. Add an entry (incl. username and password)
  4. Save
  5. Verify the plugins XML file contains data of 3.) in plain text

Verify the fix

  1. Update to version of this PR
  2. Basic/Digest Authentication is no longer available through UI (= no new plain credentials possible)
  3. Saving after an edit will remove any existing plain text credentials from the XML

Reviewer

@jenkinsci/http-request-plugin-developers and security team (@Wadeck @daniel-beck)


  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests - that demonstrates feature works or fixes the issue

@offa offa requested a review from a team as a code owner July 28, 2022 13:05
@offa
Copy link
Contributor Author

offa commented Jul 28, 2022

Related: jenkins-infra/helpdesk#3075

Copy link
Member

@daniel-beck daniel-beck left a comment

Choose a reason for hiding this comment

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

I would expect this approach to result in warnings about unloadable data.

Might work better from an admin POV to remove all UI and make the field of HttpRequestGlobalConfig transient.


private final String keyName;
private final String userName;
private final String password;
Copy link
Member

Choose a reason for hiding this comment

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

FTR the only required change was to change this field's type to hudson.util.Secret (ideally also constructor parameter and getter return value), and adapt related code as needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review!

The class is marked as deprecated for years and the UI indicates that too. I wouldn't consider this worth the effort of an API change / migration. It's a good chance to remove it for good now. There shouldn't be any use case left … hopefully.

I don't have a strong opinion on this though. So if either making fields transient or migration to Secret are more suitable solution to fix this I can update the PR.

Copy link
Member

Choose a reason for hiding this comment

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

Per the developer documentation:

Removing a field requires that you leave the field with the transient keyword. When Jenkins reads the old XML, XStream will set the value for this field, but it will no longer be written back to XML when data is saved.

Copy link
Contributor Author

@offa offa Jul 28, 2022

Choose a reason for hiding this comment

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

Is there any workflow on how to remove a class?

If I understand this correctly transient (all fields?) + UI removal is the preferred solution? PR update is on the way.

Copy link
Member

@daniel-beck daniel-beck Jul 28, 2022

Choose a reason for hiding this comment

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

Is there any workflow on how to remove a class?

Good question, it's not a case I had to handle before -- usually old types are kept around to do migration of old configuration or data to the new types, and then transient + readResolve do the job.

I'd probably keep the class, and make the field holding it transient. Then the idea would be to make the class an empty shell. I'd try implementing #readObject(ObjectInputStream) or an X-Stream Converter (as a nested class called ConverterImpl) to bypass any warnings from default behaviors, and basically do as little as possible.

(You could customize the deserialization of the class holding the field, but that doesn't seem like a great idea for longer-term maintainability.)


Regarding the comment that started this thread, it was only to point out what a minimal fix would have looked like. We're generally not asking maintainers to rip out entire features, and I did not want this PR to give this impression.

@offa offa force-pushed the security-2053-remove_basicdigestauth branch from e24d3b8 to 6a41582 Compare July 28, 2022 17:25
@offa
Copy link
Contributor Author

offa commented Jul 28, 2022

PR updated:

  • Fields are transient
  • UI element removed

@daniel-beck
Copy link
Member

FTR the field I would suggest making transient is e24d3b8#diff-e6782195225aa77d34dd0c71b85cabe588c8a7a632b36fb73198a981d63e3e7cL31.

Copy link
Member

@basil basil left a comment

Choose a reason for hiding this comment

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

Change is incomplete:

  • help-userName.html and help-password.html are not shown in the UI but are still present in the repository
  • We still serialize a <basicDigestAuthentications> when saving an HttpRequestGlobalConfig object; we don't want to serialize anything related to basic digest authentication anymore.
  • Once the above feedback is addressed by marking the basicDigestAuthentications field as transient, its getters and setters should be deprecated, as calling them will update an in-memory object that will not be persisted
  • Once the above feedback is addressed usages of the above need to be audited and deleted; we don't want to be invoking a method that will write an object to memory that will then be discarded when the object is serialized
  • Probably also want to clear any existing deprecated data in memory after reading it from the old version via a readResolve method as described in the developer documentation

@offa
Copy link
Contributor Author

offa commented Jul 28, 2022

FTR the field I would suggest making transient is e24d3b8#diff-e6782195225aa77d34dd0c71b85cabe588c8a7a632b36fb73198a981d63e3e7cL31.

Ah, I see! This would also get rid of the – now empty – list element. 👍

@offa
Copy link
Contributor Author

offa commented Jul 28, 2022

Thanks @basil, I'm going to work on your suggestions.

--

  • Remove help-userName.html and help-password.html
  • transient basicDigestAuthentications (not BasicDigestAuthentication members)
  • Deprecate basicDigestAuthentications getter / setter
  • Audit and delete basicDigestAuthentications usages
  • Clear existing in memory data

@offa offa changed the title [SECURITY-2053] Remove Basic/Digest authentication [SECURITY-2053] Prevent plain text credentials of Basic/Digest authentication Jul 28, 2022
@offa
Copy link
Contributor Author

offa commented Jul 28, 2022

Public:

The setter is used in tests only, getter in tests and filling of the authentication items list box (HttpRequest.DescriptorImpl).

Internal:

Key validation (must be unique) and mapping of key to Authenticator.

@offa offa force-pushed the security-2053-remove_basicdigestauth branch from 1e5984b to 29ff54c Compare July 28, 2022 19:39
@basil
Copy link
Member

basil commented Jul 29, 2022

I would expect this approach to result in warnings about unloadable data. Might work better from an admin POV to remove all UI and make the field of HttpRequestGlobalConfig transient.

From another perspective, perhaps unloadable data might be a good UX? Because that way administrators could choose when to delete the plaintext usernames and passwords from disk, whereas without the new approach we would delete the plaintext usernames and passwords from disk when the job is next saved. That might violate the principle of least surprise by deleting data, and perhaps some users would be upset to lose their usernames and passwords without explicitly asking to do so. What do you think @daniel-beck @offa?

@offa
Copy link
Contributor Author

offa commented Jul 29, 2022

Does unloadable data affect the basic digest elements (= good) or delete the complete data / XML file (= bad)?

In the former case it sounds like a good approach to me 👍.

@basil
Copy link
Member

basil commented Jul 29, 2022

Unloadable data doesn't cause any problems for users, but it results in an administrative warning in the Manage Jenkins UI (which has an Old Data page). The administrator than has to press a button to clear the old data. @daniel-beck seems to think that's a bug, but I think it might be considered a feature because it forces the administrator to think about whether they want to erase the old usernames and passwords before they are lost.

@daniel-beck
Copy link
Member

@daniel-beck seems to think that's a bug, but I think it might be considered a feature because it forces the administrator to think about whether they want to erase the old usernames and passwords before they are lost.

It's usually indicative of a bug in code that was changed incompatibly. Your argument is reasonable though. (IIRC we've never opened up the more user friendly "old data" API to plugins, so it'd be cumbersome to offer a better UX, would need to be a custom admin monitor, and that doesn't seem worth it.) Note however that saving the global config form will drop unloadable data, so it's not really straightforward to retain the data on disk.

@offa
Copy link
Contributor Author

offa commented Aug 1, 2022

Now, how continue here?

@daniel-beck
Copy link
Member

Now, how continue here?

Any of the possible approaches are reasonable and have the mentioned pros and cons. The current state looks reasonable to me as far as the global configuration is concerned.

I would look into what happens to job configurations that have this value configured. AFAIUI, it gets set to https://github.com/offa/http-request-plugin/blob/29ff54c72b606473e7b9fa058d6793ec66da2bb0/src/main/java/jenkins/plugins/http_request/HttpRequest.java#L70 and then provided elsewhere to https://github.com/offa/http-request-plugin/blob/29ff54c72b606473e7b9fa058d6793ec66da2bb0/src/main/java/jenkins/plugins/http_request/HttpRequest.java#L199-L201, which may fail or (unexpectedly) continue to work, at least until a Jenkins restart.

I would also remove https://github.com/offa/http-request-plugin/blob/29ff54c72b606473e7b9fa058d6793ec66da2bb0/src/main/java/jenkins/plugins/http_request/HttpRequest.java#L539-L541 as well.

Also consider https://www.jenkins.io/doc/developer/plugin-development/mark-a-plugin-incompatible/ to warn users about the release this change goes it before they update.

@offa
Copy link
Contributor Author

offa commented Aug 1, 2022

I would look into what happens to job configurations that have this value configured. […] which may fail or (unexpectedly) continue to work, at least until a Jenkins restart.

I did some quick testing with a freestyle job. The job continues to list and use the credentials after switching to this PR version and getting them removed from global config. After a Jenkins restart it jumps to - none - in the jobs UI and build fail with an exception (java.lang.IllegalStateException: Authentication '<Former name here>' doesn't exist anymore).

@offa
Copy link
Contributor Author

offa commented Aug 1, 2022

I would also remove https://github.com/offa/http-request-plugin/blob/29ff54c72b606473e7b9fa058d6793ec66da2bb0/src/main/java/jenkins/plugins/http_request/HttpRequest.java#L539-L541 as well.

Also consider https://www.jenkins.io/doc/developer/plugin-development/mark-a-plugin-incompatible/ to warn users about the release this change goes it before they update.

Both done, assuming the next release version is 1.16.

@daniel-beck
Copy link
Member

I would look into what happens to job configurations that have this value configured. […] which may fail or (unexpectedly) continue to work, at least until a Jenkins restart.

I did some quick testing with a freestyle job. The job continues to list and use the credentials after switching to this PR version and getting them removed from global config. After a Jenkins restart it jumps to - none - in the jobs UI and build fail with an exception (java.lang.IllegalStateException: Authentication '<Former name here>' doesn't exist anymore).

Seems tolerable. Depending on how much time you want to invest here, this can probably be done nicer, e.g. telling admins which jobs are losing their configured authentication. But that's potentially a lot more work.


Since I forgot to mention it before and I don't know whether you are aware of this: Please note that the Jenkins security team will not release a new version of this plugin. We're happy to help resolve the issue of course, and we'll update the security warning to mark future releases as fixed, but releasing this plugin must be done by current or future maintainers.

@offa
Copy link
Contributor Author

offa commented Aug 1, 2022

Seems tolerable. Depending on how much time you want to invest here, this can probably be done nicer, e.g. telling admins which jobs are losing their configured authentication. But that's potentially a lot more work.

Not too much honestly (it's a long time deprecated feature, which isn't used extensively hopefully).

Please note that the Jenkins security team will not release a new version of this plugin.

I know, but at least I can prepare a fix which is confirmed to solve the issue. Btw. thanks a lot at this point @daniel-beck @basil for helping me here!

Copy link
Member

@basil basil left a comment

Choose a reason for hiding this comment

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

Previous review feedback not addressed: no readResolve, deprecated usages still present, etc.

* @deprecated removed without replacement
*/
@Deprecated
private transient List<BasicDigestAuthentication> basicDigestAuthentications = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

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

Usage of deprecated field still present in HttpRequestGlobalConfig#getAuthentications

Copy link
Contributor Author

@offa offa Aug 1, 2022

Choose a reason for hiding this comment

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

I have removed the usage from getAuthentications(). The other two usages are getter and setter.

What is the best scenario for those? If getter / setter are deprecated but still available I'd suggest keeping the related test cases too (it's still some kind of public API), or remove getter, setter, field (?) and related tests altogether.

Copy link
Member

Choose a reason for hiding this comment

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

Please remove all callers of deprecated methods but retain the methods for binary compatibility

@@ -109,7 +109,7 @@ public void setFormAuthentications(
}

public List<Authenticator> getAuthentications() {
return new ArrayList<>(formAuthentications);
return new ArrayList<>(formAuthentications);
Copy link
Contributor Author

@offa offa Aug 1, 2022

Choose a reason for hiding this comment

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

The project is configured to use tabs (Intelij + Eclipse settings). :-(

@basil basil added the breaking label Aug 1, 2022
Copy link
Member

@basil basil left a comment

Choose a reason for hiding this comment

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

Thanks for the PR!

@basil basil merged commit a2cf345 into jenkinsci:master Aug 1, 2022
@offa offa deleted the security-2053-remove_basicdigestauth branch August 2, 2022 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants