Skip to content

Commit

Permalink
[JENKINS-36432 followup] Add tests of migration and reading plain-text
Browse files Browse the repository at this point in the history
- The reading of plain-text from the on-disk XML is for e.g. people using chef/puppet scripts to pre-populate their JENKINS_HOME
- The reading of plain-text from the CLI create credentials command is an obvious additional use case. This also applies to the REST API for credentials creation, but as the REST API is already tested in the credentials plugin I do not see any value in adding a specific test for that scenario.
- The migration of legacy data intact is also an obvious requirement
  • Loading branch information
stephenc committed Sep 23, 2016
1 parent 505f4e4 commit ce212cc
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 1 deletion.
@@ -1,7 +1,7 @@
/*
* The MIT License
*
* Copyright 2013 Jesse Glick, Stephen Connolly and CloudBees, Inc.
* Copyright 2013-2016 Jesse Glick, Stephen Connolly and 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
Expand Down
@@ -0,0 +1,190 @@
/*
* The MIT License
*
* Copyright 2016 Stephen Connolly and 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.plaincredentials;

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.SecretBytes;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.cli.CreateCredentialsByXmlCommand;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import hudson.cli.CLICommandInvoker;
import hudson.security.ACL;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.recipes.LocalData;

import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;

public class SecretBytesTest {

@Rule
public JenkinsRule r = new JenkinsRule();

/**
* Verifies that {@link SecretBytes} will treat a Base64 encoded plain text content as the content to be encrypted
* with the instance's secret key which gets applied when the {@link FileCredentialsImpl} is written to disk.
* @throws Exception if things go wrong.
*/
@Test
@LocalData
public void loadUnencrypted() throws Exception {
// these are the magic strings
assumeThat(Base64.encodeBase64("This is Base64 encoded plain text\n".getBytes("UTF-8")), is(
"VGhpcyBpcyBCYXNlNjQgZW5jb2RlZCBwbGFpbiB0ZXh0Cg==".getBytes("US-ASCII")));

// first check that the file on disk contains the unencrypted text
assertThat(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "credentials.xml")),
containsString("VGhpcyBpcyBCYXNlNjQgZW5jb2RlZCBwbGFpbiB0ZXh0Cg=="));

// get the credential instance under test
FileCredentials c = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
FileCredentials.class,
r.jenkins,
ACL.SYSTEM,
(List<DomainRequirement>) null
),
CredentialsMatchers.withId("secret-file")
);

// we have the credential instance we think we should have
assertThat(c, notNullValue());
assertThat(c.getFileName(), is("secret.txt"));
assertThat(c.getDescription(), is("a line"));

// now check that the content has been read correctly
assertThat(IOUtils.toString(c.getContent(), "UTF-8"), is("This is Base64 encoded plain text\n"));

// now when we re-save the credentials this should encrypt with the instance's secret key
SystemCredentialsProvider.getInstance().save();
assertThat(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "credentials.xml")),
not(containsString("VGhpcyBpcyBCYXNlNjQgZW5jb2RlZCBwbGFpbiB0ZXh0Cg==")));
}

/**
* Verifies that {@link SecretBytes} will treat a Base64 encoded plain text content as the content to be encrypted
* with the instance's secret key which gets applied when the {@link FileCredentialsImpl} is written to disk.
* @throws Exception if things go wrong.
*/
@Test
@LocalData
public void migrateLegacyData() throws Exception {
// first check that the file on disk contains the legacy format
assertThat(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "credentials.xml")),
allOf(containsString("</data>"), not(containsString("</secretBytes>"))));

// get the credential instance under test
FileCredentials c = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
FileCredentials.class,
r.jenkins,
ACL.SYSTEM,
(List<DomainRequirement>) null
),
CredentialsMatchers.withId("legacyData")
);

// we have the credential instance we think we should have
assertThat(c, notNullValue());
assertThat(c.getFileName(), is("secret.txt"));
assertThat(c.getDescription(), is("credential using legacy data format"));

// now check that the content has been converted
assertThat(IOUtils.toString(c.getContent(), "UTF-8"), is("This is a secret file from legacy encryption\n"));

// now when we re-save the credentials this should persist in the new format
SystemCredentialsProvider.getInstance().save();
assertThat(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "credentials.xml")),
allOf(not(containsString("</data>")), containsString("</secretBytes>")));
}

@Test
public void createFileCredentialsByXml() throws Exception {
// get the credential instance doesn't exist yet
FileCredentials c = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
FileCredentials.class,
r.jenkins,
ACL.SYSTEM,
(List<DomainRequirement>) null
),
CredentialsMatchers.withId("secret-file")
);
assertThat(c, nullValue());

