Merge branch 'master' of https://github.com/spring-cloud/spring-cloud-config
This commit is contained in:
@@ -307,6 +307,8 @@ start successfully with a misconfigured or invalid configuration source and
|
||||
not detect an error until an application requests configuration from that
|
||||
configuration source.
|
||||
|
||||
===== Authentication
|
||||
|
||||
To use HTTP basic authentication on the remote repository add the
|
||||
"username" and "password" properties separately (not in the URL),
|
||||
e.g.
|
||||
@@ -337,6 +339,28 @@ TIP: If you don't know where your `~/.git` directory is us `git config
|
||||
--global` to manipulate the settings (e.g. `git config --global
|
||||
http.sslVerify false`).
|
||||
|
||||
===== Authentication with AWS CodeCommit
|
||||
|
||||
http://docs.aws.amazon.com/codecommit/latest/userguide/welcome.html[AWS CodeCommit] authentication can also be
|
||||
done. AWS CodeCommit uses an authentication helper when using Git from the command line. This helper is not
|
||||
used with the JGit library, so a JGit CredentialProvider for AWS CodeCommit will be created if the Git
|
||||
URI matches the AWS CodeCommit pattern. AWS CodeCommit URIs always look like
|
||||
https://git-codecommit.${AWS_REGION}.amazonaws.com/${repopath}.
|
||||
|
||||
If you provide a username and password with an AWS CodeCommit URI, then these must be
|
||||
the http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html[AWS accessKeyId and secretAccessKey]
|
||||
to be used to access the repository. If you do not specify a username and password,
|
||||
then the accessKeyId and secretAccessKey will be retrieved using the
|
||||
http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html[AWS Default Credential Provider Chain].
|
||||
|
||||
If your Git URI matches the CodeCommit URI pattern (above) then you must provide
|
||||
valid AWS credentials in the username and password, or in one of the locations supported
|
||||
by the default credential provider chain. AWS EC2 instances may use
|
||||
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html[IAM Roles for EC2 Instances].
|
||||
|
||||
Note: The aws-java-sdk-core jar is an optional dependency. If the aws-java-sdk-core jar is not on your
|
||||
classpath, then the AWS Code Commit credential provider will not be created regardless of the git server URI.
|
||||
|
||||
===== Placeholders in Git Search Paths
|
||||
|
||||
Spring Cloud Config Server also supports a search path with
|
||||
|
||||
@@ -60,6 +60,12 @@
|
||||
<artifactId>svnkit</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-core</artifactId>
|
||||
<version>1.11.52</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.eclipse.jgit.api.TransportCommand;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.api.errors.RefNotFoundException;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||
import org.eclipse.jgit.transport.OpenSshConfig.Host;
|
||||
@@ -89,6 +90,11 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
private JGitEnvironmentRepository.JGitFactory gitFactory = new JGitEnvironmentRepository.JGitFactory();
|
||||
|
||||
private String defaultLabel = DEFAULT_LABEL;
|
||||
|
||||
/**
|
||||
* The credentials provider to use to connect to the Git repository.
|
||||
*/
|
||||
private CredentialsProvider gitCredentialsProvider;
|
||||
|
||||
/**
|
||||
* Flag to indicate that the repository should force pull. If true discard any local
|
||||
@@ -494,4 +500,18 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the gitCredentialsProvider
|
||||
*/
|
||||
public CredentialsProvider getGitCredentialsProvider() {
|
||||
return gitCredentialsProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param gitCredentialsProvider the gitCredentialsProvider to set
|
||||
*/
|
||||
public void setGitCredentialsProvider(CredentialsProvider gitCredentialsProvider) {
|
||||
this.gitCredentialsProvider = gitCredentialsProvider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Map;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.config.environment.Environment;
|
||||
import org.springframework.cloud.config.server.credentials.GitCredentialsProviderFactory;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -61,6 +62,8 @@ public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
GitCredentialsProviderFactory credentialFactory = new GitCredentialsProviderFactory();
|
||||
super.setGitCredentialsProvider(credentialFactory.createFor(getUri(), getUsername(), getPassword()));
|
||||
super.afterPropertiesSet();
|
||||
for (String name : this.repos.keySet()) {
|
||||
PatternMatchingJGitEnvironmentRepository repo = this.repos.get(name);
|
||||
@@ -74,6 +77,13 @@ public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository
|
||||
if (getTimeout() != 0 && repo.getTimeout() == 0) {
|
||||
repo.setTimeout(getTimeout());
|
||||
}
|
||||
String user = repo.getUsername();
|
||||
String pass = repo.getPassword();
|
||||
if (user == null) {
|
||||
user = getUsername();
|
||||
pass = getPassword();
|
||||
}
|
||||
repo.setGitCredentialsProvider(credentialFactory.createFor(repo.getUri(), user, pass));
|
||||
repo.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.config.server.credentials;
|
||||
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
|
||||
import com.amazonaws.auth.AWSCredentials;
|
||||
import com.amazonaws.auth.AWSCredentialsProvider;
|
||||
import com.amazonaws.auth.AWSSessionCredentials;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
|
||||
import com.amazonaws.util.ValidationUtils;
|
||||
|
||||
/**
|
||||
* Provides a jgit {@link CredentialsProvider} implementation that can provide
|
||||
* the appropriate credentials to connect to an AWS CodeCommit repository.
|
||||
* <p>
|
||||
* From the command line, you can configure git to use AWS code commit with a
|
||||
* credential helper. However, jgit does not support credential helper commands,
|
||||
* but it does provider a CredentialsProvider abstract class we can extend.
|
||||
* </p>
|
||||
* Connecting to an AWS CodeCommit (codecommit) repository requires an AWS access key
|
||||
* and secret key. These are used to calculate a signature for the git request. The
|
||||
* AWS access key is used as the codecommit username, and the calculated signature
|
||||
* is used as the password. The process for calculating this signature is documented
|
||||
* very well at http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html.
|
||||
* </p>
|
||||
*
|
||||
* @author Don Laidlaw
|
||||
*
|
||||
*/
|
||||
public class AwsCodeCommitCredentialProvider extends CredentialsProvider {
|
||||
|
||||
private static final String SHA_256 = "SHA-256"; //$NON-NLS-1$
|
||||
private static final String UTF8 = "UTF8"; //$NON-NLS-1$
|
||||
private static final String HMAC_SHA256 = "HmacSHA256"; //$NON-NLS-1$
|
||||
private static final char[] hexArray = "0123456789abcdef".toCharArray(); //$NON-NLS-1$
|
||||
|
||||
protected Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* The AWSCredentialsProvider will be used to provide the access key and
|
||||
* secret key if they are not specified.
|
||||
*/
|
||||
private AWSCredentialsProvider awsCredentialProvider;
|
||||
|
||||
/**
|
||||
* If the access and secret keys are provided, then the
|
||||
* AWSCredentialsProvider will not be used. The username is the
|
||||
* awsAccessKeyId.
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* If the access and secret keys are provided, then the AWSCredentialsProvider will
|
||||
* not be used. The password is the awsSecretKey.
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* This credentials provider cannot run interactively.
|
||||
* @return false
|
||||
* @see org.eclipse.jgit.transport.CredentialsProvider#isInteractive()
|
||||
*/
|
||||
@Override
|
||||
public boolean isInteractive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We support username and password credential items only.
|
||||
* @see org.eclipse.jgit.transport.CredentialsProvider#supports(org.eclipse.jgit.transport.CredentialItem[])
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(CredentialItem... items) {
|
||||
for (CredentialItem i : items) {
|
||||
if (i instanceof CredentialItem.Username) {
|
||||
continue;
|
||||
}
|
||||
else if (i instanceof CredentialItem.Password) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AWSCredentials. If an AWSCredentialProvider was specified, use that, otherwise,
|
||||
* create a new AWSCredentialsProvider. If the username and password are provided, then
|
||||
* use those directly as AWSCredentials. Otherwise us the {@link DefaultAWSCredentialsProviderChain}
|
||||
* as is standard with AWS applications.
|
||||
* @return the AWS credentials.
|
||||
*/
|
||||
private AWSCredentials retrieveAwsCredentials() {
|
||||
if (awsCredentialProvider == null) {
|
||||
if (username != null && password != null) {
|
||||
logger.debug("Creating a static AWSCredentialsProvider");
|
||||
awsCredentialProvider = new AWSStaticCredentialsProvider(new BasicAWSCredentials(username, password));
|
||||
} else {
|
||||
logger.debug("Creating a default AWSCredentialsProvider");
|
||||
awsCredentialProvider = new DefaultAWSCredentialsProviderChain();
|
||||
}
|
||||
}
|
||||
return awsCredentialProvider.getCredentials();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the username and password to use for the given uri.
|
||||
* @see org.eclipse.jgit.transport.CredentialsProvider#get(org.eclipse.jgit.transport.URIish, org.eclipse.jgit.transport.CredentialItem[])
|
||||
*/
|
||||
@Override
|
||||
public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem {
|
||||
String codeCommitPassword;
|
||||
String awsAccessKey;
|
||||
String awsSecretKey;
|
||||
try {
|
||||
AWSCredentials awsCredentials = retrieveAwsCredentials();
|
||||
StringBuilder awsKey = new StringBuilder();
|
||||
awsKey.append(awsCredentials.getAWSAccessKeyId());
|
||||
awsSecretKey = awsCredentials.getAWSSecretKey();
|
||||
if (awsCredentials instanceof AWSSessionCredentials) {
|
||||
AWSSessionCredentials sessionCreds = (AWSSessionCredentials) awsCredentials;
|
||||
if (sessionCreds.getSessionToken() != null) {
|
||||
awsKey.append('%')
|
||||
.append(sessionCreds.getSessionToken());
|
||||
}
|
||||
}
|
||||
awsAccessKey = awsKey.toString();
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Unable to retrieve AWS Credentials", t);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
codeCommitPassword = calculateCodeCommitPassword(uri, awsSecretKey);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Error calculating the AWS CodeCommit password", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CredentialItem i : items) {
|
||||
if (i instanceof CredentialItem.Username) {
|
||||
((CredentialItem.Username) i).setValue(awsAccessKey);
|
||||
logger.trace("Returning username " + awsAccessKey);
|
||||
continue;
|
||||
}
|
||||
if (i instanceof CredentialItem.Password) {
|
||||
((CredentialItem.Password) i).setValue(codeCommitPassword.toCharArray());
|
||||
logger.trace("Returning password " + codeCommitPassword);
|
||||
continue;
|
||||
}
|
||||
if (i instanceof CredentialItem.StringType && i.getPromptText().equals("Password: ")) { //$NON-NLS-1$
|
||||
((CredentialItem.StringType) i).setValue(codeCommitPassword);
|
||||
logger.trace("Returning password string " + codeCommitPassword);
|
||||
continue;
|
||||
}
|
||||
throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText()); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the AWS CodeCommit password for the provided URI and AWS secret key.
|
||||
* This uses the algorithm published by AWS at
|
||||
* http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
|
||||
* @param uri the codecommit repository uri
|
||||
* @param awsSecretKey the aws secret key
|
||||
* @return the password to use in the git request
|
||||
*/
|
||||
protected static String calculateCodeCommitPassword(URIish uri, String awsSecretKey) {
|
||||
String[] split = uri.getHost().split("\\.");
|
||||
if (split.length < 4) {
|
||||
throw new CredentialException("Cannot detect AWS region from URI", null);
|
||||
}
|
||||
String region = split[1];
|
||||
|
||||
Date now = new Date();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
String dateStamp = dateFormat.format(now);
|
||||
String shortDateStamp = dateStamp.substring(0, 8);
|
||||
|
||||
String codeCommitPassword;
|
||||
try {
|
||||
StringBuilder stringToSign = new StringBuilder();
|
||||
stringToSign.append("AWS4-HMAC-SHA256\n")
|
||||
.append(dateStamp).append("\n")
|
||||
.append(shortDateStamp)
|
||||
.append("/").append(region)
|
||||
.append("/codecommit/aws4_request\n")
|
||||
.append(bytesToHexString(canonicalRequestDigest(uri)));
|
||||
|
||||
byte[] signedRequest = sign(awsSecretKey, shortDateStamp, region, stringToSign.toString());
|
||||
codeCommitPassword = dateStamp + "Z" + bytesToHexString(signedRequest);
|
||||
} catch (Exception e) {
|
||||
throw new CredentialException("Error calculating AWS CodeCommit password", e);
|
||||
}
|
||||
|
||||
return codeCommitPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw out cached data and force retrieval of AWS credentials.
|
||||
* @param uri This parameter is not used in this implementation.
|
||||
*/
|
||||
@Override
|
||||
public void reset(URIish uri) {
|
||||
// Should throw out cached info.
|
||||
// Note that even though the credentials (password) we calculate here is
|
||||
// valid for 15 minutes, we do not cache it. Instead we just re-calculate
|
||||
// it each time we need it. However, the AWSCredentialProvider will cache
|
||||
// its AWSCredentials object.
|
||||
}
|
||||
|
||||
private static byte[] hmacSha256(String data, byte[] key) throws Exception {
|
||||
String algorithm = HMAC_SHA256;
|
||||
Mac mac = Mac.getInstance(algorithm);
|
||||
mac.init(new SecretKeySpec(key, algorithm));
|
||||
return mac.doFinal(data.getBytes(UTF8));
|
||||
}
|
||||
|
||||
private static byte[] sign(String secret, String shortDateStamp, String region, String toSign) throws Exception {
|
||||
byte[] kSecret = ("AWS4" + secret).getBytes(UTF8);
|
||||
byte[] kDate = hmacSha256(shortDateStamp, kSecret);
|
||||
byte[] kRegion = hmacSha256(region, kDate);
|
||||
byte[] kService = hmacSha256("codecommit", kRegion);
|
||||
byte[] kSigning = hmacSha256("aws4_request", kService);
|
||||
return hmacSha256(toSign, kSigning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message digest
|
||||
* @param uri
|
||||
* @return
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private static byte[] canonicalRequestDigest(URIish uri) throws NoSuchAlgorithmException {
|
||||
StringBuilder canonicalRequest = new StringBuilder();
|
||||
canonicalRequest.append("GIT\n") // codecommit uses GIT as the request method
|
||||
.append(uri.getPath()).append("\n") // URI request path
|
||||
.append("\n") // Query string, always empty for codecommit
|
||||
// Next is canonical headers, codecommit only requires the host header
|
||||
.append("host:").append(uri.getHost()).append("\n")
|
||||
.append("\n") // canonical headers are always terminated by newline
|
||||
.append("host\n"); // The list of canonical headers, only one for codecommit
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance(SHA_256);
|
||||
|
||||
return digest.digest(canonicalRequest.toString().getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to a hex string
|
||||
* @param bytes the bytes
|
||||
* @return a string of hex characters encoding the bytes.
|
||||
*/
|
||||
private static String bytesToHexString(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the awsCredentialProvider
|
||||
*/
|
||||
public AWSCredentialsProvider getAwsCredentialProvider() {
|
||||
return awsCredentialProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param awsCredentialProvider the awsCredentialProvider to set
|
||||
*/
|
||||
public void setAwsCredentialProvider(AWSCredentialsProvider awsCredentialProvider) {
|
||||
this.awsCredentialProvider = awsCredentialProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* This provider can handle uris like https://git-codecommit.$AWS_REGION.amazonaws.com/v1/repos/$REPO
|
||||
* @see org.springframework.cloud.config.server.credentials.GitCredentialsProvider#canHandleUri(java.lang.String)
|
||||
*/
|
||||
public static boolean canHandle(String uri) {
|
||||
if (!hasText(uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
URI u = new URI(uri.toLowerCase());
|
||||
if (u.getScheme().equals("https")) {
|
||||
String host = u.getHost();
|
||||
if (host.endsWith(".amazonaws.com") && host.startsWith("git-codecommit.")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// ignore all, we can't handle it
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param username the username to set
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param password the password to set
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple implementation of AWSCredentialsProvider that just wraps static AWSCredentials.
|
||||
* AWS Actually provides this class in newer versions of the AWS API.
|
||||
*/
|
||||
public class AWSStaticCredentialsProvider implements AWSCredentialsProvider {
|
||||
|
||||
private final AWSCredentials credentials;
|
||||
|
||||
public AWSStaticCredentialsProvider(AWSCredentials credentials) {
|
||||
this.credentials = ValidationUtils.assertNotNull(credentials, "credentials");
|
||||
}
|
||||
|
||||
public AWSCredentials getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
// Nothing to do for static credentials.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.config.server.credentials;
|
||||
|
||||
/**
|
||||
* @author Don Laidlaw
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CredentialException extends RuntimeException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public CredentialException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.config.server.credentials;
|
||||
|
||||
import static org.springframework.util.StringUtils.hasText;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A CredentialsProvider factory for Git repositories. Can handle AWS CodeCommit
|
||||
* repositories and other repositories with username/password.
|
||||
*
|
||||
* @author Don Laidlaw
|
||||
*
|
||||
*/
|
||||
public class GitCredentialsProviderFactory {
|
||||
protected Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Enable the AWS Code Commit credentials provider for Git URI's
|
||||
* that match the AWS Code Commit pattern of
|
||||
* https://git-codecommit.${AWS_REGION}.amazonaws.com/${repoPath}.
|
||||
* Enabled by default.
|
||||
*/
|
||||
protected boolean awsCodeCommitEnabled = true;
|
||||
|
||||
/**
|
||||
* Search for a credential provider that will handle the specified URI. If
|
||||
* not found, and the username has text, then create a default using the
|
||||
* provided username and password. Otherwise null.
|
||||
* @param uri the URI of the repository (cannot be null)
|
||||
* @param username the username provided for the repository (may be null)
|
||||
* @param password the password provided for the repository (may be null)
|
||||
* @return the first matched credentials provider or the default or null.
|
||||
*/
|
||||
public CredentialsProvider createFor(String uri, String username, String password) {
|
||||
CredentialsProvider provider = null;
|
||||
if (awsAvailable() && AwsCodeCommitCredentialProvider.canHandle(uri)) {
|
||||
logger.debug("Constructing AwsCodeCommitCredentialProvider for URI " + uri);
|
||||
AwsCodeCommitCredentialProvider aws = new AwsCodeCommitCredentialProvider();
|
||||
aws.setUsername(username);
|
||||
aws.setPassword(password);
|
||||
provider = aws;
|
||||
}
|
||||
else if (hasText(username)) {
|
||||
logger.debug("Constructing UsernamePasswordCredentialsProvider for URI " + uri);
|
||||
provider = new UsernamePasswordCredentialsProvider(username, password.toCharArray());
|
||||
}
|
||||
else {
|
||||
logger.debug("No credentials provider required for URI " + uri);
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the AWS Authentication API is available.
|
||||
* @return true if the com.amazonaws.auth.DefaultAWSCredentialsProviderChain is present,
|
||||
* false otherwise.
|
||||
*/
|
||||
private boolean awsAvailable() {
|
||||
return awsCodeCommitEnabled
|
||||
&& ClassUtils.isPresent("com.amazonaws.auth.DefaultAWSCredentialsProviderChain", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the awsCodeCommitEnabled
|
||||
*/
|
||||
public boolean isAwsCodeCommitEnabled() {
|
||||
return awsCodeCommitEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param awsCodeCommitEnabled the awsCodeCommitEnabled to set
|
||||
*/
|
||||
public void setAwsCodeCommitEnabled(boolean awsCodeCommitEnabled) {
|
||||
this.awsCodeCommitEnabled = awsCodeCommitEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.config.server.credentials;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||
import org.eclipse.jgit.transport.CredentialItem;
|
||||
import org.eclipse.jgit.transport.URIish;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.amazonaws.auth.AWSCredentialsProvider;
|
||||
|
||||
/**
|
||||
* It would be nice to do an integration test, however, this would require
|
||||
* using real AWS credentials. How can we test the credential generation
|
||||
* without real credentials?
|
||||
*
|
||||
* @author don laidlaw
|
||||
*
|
||||
*/
|
||||
public class AwsCodeCommitCredentialsProviderTests {
|
||||
private static final String PASSWORD = "secret";
|
||||
private static final String USER = "test";
|
||||
private static final String AWS_REPO = "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test";
|
||||
private static final String BAD_REPO = "https://amazonaws.com/v1/repos/test";
|
||||
|
||||
private AwsCodeCommitCredentialProvider provider;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
GitCredentialsProviderFactory factory = new GitCredentialsProviderFactory();
|
||||
provider = (AwsCodeCommitCredentialProvider)
|
||||
factory.createFor(AWS_REPO, USER, PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basics() {
|
||||
assertNotNull(provider);
|
||||
assertEquals(USER, provider.getUsername());
|
||||
assertEquals(PASSWORD, provider.getPassword());
|
||||
assertFalse(provider.isInteractive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsUsernamePassword() {
|
||||
assertTrue(provider.supports(new CredentialItem[] {
|
||||
new CredentialItem.Username(),
|
||||
new CredentialItem.Password()
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSupportsOther() {
|
||||
assertFalse(provider.supports(new CredentialItem[] {
|
||||
new CredentialItem.YesNoType("OK To Login?") // this is not ok
|
||||
}));
|
||||
assertFalse(provider.supports(new CredentialItem[] {
|
||||
new CredentialItem.StringType("OK To Login?", true) // this is not ok
|
||||
}));
|
||||
assertFalse(provider.supports(new CredentialItem[] {
|
||||
new CredentialItem.Username(), // this is ok
|
||||
new CredentialItem.Password(), // this is ok
|
||||
new CredentialItem.StringType("OK To Login?", true) // this is not ok
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAwsCredentialsProviderIsNullInitially() {
|
||||
AWSCredentialsProvider awsProvider = provider.getAwsCredentialProvider();
|
||||
assertNull(awsProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAwsCredentialsProviderIsDefinedAfterGet() throws URISyntaxException {
|
||||
AWSCredentialsProvider awsProvider = provider.getAwsCredentialProvider();
|
||||
assertNull(awsProvider);
|
||||
assertTrue(provider.get(new URIish(AWS_REPO), makeCredentialItems()));
|
||||
awsProvider = provider.getAwsCredentialProvider();
|
||||
assertNotNull(awsProvider);
|
||||
assertTrue(awsProvider instanceof AwsCodeCommitCredentialProvider.AWSStaticCredentialsProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadUriReturnsFalse() throws UnsupportedCredentialItem, URISyntaxException {
|
||||
CredentialItem[] credentialItems = makeCredentialItems();
|
||||
assertFalse(provider.get(new URIish(BAD_REPO), credentialItems));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowsUnsupportedCredentialException() throws URISyntaxException {
|
||||
CredentialItem[] goodCredentialItems = makeCredentialItems();
|
||||
CredentialItem[] badCredentialItems = new CredentialItem[] {
|
||||
goodCredentialItems[0],
|
||||
goodCredentialItems[1],
|
||||
new CredentialItem.YesNoType("OK?")
|
||||
};
|
||||
try {
|
||||
provider.get(new URIish(AWS_REPO), badCredentialItems);
|
||||
fail("Expected UnsupportedCredentialItem exception");
|
||||
} catch (UnsupportedCredentialItem e) {
|
||||
assertNotNull(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnsCredentials() throws URISyntaxException {
|
||||
CredentialItem[] credentialItems = makeCredentialItems();
|
||||
assertTrue(provider.get(new URIish(AWS_REPO), credentialItems));
|
||||
|
||||
String theUsername = ((CredentialItem.Username) credentialItems[0]).getValue();
|
||||
char[] thePassword = ((CredentialItem.Password) credentialItems[1]).getValue();
|
||||
|
||||
assertEquals(USER, theUsername);
|
||||
assertNotNull(thePassword);
|
||||
|
||||
// The password will always begin with a timestamp like
|
||||
// 20161113T121314Z
|
||||
assertTrue(thePassword.length > 16);
|
||||
assertEquals('T', thePassword[8]);
|
||||
assertEquals('Z', thePassword[15]);
|
||||
}
|
||||
|
||||
private CredentialItem[] makeCredentialItems() {
|
||||
CredentialItem[] credentialItems = new CredentialItem[2];
|
||||
credentialItems[0] = new CredentialItem.Username();
|
||||
credentialItems[1] = new CredentialItem.Password();
|
||||
return credentialItems;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.config.server.credentials;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author don laidlaw
|
||||
*
|
||||
*/
|
||||
public class GitCredentialsProviderFactoryTests {
|
||||
private static final String PASSWORD = "secret";
|
||||
private static final String USER = "test";
|
||||
private static final String FILE_REPO = "file:///home/user/repo";
|
||||
private static final String GIT_REPO = "https://github.com/spring-cloud/spring-cloud-config-test";
|
||||
private static final String AWS_REPO = "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/test";
|
||||
|
||||
private GitCredentialsProviderFactory factory;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
factory = new GitCredentialsProviderFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForFileNoUsernameIsNull() {
|
||||
CredentialsProvider provider = factory.createFor(FILE_REPO, null, null);
|
||||
assertNull(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForFileWithUsername() {
|
||||
CredentialsProvider provider = factory.createFor(FILE_REPO, USER, PASSWORD);
|
||||
assertNotNull(provider);
|
||||
assertTrue(provider instanceof UsernamePasswordCredentialsProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForServerNoUsernameIsNull() {
|
||||
CredentialsProvider provider = factory.createFor(GIT_REPO, null, null);
|
||||
assertNull(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForServerWithUsername() {
|
||||
CredentialsProvider provider = factory.createFor(GIT_REPO, USER, PASSWORD);
|
||||
assertNotNull(provider);
|
||||
assertTrue(provider instanceof UsernamePasswordCredentialsProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForAwsNoUsername() {
|
||||
CredentialsProvider provider = factory.createFor(AWS_REPO, null, null);
|
||||
assertNotNull(provider);
|
||||
assertTrue(provider instanceof AwsCodeCommitCredentialProvider);
|
||||
AwsCodeCommitCredentialProvider aws = (AwsCodeCommitCredentialProvider) provider;
|
||||
assertNull(aws.getUsername());
|
||||
assertNull(aws.getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForAwsWithUsername() {
|
||||
CredentialsProvider provider = factory.createFor(AWS_REPO, USER, PASSWORD);
|
||||
assertNotNull(provider);
|
||||
assertTrue(provider instanceof AwsCodeCommitCredentialProvider);
|
||||
AwsCodeCommitCredentialProvider aws = (AwsCodeCommitCredentialProvider) provider;
|
||||
assertEquals(USER, aws.getUsername());
|
||||
assertEquals(PASSWORD, aws.getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateForAwsDisabled() {
|
||||
factory.setAwsCodeCommitEnabled(false);
|
||||
CredentialsProvider provider = factory.createFor(AWS_REPO, null, null);
|
||||
assertNull(provider);
|
||||
provider = factory.createFor(AWS_REPO, USER, PASSWORD);
|
||||
assertNotNull(provider);
|
||||
assertTrue(provider instanceof UsernamePasswordCredentialsProvider);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIsAwsCodeCommitEnabled() {
|
||||
assertTrue(factory.isAwsCodeCommitEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAwsCodeCommitEnabled() {
|
||||
assertTrue(factory.isAwsCodeCommitEnabled());
|
||||
factory.setAwsCodeCommitEnabled(false);
|
||||
assertFalse(factory.isAwsCodeCommitEnabled());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user