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

[JENKINS-48437] Authorization for Docker Endpoint #68

Merged
merged 11 commits into from
Apr 2, 2019

Conversation

Dohbedoh
Copy link
Contributor

@Dohbedoh Dohbedoh commented Mar 28, 2018

JENKINS-48437

Proposed a fix for the issue with the method that retrieves the credentials to get the DockerRegistryToken.
Add implementation accepting a Run as context (and using CredentialsProvider.findCredentialById) that can be later leverage by docker-workflow.

@reviewbybees

@jglick jglick requested a review from stephenc April 5, 2018 01:11
jglick
jglick previously requested changes Apr 5, 2018
Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

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

Not entirely sure what is going on here but this does not look like the right solution to me. Anyway there is no downstream PR in docker-workflow using a timestamped snapshot to consume the newly introduced APIs, much less a test that demonstrates the bug and the effectiveness of the fix using MockQueueItemAuthenticator.

@@ -182,10 +185,35 @@ DockerRegistryToken getToken(Item context) {

// look for subtypes that know how to create a token, such as Google Container Registry
return AuthenticationTokens.convert(DockerRegistryToken.class, firstOrNull(CredentialsProvider.lookupCredentials(
IdCredentials.class, context, Jenkins.getAuthentication(),requirements),
IdCredentials.class, context, Tasks.getAuthenticationOf((Queue.Task)context),requirements),
Copy link
Member

Choose a reason for hiding this comment

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

Smells wrong. As you can see from the Javadoc of that method, it is a prediction, not an indication of what actual authentication is associated with the build—which would be Jenkins.getAuthentication().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an attempt to fix the JENKINS-48437 without needing to update the dependencies in docker-worflow (or other dependents of docker-commons).

domainRequirements);
}

// the directory needs to be outside workspace to avoid prying eyes
Copy link
Member

Choose a reason for hiding this comment

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

If a new overload is indeed necessary, clearly we would want the duplicated code to be factored out into an internal method somehow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jglick How do you want me to factor this out exactly ? I don't see how I could avoid to duplicate the code here unless I use a typing like Object or one of the interface that Run and Item have in common (not sure if doing this would make sense though). So is it worth it ?

Copy link
Member

Choose a reason for hiding this comment

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

I guess it does not matter so long as only one nondeprecated code path remains.

@Dohbedoh
Copy link
Contributor Author

@jglick Are the PR builds deploying snapshots that I can use ? How exactly can I do the following ?:

there is no downstream PR in docker-workflow using a timestamped snapshot to consume the newly introduced APIs

@jglick
Copy link
Member

jglick commented May 11, 2018

Are the PR builds deploying snapshots that I can use?

No; traditionally this was done by a manual mvn deploy. Now that JEP-305 is live, though, I could just “incrementalify” this repository.

@jglick
Copy link
Member

jglick commented May 17, 2018

#70

Copy link
Contributor Author

@Dohbedoh Dohbedoh left a comment

Choose a reason for hiding this comment

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

@jglick Could I get further review ?

domainRequirements);
}

// the directory needs to be outside workspace to avoid prying eyes
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jglick How do you want me to factor this out exactly ? I don't see how I could avoid to duplicate the code here unless I use a typing like Object or one of the interface that Run and Item have in common (not sure if doing this would make sense though). So is it worth it ?

Copy link
Member

@stephenc stephenc left a comment

Choose a reason for hiding this comment

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

Credentials stuff looks correct from 10,000ft

@jglick jglick self-requested a review January 2, 2019 20:20
Copy link
Member

@jglick jglick left a comment

Choose a reason for hiding this comment

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

A few nits. Will have to take @stephenc’s word for it on the appropriateness of the new API calls, since there is no functional test. (I never managed to write one in DockerRegistryEndpointTest, especially now as it would be trying to run docker login, so you would need to either have a mock endpoint accepting the authentication or wrap in a test block step with a LauncherDecorator that just echoes commands rather than actually running them.)

domainRequirements);
}

// the directory needs to be outside workspace to avoid prying eyes
Copy link
Member

Choose a reason for hiding this comment

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

I guess it does not matter so long as only one nondeprecated code path remains.

* specify that build. Otherwise null. If you are scoped to something else, you might
* have to interact with {@link CredentialsProvider} directly.
*/
public @CheckForNull DockerRegistryToken getToken(Run context) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this public if it is only called internally and from tests? Make it default access. If and when some other plugin actually needs to call this (apparently docker-workflow does not), a separate set of PRs can be made which demonstrates the usage.

*
* @param context
* If you are a build step trying to access DockerHub in the context of a build/job,
* specify that build. Otherwise null. If you are scoped to something else, you might
Copy link
Member