// create the credentials
CreateCredentialsByXmlCommand cmd = new CreateCredentialsByXmlCommand();
CLICommandInvoker invoker = new CLICommandInvoker(r, cmd);
assertThat(invoker.withStdin(new ByteArrayInputStream(("<?xml version='1.0' encoding='UTF-8'?>\n"
+ "<org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl>\n"
+ " <scope>GLOBAL</scope>\n"
+ " <id>secret-file</id>\n"
+ " <description>a line</description>\n"
+ " <fileName>secret.txt</fileName>\n"
+ " <secretBytes>VGhpcyBpcyBCYXNlNjQgZW5jb2RlZCBwbGFpbiB0ZXh0Cg==</secretBytes>\n"
+ "</org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl>\n")
.getBytes(Charset.forName("UTF-8"))))
.invokeWithArgs("system::system::jenkins", "_"),
succeededSilently());

// get the credential instance under test
c = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
FileCredentials.class,
r.jenkins,
ACL.SYSTEM,
(List<DomainRequirement>) null
),
CredentialsMatchers.withId("secret-file")
);

// we have the credential instance we think we should have
assertThat(c, notNullValue());
assertThat(c.getFileName(), is("secret.txt"));
assertThat(c.getDescription(), is("a line"));

// now check that the content has been read correctly
assertThat(IOUtils.toString(c.getContent(), "UTF-8"), is("This is Base64 encoded plain text\n"));

// now when we re-save the credentials this should encrypt with the instance's secret key
SystemCredentialsProvider.getInstance().save();
assertThat(FileUtils.readFileToString(new File(r.jenkins.getRootDir(), "credentials.xml")),
not(containsString("VGhpcyBpcyBCYXNlNjQgZW5jb2RlZCBwbGFpbiB0ZXh0Cg==")));
}

}
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<hudson/>
@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<com.cloudbees.plugins.credentials.SystemCredentialsProvider plugin="credentials@2.1.5">
<domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash">
<entry>
<com.cloudbees.plugins.credentials.domains.Domain>
<specifications/>
</com.cloudbees.plugins.credentials.domains.Domain>
<java.util.concurrent.CopyOnWriteArrayList>
<org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl plugin="plain-credentials@1.3-SNAPSHOT">
<scope>GLOBAL</scope>
<id>secret-file</id>
<description>a line</description>
<fileName>secret.txt</fileName>
<secretBytes>VGhpcyBpcyBCYXNlNjQgZW5jb2RlZCBwbGFpbiB0ZXh0Cg==</secretBytes>
</org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl>
</java.util.concurrent.CopyOnWriteArrayList>
</entry>
</domainCredentialsMap>
</com.cloudbees.plugins.credentials.SystemCredentialsProvider>
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<hudson/>
@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<com.cloudbees.plugins.credentials.SystemCredentialsProvider plugin="credentials@2.1.5">
<domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash">
<entry>
<com.cloudbees.plugins.credentials.domains.Domain>
<specifications/>
</com.cloudbees.plugins.credentials.domains.Domain>
<java.util.concurrent.CopyOnWriteArrayList>
<org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl plugin="plain-credentials@1.2">
<scope>GLOBAL</scope>
<id>legacyData</id>
<description>credential using legacy data format</description>
<fileName>secret.txt</fileName>
<data>DMG4Q+h/SWXBvMJQy7vMACNZgmCVggCvjP5qeNqsAQo8o7dC69vHlHOjReE1MDIr</data>
</org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl>
</java.util.concurrent.CopyOnWriteArrayList>
</entry>
</domainCredentialsMap>
</com.cloudbees.plugins.credentials.SystemCredentialsProvider>
@@ -0,0 +1 @@
479791e961495d820f337e3afd8fcb70aa36d8c4738093de71d13f7fe6ed6fa0
@@ -0,0 +1 @@
85f522c34c8efc07970009211478cd1e0aa2c85e75bae2b5ff4396d38d6a97dd03717f48447313597c3e3c8ede2e7fb131c3e383366dea755f39af06d0fcf9c2ac6b9fbadca31180f8346372c5e78d4b9bc790393de47f36170a0d30da7aa022c3ccb8ccf7a073dc1a220877fe5554d0b8558800a9ea43ea38a87a54c52ebfc9
Binary file not shown.
@@ -0,0 +1,2 @@
�F�)�,$�x{ ����Ԗ�~�$���p�/�������a��n$���-Ʉ��&���=�T�X/��+ ��egܨ^�
��_u�5�oT?�����v)j$9�ZέX,v�4 �=ul-���yV]���^�� �%J��cT8x;dU,-��.�tK].� ��K���n�_��}_f���o��q�Sn_2"S����҉f^6n������h�-��zVዿ� ����ٿsz;h�%dz-CpЃ�m_&����9�>�

0 comments on commit ce212cc

Please sign in to comment.