/* * The MIT License * * Copyright (c) 2015, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jenkinsci.plugins.docker.workflow; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.common.IdCredentials; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import com.google.common.collect.ImmutableSet; import hudson.Launcher; import hudson.LauncherDecorator; import hudson.model.Computer; import hudson.model.Item; import hudson.model.Node; import hudson.model.Result; import hudson.model.User; import hudson.security.ACL; import hudson.security.ACLContext; import jenkins.model.Jenkins; import jenkins.security.QueueItemAuthenticatorConfiguration; import org.acegisecurity.Authentication; import org.jenkinsci.plugins.docker.commons.credentials.DockerRegistryEndpoint; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.cps.SnippetizerTester; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; import org.jenkinsci.plugins.workflow.steps.BodyInvoker; import org.jenkinsci.plugins.workflow.steps.Step; import org.jenkinsci.plugins.workflow.steps.StepConfigTester; import org.jenkinsci.plugins.workflow.steps.StepContext; import org.jenkinsci.plugins.workflow.steps.StepDescriptor; import org.jenkinsci.plugins.workflow.steps.StepExecution; import static org.jenkinsci.plugins.docker.workflow.DockerTestUtil.assumeNotWindows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.MockAuthorizationStrategy; import org.jvnet.hudson.test.MockQueueItemAuthenticator; import org.jvnet.hudson.test.TestExtension; import org.kohsuke.stapler.DataBoundConstructor; import javax.annotation.Nonnull; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Set; public class RegistryEndpointStepTest { @Rule public JenkinsRule r = new JenkinsRule(); @Issue("JENKINS-51395") @Test public void configRoundTrip() throws Exception { { // Recommended syntax. SnippetizerTester st = new SnippetizerTester(r); RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", null)); step.setToolName(""); st.assertRoundTrip(step, "withDockerRegistry(url: 'https://myreg/') {\n // some block\n}"); step = new RegistryEndpointStep(new DockerRegistryEndpoint(null, "hubcreds")); st.assertRoundTrip(step, "withDockerRegistry(credentialsId: 'hubcreds') {\n // some block\n}"); step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", "mycreds")); step.setToolName("ce"); st.assertRoundTrip(step, "withDockerRegistry(credentialsId: 'mycreds', toolName: 'ce', url: 'https://myreg/') {\n // some block\n}"); } { // Older syntax. WorkflowJob p = r.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry(registry: [url: 'https://docker.my.com/'], toolName: 'irrelevant') {}}", true)); r.buildAndAssertSuccess(p); p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry(registry: [url: 'https://docker.my.com/']) {}}", true)); r.buildAndAssertSuccess(p); p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry([url: 'https://docker.my.com/']) {}}", true)); r.buildAndAssertSuccess(p); // and new, just in case SnippetizerTester is faking it: p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry(url: 'https://docker.my.com/') {}}", true)); r.buildAndAssertSuccess(p); } { // UI form. IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); StepConfigTester sct = new StepConfigTester(r); RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://docker.my.com/", "registryCreds")); step = sct.configRoundTrip(step); DockerRegistryEndpoint registry = step.getRegistry(); assertNotNull(registry); assertEquals("https://docker.my.com/", registry.getUrl()); assertEquals("registryCreds", registry.getCredentialsId()); // TODO check toolName } } @Test public void stepExecutionWithCredentials() throws Exception { assumeNotWindows(); IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); WorkflowJob p = r.createProject(WorkflowJob.class, "prj"); p.setDefinition(new CpsFlowDefinition( "node {\n" + " mockDockerLoginWithEcho {\n" + " withDockerRegistry(url: 'https://my-reg:1234', credentialsId: 'registryCreds') {\n" + " }\n" + " }\n" + "}", true)); WorkflowRun b = r.buildAndAssertSuccess(p); r.assertLogContains("docker login -u me -p pass https://my-reg:1234", r.assertBuildStatusSuccess(r.waitForCompletion(b))); } @Test public void stepExecutionWithCredentialsAndQueueItemAuthenticator() throws Exception { assumeNotWindows(); r.getInstance().setSecurityRealm(r.createDummySecurityRealm()); MockAuthorizationStrategy auth = new MockAuthorizationStrategy() .grant(Jenkins.READ).everywhere().to("alice", "bob") .grant(Computer.BUILD).everywhere().to("alice", "bob") // Item.CONFIGURE implies Credentials.USE_ITEM, which is what CredentialsProvider.findCredentialById // uses when determining whether to include item-scope credentials in the search. .grant(Item.CONFIGURE).everywhere().to("alice"); r.getInstance().setAuthorizationStrategy(auth); IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); String script = "node {\n" + " mockDockerLoginWithEcho {\n" + " withDockerRegistry(url: 'https://my-reg:1234', credentialsId: 'registryCreds') {\n" + " }\n" + " }\n" + "}"; WorkflowJob p1 = r.createProject(WorkflowJob.class, "prj1"); p1.setDefinition(new CpsFlowDefinition(script, true)); WorkflowJob p2 = r.createProject(WorkflowJob.class, "prj2"); p2.setDefinition(new CpsFlowDefinition(script, true)); Map<String, Authentication> jobsToAuths = new HashMap<>(); jobsToAuths.put(p1.getFullName(), User.getById("alice", true).impersonate()); jobsToAuths.put(p2.getFullName(), User.getById("bob", true).impersonate()); QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(jobsToAuths)); // Alice has Credentials.USE_ITEM permission and should be able to use the credential. WorkflowRun b1 = r.buildAndAssertSuccess(p1); r.assertLogContains("docker login -u me -p pass https://my-reg:1234", b1); // Bob does not have Credentials.USE_ITEM permission and should not be able to use the credential. r.assertBuildStatus(Result.FAILURE, p2.scheduleBuild2(0)); } public static class MockLauncherWithEchoStep extends Step { @DataBoundConstructor public MockLauncherWithEchoStep() {} @Override public StepExecution start(StepContext stepContext) { return new Execution(this, stepContext); } public static class Execution extends StepExecution { private static final long serialVersionUID = 1; private final transient MockLauncherWithEchoStep step; Execution(MockLauncherWithEchoStep step, StepContext context) { super(context); this.step = step; } @Override public boolean start() throws Exception { getContext().newBodyInvoker(). withContext(BodyInvoker.mergeLauncherDecorators(getContext().get(LauncherDecorator.class), new Decorator())). withCallback(BodyExecutionCallback.wrap(getContext())). start(); return false; } @Override public void stop(@Nonnull Throwable throwable) { } } private static class Decorator extends LauncherDecorator implements Serializable { private static final long serialVersionUID = 1; @Override public Launcher decorate(Launcher launcher, Node node) { return launcher.decorateByPrefix("echo"); } } @TestExtension public static class DescriptorImpl extends StepDescriptor { @Override public Set<? extends Class<?>> getRequiredContext() { return ImmutableSet.of(Launcher.class); } @Override public String getFunctionName() { return "mockDockerLoginWithEcho"; } @Override public String getDisplayName() { return "Mock Docker Login with Echo"; } @Override public boolean takesImplicitBlockArgument() { return true; } } } }