Skip to content

Commit

Permalink
[JENKINS-24571] - support private keys from new google developer cons…
Browse files Browse the repository at this point in the history
…ole - changes after review - part 1
  • Loading branch information
DavidHamm committed Sep 29, 2014
1 parent 6e67cd9 commit f74bf21
Show file tree
Hide file tree
Showing 19 changed files with 413 additions and 330 deletions.
Expand Up @@ -17,8 +17,6 @@

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -50,7 +48,7 @@ public final class GoogleRobotPrivateKeyCredentials
private static final long serialVersionUID = -6768343254941345944L;
private static final Logger LOGGER = Logger.getLogger(
GoogleRobotPrivateKeyCredentials.class.getSimpleName());
private KeyType keyType;
private ServiceAccountConfig serviceAccountConfig;
@Deprecated
private transient String secretsFile;
@Deprecated
Expand All @@ -60,21 +58,23 @@ public final class GoogleRobotPrivateKeyCredentials
* Construct a set of service account credentials.
*
* @param projectId The project id associated with this service account
* @param keyType The KeyType to use
* @param serviceAccountConfig The ServiceAccountConfig to use
* @param module The module for instantiating dependent objects, or null.
*/
@DataBoundConstructor
public GoogleRobotPrivateKeyCredentials(String projectId, KeyType keyType,
public GoogleRobotPrivateKeyCredentials(String projectId,
ServiceAccountConfig serviceAccountConfig,
@Nullable GoogleRobotCredentialsModule module) throws Exception {
super(projectId, module);
this.keyType = keyType;
this.serviceAccountConfig = serviceAccountConfig;
}

@SuppressWarnings("deprecation")
public Object readResolve() {
if (keyType == null) {
if (serviceAccountConfig == null) {
String clientEmail = getClientEmailFromSecretsFileAndLogErrors();
keyType = new P12KeyType(clientEmail, null, p12File);
serviceAccountConfig = new P12ServiceAccountConfig(clientEmail, null,
p12File);
}
return this;
}
Expand Down Expand Up @@ -125,28 +125,33 @@ private String getClientEmailFromSecretsFile()
return clientEmail;
}

public static List<KeyType.Descriptor> getKeyTypeDescriptors() {
/**
* Used for populating the configuration for each {@link ServiceAccountConfig}
*
* @return list of possible {@link ServiceAccountConfig}s
*/
public static List<ServiceAccountConfig.Descriptor>
getServiceAccountConfigDescriptors() {
Jenkins instance = Jenkins.getInstance();
return Arrays.asList(
(KeyType.Descriptor) instance.getDescriptorOrDie(JsonKeyType.class),
(KeyType.Descriptor) instance.getDescriptorOrDie(P12KeyType.class));
return ImmutableList.of(
(ServiceAccountConfig.Descriptor) instance
.getDescriptorOrDie(JsonServiceAccountConfig.class),
(ServiceAccountConfig.Descriptor) instance
.getDescriptorOrDie(P12ServiceAccountConfig.class));
}

@NonNull
@Override
public String getUsername() throws KeyTypeNotSetException,
AccountIdNotSetException, PrivateKeyNotSetException {
GoogleCredential credential = getGoogleCredential(
new GoogleOAuth2ScopeRequirement() {
private static final long serialVersionUID =
-8046870980553756366L;

@Override
public Collection<String> getScopes() {
return ImmutableList.of();
}
});
return credential.getServiceAccountId();
AccountIdNotSetException {
if (serviceAccountConfig == null) {
throw new KeyTypeNotSetException();
}
String accountId = serviceAccountConfig.getAccountId();
if (accountId == null) {
throw new AccountIdNotSetException();
}
return accountId;
}

/**
Expand All @@ -169,26 +174,26 @@ public GoogleCredential getGoogleCredential(
GoogleOAuth2ScopeRequirement requirement)
throws KeyTypeNotSetException, AccountIdNotSetException,
PrivateKeyNotSetException {
if (keyType == null) {
if (serviceAccountConfig == null) {
throw new KeyTypeNotSetException();
}
if (keyType.getAccountId() == null) {
if (serviceAccountConfig.getAccountId() == null) {
throw new AccountIdNotSetException();
}
if (keyType.getPrivateKey() == null) {
if (serviceAccountConfig.getPrivateKey() == null) {
throw new PrivateKeyNotSetException();
}
return new GoogleCredential.Builder()
.setTransport(getModule().getHttpTransport())
.setJsonFactory(getModule().getJsonFactory())
.setServiceAccountScopes(requirement.getScopes())
.setServiceAccountId(keyType.getAccountId())
.setServiceAccountPrivateKey(keyType.getPrivateKey())
.setServiceAccountId(serviceAccountConfig.getAccountId())
.setServiceAccountPrivateKey(serviceAccountConfig.getPrivateKey())
.build();
}

public KeyType getKeyType() {
return keyType;
public ServiceAccountConfig getServiceAccountConfig() {
return serviceAccountConfig;
}

/**
Expand Down Expand Up @@ -249,7 +254,7 @@ public SecretsFileNotFoundException(Throwable cause) {
}

/**
* Exception that gets thrown if KeyType is not set.
* Exception that gets thrown if ServiceAccountConfig is not set.
*/
public static class KeyTypeNotSetException extends RuntimeException {
}
Expand Down
Expand Up @@ -26,8 +26,8 @@
/**
* the
* <a href="https://console.developers.google.com">Google Developer Console</a>
* provides private keys for service accounts in two different ways. on of
* them is a .json file that can be downloaded in the
* provides private keys for service accounts in two different ways. one of
* them is a .json file that can be downloaded from the
* <a href="https://console.developers.google.com">Google Developer Console</a>.
* <p/>
* the structure of this json file is:
Expand Down
Expand Up @@ -15,7 +15,11 @@
*/
package com.google.jenkins.plugins.credentials.oauth;

import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
Expand All @@ -36,7 +40,7 @@
/**
* provides authentication mechanism for a service account by setting a a .json
* private key file. the .json file
* sturcture needs to be:
* structure needs to be:
* <p/>
* <code>
* <pre>
Expand All @@ -50,15 +54,16 @@
* </pre>
* </code>
*/
public class JsonKeyType extends KeyType {
public class JsonServiceAccountConfig extends ServiceAccountConfig {
private static final long serialVersionUID = 6818111194672325387L;
private static final Logger LOGGER =
Logger.getLogger(JsonKeyType.class.getSimpleName());
Logger.getLogger(JsonServiceAccountConfig.class.getSimpleName());
private String jsonKeyFile;
private transient JsonKey jsonKey;

@DataBoundConstructor
public JsonKeyType(FileItem jsonKeyFile, String prevJsonKeyFile) {
public JsonServiceAccountConfig(FileItem jsonKeyFile,
String prevJsonKeyFile) {
if (jsonKeyFile != null && jsonKeyFile.getSize() > 0) {
try {
JsonKey jsonKey = JsonKey.load(new JacksonFactory(),
Expand Down Expand Up @@ -101,16 +106,14 @@ private void writeJsonKeyToFile(JsonKey jsonKey, File file)
out = new FileOutputStream(file);
IOUtils.write(jsonKey.toPrettyString(), out);
} finally {
if (out != null) {
out.close();
}
IOUtils.closeQuietly(out);
}
}

@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) Jenkins.getInstance()
.getDescriptorOrDie(JsonKeyType.class);
.getDescriptorOrDie(JsonServiceAccountConfig.class);
}

public String getJsonKeyFile() {
Expand Down Expand Up @@ -169,7 +172,7 @@ private JsonKey getJsonKey() {
public static final class DescriptorImpl extends Descriptor {
@Override
public String getDisplayName() {
return "JSON key";
return Messages.JsonServiceAccountConfig_DisplayName();
}
}
}
Expand Up @@ -15,8 +15,16 @@
*/
package com.google.jenkins.plugins.credentials.oauth;

import java.io.*;
import java.security.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -32,16 +40,18 @@
* provides authentication mechanism for a service account by setting a service
* account email address and .p12 private key file
*/
public class P12KeyType extends KeyType {
public class P12ServiceAccountConfig extends ServiceAccountConfig {
private static final long serialVersionUID = 8706353638974721795L;
private static final Logger LOGGER =
Logger.getLogger(P12KeyType.class.getSimpleName());
Logger.getLogger(P12ServiceAccountConfig.class.getSimpleName());
private static final String DEFAULT_P12_SECRET = "notasecret";
private static final String DEFAULT_P12_ALIAS = "privatekey";
private final String emailAddress;
private String p12KeyFile;

@DataBoundConstructor
public P12KeyType(String emailAddress, FileItem p12KeyFile,
String prevP12KeyFile) {
public P12ServiceAccountConfig(String emailAddress, FileItem p12KeyFile,
String prevP12KeyFile) {
this.emailAddress = emailAddress;
if (p12KeyFile != null && p12KeyFile.getSize() > 0) {
try {
Expand Down Expand Up @@ -69,12 +79,8 @@ private void writeP12KeyToFile(FileItem p12KeyFileItem, File p12KeyFile)
out = new FileOutputStream(p12KeyFile);
IOUtils.copy(in, out);
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}

Expand All @@ -90,7 +96,7 @@ private File createP12KeyFile() throws IOException {
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) Jenkins.getInstance()
.getDescriptorOrDie(P12KeyType.class);
.getDescriptorOrDie(P12ServiceAccountConfig.class);
}

public String getEmailAddress() {
Expand All @@ -111,8 +117,8 @@ public PrivateKey getPrivateKey() {
if (p12KeyFile != null) {
try {
KeyStore p12KeyStore = getP12KeyStore();
return (PrivateKey) p12KeyStore.getKey("privatekey",
"notasecret".toCharArray());
return (PrivateKey) p12KeyStore.getKey(DEFAULT_P12_ALIAS,
DEFAULT_P12_SECRET.toCharArray());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to read private key", e);
} catch (GeneralSecurityException e) {
Expand All @@ -128,12 +134,10 @@ private KeyStore getP12KeyStore() throws KeyStoreException,
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
in = new FileInputStream(p12KeyFile);
keyStore.load(in, "notasecret".toCharArray());
keyStore.load(in, DEFAULT_P12_SECRET.toCharArray());
return keyStore;
} finally {
if (in != null) {
in.close();
}
IOUtils.closeQuietly(in);
}
}

Expand All @@ -144,7 +148,7 @@ private KeyStore getP12KeyStore() throws KeyStoreException,
public static final class DescriptorImpl extends Descriptor {
@Override
public String getDisplayName() {
return "P12 key";
return Messages.P12ServiceAccountConfig_DisplayName();
}
}
}
Expand Up @@ -25,7 +25,8 @@
* mechanism. subclasses need to provide an accountId and a private key to use
* for authenticating a service account
*/
public abstract class KeyType implements Describable<KeyType>, Serializable {
public abstract class ServiceAccountConfig
implements Describable<ServiceAccountConfig>, Serializable {
private static final long serialVersionUID = 6355493019938144806L;

public abstract String getAccountId();
Expand All @@ -36,6 +37,6 @@ public abstract class KeyType implements Describable<KeyType>, Serializable {
* abstract descriptor for service account authentication
*/
public abstract static class Descriptor
extends hudson.model.Descriptor<KeyType> {
extends hudson.model.Descriptor<ServiceAccountConfig> {
}
}
Expand Up @@ -20,18 +20,18 @@
<!-- When Jenkins is running outside of GCE, they are forced to utilize
explicit credentials -->
<input type="hidden" name="explicitCredentials" value="true" />
<j:set value="${instance.keyType}"
var="currentKeyType"/>
<j:set value="${instance.serviceAccountConfig}"
var="currentServiceAccountConfig"/>
<j:invokeStatic className="com.google.jenkins.plugins.credentials.oauth.GoogleRobotPrivateKeyCredentials"
method="getKeyTypeDescriptors"
var="keyTypeDescriptors"/>
<j:forEach items="${keyTypeDescriptors}"
method="getServiceAccountConfigDescriptors"
var="serviceAccountConfigDescriptors"/>
<j:forEach items="${serviceAccountConfigDescriptors}"
var="descriptor"
varStatus="loop">
<j:set value="${descriptor == currentKeyType.descriptor ? currentKeyType : null}"
var="keyType"/>
<f:radioBlock checked="${currentKeyType == null ? loop.index == 0 : descriptor == currentKeyType.descriptor}"
name="keyType"
<j:set value="${descriptor == currentServiceAccountConfig.descriptor ? currentServiceAccountConfig : null}"
var="serviceAccountConfig"/>
<f:radioBlock checked="${currentServiceAccountConfig == null ? loop.index == 0 : descriptor == currentServiceAccountConfig.descriptor}"
name="serviceAccountConfig"
title="${descriptor.displayName}"
value="${loop.index}">
<st:include from="${descriptor}"
Expand Down
Expand Up @@ -5,11 +5,11 @@
<input jsonAware="yes"
name="jsonKeyFile"
type="file"/>
<j:if test="${keyType.jsonKeyFile != null}">
<j:if test="${serviceAccountConfig.jsonKeyFile != null}">
<j:new className="java.io.File"
var="jsonKeyFile">
<j:arg type="java.lang.String"
value="${keyType.jsonKeyFile}"/>
value="${serviceAccountConfig.jsonKeyFile}"/>
</j:new>
<label class="attach-previous">
(${%Or reuse previous file:}
Expand All @@ -19,5 +19,5 @@
</f:entry>
<input name="prevJsonKeyFile"
type="hidden"
value="${keyType.jsonKeyFile}"/>
value="${serviceAccountConfig.jsonKeyFile}"/>
</j:jelly>
Expand Up @@ -15,6 +15,8 @@ GoogleRobotPrivateKeyCredentials.ExceptionString=An unknown problem occured whil
GoogleRobotPrivateKeyCredentials.DisplayName=Google Service Account from private key
GoogleRobotPrivateKeyCredentials.BadCredentials=An error occurred deducing a username from the provided credentials files.
GoogleRobotPrivateKeyCredentials.ProjectIDError=A project name must be specified
JsonServiceAccountConfig.DisplayName=JSON key
P12ServiceAccountConfig.DisplayName=P12 key
GoogleRobotMetadataCredentials.DisplayName=Google Service Account from metadata
GoogleRobotMetadataCredentials.ProjectIDError=A project name must be specified
GoogleRobotMetadataCredentials.AddProjectIdAuthError=Insufficient privileges to add a project
Expand Down

0 comments on commit f74bf21

Please sign in to comment.