Choose a reason for hiding this comment

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

Otherwise null.

Then mark it @CheckForNull.

Copy link
Member

Choose a reason for hiding this comment

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

Done in bc6d2de.

@@ -181,12 +190,44 @@ DockerRegistryToken getToken(Item context) {
// shrug off this error and move on. We are matching with ID anyway.
}

Authentication runAuth = context instanceof Queue.Task ?
Copy link
Member

Choose a reason for hiding this comment

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

Why it getToken(Item) being patched if this method no longer being used?

@@ -72,6 +89,56 @@ public void testParseNullUrlAndImageName() throws Exception {
new DockerRegistryEndpoint(null, null).imageName(null);
}

@Issue("JENKINS-48437")
@Test
public void testGetTokenForItem() throws IOException {
Copy link
Member

Choose a reason for hiding this comment

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

Do we care about this case? IIUC it is deprecated anyway.

globalCredentialsId, "test-global-creds", "user", "password");
CredentialsProvider.lookupStores(j.jenkins).iterator().next().addCredentials(Domain.global(), credentials);

FreeStyleProject item = j.createFreeStyleProject("testGetToken");
Copy link
Member

Choose a reason for hiding this comment

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

BTW @Rule JenkinsRule starts a new instance for each test case, so there is no need to pass a job name argument.


FreeStyleProject item = j.createFreeStyleProject("testGetToken");

SecurityContextHolder.getContext().setAuthentication(User.get("alice").impersonate());
Copy link
Member

Choose a reason for hiding this comment

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

Please rather use ACL.as, even in tests. Just safer.

import hudson.model.*;
import jenkins.model.Jenkins;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.codec.binary.Base64;
Copy link
Member

Choose a reason for hiding this comment

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

BTW there is a standard utility now in Java 8.

@jglick jglick dismissed their stale review January 4, 2019 19:51

stale

@jglick
Copy link
Member

jglick commented Jan 4, 2019

there is no functional test. (I never managed to write one in DockerRegistryEndpointTest

RegistryEndpointStepTest I meant.

…though you could write a downstream functional test demonstrating the complete scenario in ServerEndpointStepTest, using variables as a model (no need for the Jenkins restart in the middle), since in that case the step does more portable stuff which you can test comfortably using MockQueueItemAuthenticator and a user credentials store.

@jglick jglick self-requested a review January 22, 2019 18:29
@mfominov
Copy link

mfominov commented Mar 1, 2019

@jglick any plans to accept this PR or fix this bug by Jenkins core team?

@Dohbedoh
Copy link
Contributor Author

Dohbedoh commented Mar 5, 2019

Adding tests downstream: jenkinsci/docker-workflow-plugin#141

@oleg-nenashev
Copy link
Member

oleg-nenashev commented Mar 5, 2019 via email

@caitlinelfring
Copy link

Hoping to get an update on releasing this? It's currently blocking me from using docker within pipelines with authentication. Thanks in advance!

@jglick jglick removed their request for review March 18, 2019 18:49
Copy link
Member

@dwnusbaum dwnusbaum left a comment

Choose a reason for hiding this comment

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

Ok, I dug through the credential-related APIs to understand the key differences between the old and new code. The summary is that CredentialsProvider.findCredentialById has a fallback to call Credentials.lookupCredentials with ACL.SYSTEM if the Run's authentication has Credentials.USE_ITEM permissions (implied by Job.CONFIGURE in practice) on the Run's Job. Users without Credentials.USE_ITEM still won't be able to use credentials in some cases if authorize-project is in use, so that's something to keep in mind if we see further reports from users.

We could inline that same fallback here to avoid introducing new APIs and deprecating the existing ones, but Credentials.findCredentialById has other fallbacks related to parameters that might matter in some cases so I think this PR is the best approach.

I submitted #74 which subsumes this PR to address the minor nits I noted and to add another case to the new test to make the permissions that matter a bit clearer.

*
* @param context
* If you are a build step trying to access DockerHub in the context of a build/job,
* specify that build. Otherwise null. If you are scoped to something else, you might
Copy link
Member

Choose a reason for hiding this comment

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

Done in bc6d2de.

import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.model.*;
Copy link
Member

Choose a reason for hiding this comment

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

nit: please don't do this in non-test code (and even in test code I tend to avoid it for things other than assertion methods or similar)

try {
requirements = Collections.<DomainRequirement>singletonList(new HostnameRequirement(getEffectiveUrl().getHost()));
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not initiliaze registry URL: ", e);
Copy link
Member

Choose a reason for hiding this comment

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

nit: typo. Also, given that the previous code didn't log here, would it be best for this to not log as well, or at least to log only at Level.FINE (and make the old code log as well for consistency)?

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.

7 participants