diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 28883007..58da7001 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -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 diff --git a/spring-cloud-config-server/pom.xml b/spring-cloud-config-server/pom.xml index 311e1bb7..5a251463 100644 --- a/spring-cloud-config-server/pom.xml +++ b/spring-cloud-config-server/pom.xml @@ -60,6 +60,12 @@ svnkit true + + com.amazonaws + aws-java-sdk-core + 1.11.52 + true + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java index cfacb856..8ba84996 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java @@ -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; + } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepository.java index b5333be5..96a75a42 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentRepository.java @@ -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(); } } diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AwsCodeCommitCredentialProvider.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AwsCodeCommitCredentialProvider.java new file mode 100644 index 00000000..4ef927d9 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AwsCodeCommitCredentialProvider.java @@ -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. + *

+ * 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. + *

+ * 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. + *

+ * + * @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. + } + + } +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/CredentialException.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/CredentialException.java new file mode 100644 index 00000000..4e35f495 --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/CredentialException.java @@ -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); + } + +} diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/GitCredentialsProviderFactory.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/GitCredentialsProviderFactory.java new file mode 100644 index 00000000..ffec036d --- /dev/null +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/GitCredentialsProviderFactory.java @@ -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; + } + +} diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/credentials/AwsCodeCommitCredentialsProviderTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/credentials/AwsCodeCommitCredentialsProviderTests.java new file mode 100644 index 00000000..c1536995 --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/credentials/AwsCodeCommitCredentialsProviderTests.java @@ -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; + } +} diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/credentials/GitCredentialsProviderFactoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/credentials/GitCredentialsProviderFactoryTests.java new file mode 100644 index 00000000..dde8b5ec --- /dev/null +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/credentials/GitCredentialsProviderFactoryTests.java @@ -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()); + } + +}