diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java index e2c21963e5..3d4e4584bd 100644 --- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java @@ -41,6 +41,7 @@ import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; import org.apache.nifi.ssl.SSLContextService; import com.amazonaws.AmazonWebServiceClient; @@ -72,28 +73,9 @@ public abstract class AbstractAWSProcessor relationships = Collections.unmodifiableSet( new HashSet<>(Arrays.asList(REL_SUCCESS, REL_FAILURE))); - public static final PropertyDescriptor CREDENTIALS_FILE = new PropertyDescriptor.Builder() - .name("Credentials File") - .expressionLanguageSupported(false) - .required(false) - .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR) - .build(); - - public static final PropertyDescriptor ACCESS_KEY = new PropertyDescriptor.Builder() - .name("Access Key") - .expressionLanguageSupported(true) - .required(false) - .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) - .sensitive(true) - .build(); - - public static final PropertyDescriptor SECRET_KEY = new PropertyDescriptor.Builder() - .name("Secret Key") - .expressionLanguageSupported(true) - .required(false) - .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) - .sensitive(true) - .build(); + public static final PropertyDescriptor CREDENTIALS_FILE = CredentialPropertyDescriptors.CREDENTIALS_FILE; + public static final PropertyDescriptor ACCESS_KEY = CredentialPropertyDescriptors.ACCESS_KEY; + public static final PropertyDescriptor SECRET_KEY = CredentialPropertyDescriptors.SECRET_KEY; public static final PropertyDescriptor PROXY_HOST = new PropertyDescriptor.Builder() .name("Proxy Host") diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java new file mode 100644 index 0000000000..04330ab680 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialPropertyDescriptors.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processor.util.StandardValidators; + +/** + * Shared definitions of properties that specify various AWS credentials. + * + * @see + * Providing AWS Credentials in the AWS SDK for Java + */ +public class CredentialPropertyDescriptors { + + /** + * Specifies use of the Default Credential Provider Chain + * + * @see + * AWS SDK: Default Credential Provider Chain + * + */ + public static final PropertyDescriptor USE_DEFAULT_CREDENTIALS = new PropertyDescriptor.Builder() + .name("default-credentials") + .displayName("Use Default Credentials") + .expressionLanguageSupported(false) + .required(false) + .addValidator(StandardValidators.BOOLEAN_VALIDATOR) + .sensitive(false) + .allowableValues("true", "false") + .defaultValue("false") + .description("If true, uses the Default Credential chain, including EC2 instance profiles or roles, " + + "environment variables, default user credentials, etc.") + .build(); + + public static final PropertyDescriptor CREDENTIALS_FILE = new PropertyDescriptor.Builder() + .name("Credentials File") + .displayName("Credentials File") + .expressionLanguageSupported(false) + .required(false) + .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR) + .description("Path to a file containing AWS access key and secret key in properties file format.") + .build(); + + public static final PropertyDescriptor ACCESS_KEY = new PropertyDescriptor.Builder() + .name("Access Key") + .displayName("Access Key") + .expressionLanguageSupported(true) + .required(false) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(true) + .build(); + + public static final PropertyDescriptor SECRET_KEY = new PropertyDescriptor.Builder() + .name("Secret Key") + .displayName("Secret Key") + .expressionLanguageSupported(true) + .required(false) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(true) + .build(); + + /** + * Specifies use of a named profile credential. + * + * @see + * ProfileCredentialsProvider + */ + public static final PropertyDescriptor PROFILE_NAME = new PropertyDescriptor.Builder() + .name("profile-name") + .displayName("Profile Name") + .expressionLanguageSupported(true) + .required(false) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(false) + .description("The AWS profile name for credentials from the profile configuration file.") + .build(); + + public static final PropertyDescriptor USE_ANONYMOUS_CREDENTIALS = new PropertyDescriptor.Builder() + .name("anonymous-credentials") + .displayName("Use Anonymous Credentials") + .expressionLanguageSupported(false) + .required(false) + .addValidator(StandardValidators.BOOLEAN_VALIDATOR) + .sensitive(false) + .allowableValues("true", "false") + .defaultValue("false") + .description("If true, uses Anonymous credentials") + .build(); + + /** + * AWS Role Arn used for cross account access + * + * @see AWS ARN + */ + public static final PropertyDescriptor ASSUME_ROLE_ARN = new PropertyDescriptor.Builder() + .name("Assume Role ARN") + .displayName("Assume Role ARN") + .expressionLanguageSupported(false) + .required(false) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(false) + .description("The AWS Role ARN for cross account access. This is used in conjunction with role name and session timeout") + .build(); + + /** + * The role name while creating aws role + */ + public static final PropertyDescriptor ASSUME_ROLE_NAME = new PropertyDescriptor.Builder() + .name("Assume Role Session Name") + .displayName("Assume Role Session Name") + .expressionLanguageSupported(false) + .required(false) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(false) + .description("The AWS Role Name for cross account access. This is used in conjunction with role ARN and session time out") + .build(); + + /** + * Max session time for role based credentials. The range is between 900 and 3600 seconds. + */ + public static final PropertyDescriptor MAX_SESSION_TIME = new PropertyDescriptor.Builder() + .name("Session Time") + .description("Session time for role based session (between 900 and 3600 seconds). This is used in conjunction with role ARN and name") + .defaultValue("3600") + .required(false) + .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR) + .sensitive(false) + .build(); + + /** + * The ExternalId used while creating aws role. + */ + public static final PropertyDescriptor ASSUME_ROLE_EXTERNAL_ID = new PropertyDescriptor.Builder() + .name("assume-role-external-id") + .displayName("Assume Role External ID") + .expressionLanguageSupported(false) + .required(false) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .sensitive(false) + .description("External ID for cross-account access. This is used in conjunction with role arn, " + + "role name, and optional session time out") + .build(); +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialsProviderFactory.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialsProviderFactory.java new file mode 100644 index 0000000000..94f041005c --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialsProviderFactory.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.ExplicitDefaultCredentialsStrategy; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.AccessKeyPairCredentialsStrategy; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.FileCredentialsStrategy; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.NamedProfileCredentialsStrategy; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.AnonymousCredentialsStrategy; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.ImplicitDefaultCredentialsStrategy; +import org.apache.nifi.processors.aws.credentials.provider.factory.strategies.AssumeRoleCredentialsStrategy; + +import com.amazonaws.auth.AWSCredentialsProvider; + + +/** + * Generates AWS credentials in the form of AWSCredentialsProvider implementations for processors + * and controller services. The factory supports a number of strategies for specifying and validating + * AWS credentials, interpreted as an ordered list of most-preferred to least-preferred. It also supports + * derived credential strategies like Assume Role, which require a primary credential as an input. + * + * Additional strategies should implement CredentialsStrategy, then be added to the strategies list in the + * constructor. + * + * @see org.apache.nifi.processors.aws.credentials.provider.factory.strategies + */ +public class CredentialsProviderFactory { + + private final List strategies = new ArrayList(); + + public CredentialsProviderFactory() { + // Primary Credential Strategies + strategies.add(new ExplicitDefaultCredentialsStrategy()); + strategies.add(new AccessKeyPairCredentialsStrategy()); + strategies.add(new FileCredentialsStrategy()); + strategies.add(new NamedProfileCredentialsStrategy()); + strategies.add(new AnonymousCredentialsStrategy()); + + // Implicit Default is the catch-all primary strategy + strategies.add(new ImplicitDefaultCredentialsStrategy()); + + // Derived Credential Strategies + strategies.add(new AssumeRoleCredentialsStrategy()); + } + + public CredentialsStrategy selectPrimaryStrategy(final Map properties) { + for (CredentialsStrategy strategy : strategies) { + if (strategy.canCreatePrimaryCredential(properties)) { + return strategy; + } + } + return null; + } + + public CredentialsStrategy selectPrimaryStrategy(final ValidationContext validationContext) { + final Map properties = validationContext.getProperties(); + return selectPrimaryStrategy(properties); + } + + /** + * Validates AWS credential properties against the configured strategies to report any validation errors. + * @return Validation errors + */ + public Collection validate(final ValidationContext validationContext) { + final CredentialsStrategy selectedStrategy = selectPrimaryStrategy(validationContext); + final ArrayList validationFailureResults = new ArrayList(); + + for (CredentialsStrategy strategy : strategies) { + final Collection strategyValidationFailures = strategy.validate(validationContext, + selectedStrategy); + if (strategyValidationFailures != null) { + validationFailureResults.addAll(strategyValidationFailures); + } + } + + return validationFailureResults; + } + + /** + * Produces the AWSCredentialsProvider according to the given property set and the strategies configured in + * the factory. + * @return AWSCredentialsProvider implementation + */ + public AWSCredentialsProvider getCredentialsProvider(final Map properties) { + final CredentialsStrategy primaryStrategy = selectPrimaryStrategy(properties); + AWSCredentialsProvider primaryCredentialsProvider = primaryStrategy.getCredentialsProvider(properties); + AWSCredentialsProvider derivedCredentialsProvider = null; + + for (CredentialsStrategy strategy : strategies) { + if (strategy.canCreateDerivedCredential(properties)) { + derivedCredentialsProvider = strategy.getDerivedCredentialsProvider(properties, + primaryCredentialsProvider); + break; + } + } + + if (derivedCredentialsProvider != null) { + return derivedCredentialsProvider; + } else { + return primaryCredentialsProvider; + } + } +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialsStrategy.java new file mode 100644 index 0000000000..d5d93a7277 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/CredentialsStrategy.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory; + +import java.util.Collection; +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; + +import com.amazonaws.auth.AWSCredentialsProvider; + + +/** + * Specifies a strategy for validating and creating AWS credentials from a list of properties configured on a + * Processor, Controller Service, Reporting Service, or other component. Supports both primary credentials like + * default credentials or API keys and also derived credentials from Assume Role. + */ +public interface CredentialsStrategy { + + /** + * Name of the strategy, suitable for displaying to a user in validation messages. + * @return strategy name + */ + String getName(); + + /** + * Determines if this strategy can create primary credentials using the given properties. + * @return true if primary credentials can be created + */ + boolean canCreatePrimaryCredential(Map properties); + + /** + * Determines if this strategy can create derived credentials using the given properties. + * @return true if derived credentials can be created + */ + boolean canCreateDerivedCredential(Map properties); + + /** + * Validates the properties belonging to this strategy, given the selected primary strategy. Errors may result + * from individually malformed properties, invalid combinations of properties, or inappropriate use of properties + * not consistent with the primary strategy. + * @param primaryStrategy the prevailing primary strategy + * @return validation errors + */ + Collection validate(ValidationContext validationContext, CredentialsStrategy primaryStrategy); + + /** + * Creates an AWSCredentialsProvider instance for this strategy, given the properties defined by the user. + */ + AWSCredentialsProvider getCredentialsProvider(Map properties); + + /** + * Creates an AWSCredentialsProvider instance for this strategy, given the properties defined by the user and + * the AWSCredentialsProvider from the winning primary strategy. + */ + AWSCredentialsProvider getDerivedCredentialsProvider(Map properties, + AWSCredentialsProvider primaryCredentialsProvider); + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AbstractBooleanCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AbstractBooleanCredentialsStrategy.java new file mode 100644 index 0000000000..2c5b9a39db --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AbstractBooleanCredentialsStrategy.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy; + + +/** + * Partial implementation of CredentialsStrategy to provide support for credential strategies specified by + * a single boolean property. + */ +public abstract class AbstractBooleanCredentialsStrategy extends AbstractCredentialsStrategy { + + private PropertyDescriptor strategyProperty; + + public AbstractBooleanCredentialsStrategy(String name, PropertyDescriptor strategyProperty) { + super("Default Credentials", new PropertyDescriptor[]{ + strategyProperty + }); + this.strategyProperty = strategyProperty; + } + + @Override + public boolean canCreatePrimaryCredential(Map properties) { + String useStrategyString = properties.get(strategyProperty); + Boolean useStrategy = Boolean.parseBoolean(useStrategyString); + return useStrategy; + } + + @Override + public Collection validate(final ValidationContext validationContext, + final CredentialsStrategy primaryStrategy) { + boolean thisIsSelectedStrategy = this == primaryStrategy; + Boolean useStrategy = validationContext.getProperty(strategyProperty).asBoolean(); + if (!thisIsSelectedStrategy && useStrategy) { + String failureFormat = "property %1$s cannot be used with %2$s"; + Collection validationFailureResults = new ArrayList(); + String message = String.format(failureFormat, strategyProperty.getDisplayName(), + primaryStrategy.getName()); + validationFailureResults.add(new ValidationResult.Builder() + .subject(strategyProperty.getDisplayName()) + .valid(false) + .explanation(message).build()); + return validationFailureResults; + } + return null; + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AbstractCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AbstractCredentialsStrategy.java new file mode 100644 index 0000000000..29f1000318 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AbstractCredentialsStrategy.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy; + +import com.amazonaws.auth.AWSCredentialsProvider; + + +/** + * Partial implementation of CredentialsStrategy to support most simple property-based strategies. + */ +public abstract class AbstractCredentialsStrategy implements CredentialsStrategy { + private final String name; + private final PropertyDescriptor[] requiredProperties; + + public AbstractCredentialsStrategy(String name, PropertyDescriptor[] requiredProperties) { + this.name = name; + this.requiredProperties = requiredProperties; + } + + @Override + public boolean canCreatePrimaryCredential(Map properties) { + for (PropertyDescriptor requiredProperty : requiredProperties) { + boolean containsRequiredProperty = properties.containsKey(requiredProperty); + String propertyValue = properties.get(requiredProperty); + boolean containsValue = propertyValue != null; + if (!containsRequiredProperty || !containsValue) { + return false; + } + } + return true; + } + + @Override + public Collection validate(final ValidationContext validationContext, + final CredentialsStrategy primaryStrategy) { + boolean thisIsSelectedStrategy = this == primaryStrategy; + String requiredMessageFormat = "property %1$s must be set with %2$s"; + String excludedMessageFormat = "property %1$s cannot be used with %2$s"; + String failureFormat = thisIsSelectedStrategy ? requiredMessageFormat : excludedMessageFormat; + Collection validationFailureResults = null; + + for (PropertyDescriptor requiredProperty : requiredProperties) { + boolean requiredPropertyIsSet = validationContext.getProperty(requiredProperty).isSet(); + if (requiredPropertyIsSet != thisIsSelectedStrategy) { + String message = String.format(failureFormat, requiredProperty.getDisplayName(), + primaryStrategy.getName()); + if (validationFailureResults == null) { + validationFailureResults = new ArrayList(); + } + validationFailureResults.add(new ValidationResult.Builder() + .subject(requiredProperty.getDisplayName()) + .valid(false) + .explanation(message).build()); + } + } + + return validationFailureResults; + } + + public abstract AWSCredentialsProvider getCredentialsProvider(Map properties); + + public String getName() { + return name; + } + + + @Override + public boolean canCreateDerivedCredential(Map properties) { + return false; + } + + @Override + public AWSCredentialsProvider getDerivedCredentialsProvider(Map properties, + AWSCredentialsProvider primaryCredentialsProvider) { + throw new UnsupportedOperationException(); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AccessKeyPairCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AccessKeyPairCredentialsStrategy.java new file mode 100644 index 0000000000..5cf8869276 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AccessKeyPairCredentialsStrategy.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.internal.StaticCredentialsProvider; + + +/** + * Supports AWS credentials defined by an Access Key and Secret Key pair. + * + * @see + * BasicAWSCredentials + */ +public class AccessKeyPairCredentialsStrategy extends AbstractCredentialsStrategy { + + public AccessKeyPairCredentialsStrategy() { + super("Access Key Pair", new PropertyDescriptor[] { + CredentialPropertyDescriptors.ACCESS_KEY, + CredentialPropertyDescriptors.SECRET_KEY + }); + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + String accessKey = properties.get(CredentialPropertyDescriptors.ACCESS_KEY); + String secretKey = properties.get(CredentialPropertyDescriptors.SECRET_KEY); + BasicAWSCredentials creds = new BasicAWSCredentials(accessKey, secretKey); + return new StaticCredentialsProvider(creds); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AnonymousCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AnonymousCredentialsStrategy.java new file mode 100644 index 0000000000..4f8368ed14 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AnonymousCredentialsStrategy.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AnonymousAWSCredentials; +import com.amazonaws.internal.StaticCredentialsProvider; + + +/** + * Supports Anonymous AWS credentials. + * + * @see + * AnonymousAWSCredentials + */ +public class AnonymousCredentialsStrategy extends AbstractBooleanCredentialsStrategy { + + public AnonymousCredentialsStrategy() { + super("Anonymous Credentials", CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS); + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + AnonymousAWSCredentials creds = new AnonymousAWSCredentials(); + return new StaticCredentialsProvider(creds); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java new file mode 100644 index 0000000000..186a97b5fa --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/AssumeRoleCredentialsStrategy.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_ARN; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_NAME; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.MAX_SESSION_TIME; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_EXTERNAL_ID; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsStrategy; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; + + +/** + * Supports AWS credentials via Assume Role. Assume Role is a derived credential strategy, requiring a primary + * credential to retrieve and periodically refresh temporary credentials. + * + * @see + * STSAssumeRoleCredentialsProvider + */ +public class AssumeRoleCredentialsStrategy extends AbstractCredentialsStrategy { + + public AssumeRoleCredentialsStrategy() { + super("Assume Role", new PropertyDescriptor[] { + ASSUME_ROLE_ARN, + ASSUME_ROLE_NAME, + MAX_SESSION_TIME, + }); + } + + @Override + public boolean canCreatePrimaryCredential(Map properties) { + return false; + } + + @Override + public boolean canCreateDerivedCredential(Map properties) { + final String assumeRoleArn = properties.get(ASSUME_ROLE_ARN); + final String assumeRoleName = properties.get(ASSUME_ROLE_NAME); + if (assumeRoleArn != null && !assumeRoleArn.isEmpty() + && assumeRoleName != null && !assumeRoleName.isEmpty()) { + return true; + } + return false; + } + + @Override + public Collection validate(final ValidationContext validationContext, + final CredentialsStrategy primaryStrategy) { + final boolean assumeRoleArnIsSet = validationContext.getProperty(ASSUME_ROLE_ARN).isSet(); + final boolean assumeRoleNameIsSet = validationContext.getProperty(ASSUME_ROLE_NAME).isSet(); + final Integer maxSessionTime = validationContext.getProperty(MAX_SESSION_TIME).asInteger(); + final boolean assumeRoleExternalIdIsSet = validationContext.getProperty(ASSUME_ROLE_EXTERNAL_ID).isSet(); + + final Collection validationFailureResults = new ArrayList(); + + // Both role and arn name are req if present + if (assumeRoleArnIsSet ^ assumeRoleNameIsSet ) { + validationFailureResults.add(new ValidationResult.Builder().input("Assume Role Arn and Name") + .valid(false).explanation("Assume role requires both arn and name to be set").build()); + } + + // Session time only b/w 900 to 3600 sec (see sts session class) + if ( maxSessionTime < 900 || maxSessionTime > 3600 ) + validationFailureResults.add(new ValidationResult.Builder().valid(false).input(maxSessionTime + "") + .explanation(MAX_SESSION_TIME.getDisplayName() + + " must be between 900 and 3600 seconds").build()); + + // External ID should only be provided with viable Assume Role ARN and Name + if (assumeRoleExternalIdIsSet && (!assumeRoleArnIsSet || !assumeRoleNameIsSet)) { + validationFailureResults.add(new ValidationResult.Builder().input("Assume Role External ID") + .valid(false) + .explanation("Assume role requires both arn and name to be set with External ID") + .build()); + } + + return validationFailureResults; + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + throw new UnsupportedOperationException(); + } + + @Override + public AWSCredentialsProvider getDerivedCredentialsProvider(Map properties, + AWSCredentialsProvider primaryCredentialsProvider) { + final String assumeRoleArn = properties.get(ASSUME_ROLE_ARN); + final String assumeRoleName = properties.get(ASSUME_ROLE_NAME); + String rawMaxSessionTime = properties.get(MAX_SESSION_TIME); + rawMaxSessionTime = (rawMaxSessionTime != null) ? rawMaxSessionTime : MAX_SESSION_TIME.getDefaultValue(); + final Integer maxSessionTime = Integer.parseInt(rawMaxSessionTime.trim()); + final String assumeRoleExternalId = properties.get(ASSUME_ROLE_EXTERNAL_ID); + + STSAssumeRoleSessionCredentialsProvider.Builder builder = new STSAssumeRoleSessionCredentialsProvider + .Builder(assumeRoleArn, assumeRoleName) + .withLongLivedCredentialsProvider(primaryCredentialsProvider) + .withRoleSessionDurationSeconds(maxSessionTime); + if (assumeRoleExternalId != null && !assumeRoleExternalId.isEmpty()) { + builder = builder.withExternalId(assumeRoleExternalId); + } + final AWSCredentialsProvider credsProvider = builder.build(); + + return credsProvider; + } +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/ExplicitDefaultCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/ExplicitDefaultCredentialsStrategy.java new file mode 100644 index 0000000000..a8ac2e8605 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/ExplicitDefaultCredentialsStrategy.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; + + +/** + * Supports AWS Default Credentials. Compared to ImplicitDefaultCredentialsStrategy, this strategy is designed to be + * visible to the user, and depends on an affirmative selection from the user. + * + * @see + * DefaultAWSCredentialsProviderChain + */ +public class ExplicitDefaultCredentialsStrategy extends AbstractBooleanCredentialsStrategy { + + public ExplicitDefaultCredentialsStrategy() { + super("Default Credentials", CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS); + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + return new DefaultAWSCredentialsProviderChain(); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/FileCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/FileCredentialsStrategy.java new file mode 100644 index 0000000000..7fdf93b4d5 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/FileCredentialsStrategy.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.PropertiesFileCredentialsProvider; + + +/** + * Supports AWS credentials stored in a file. The file format should be a Java properties file like the following: + * + * + * accessKey = XXXXXXXXXXXXXXXXXXXX + * secretKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + * + * + * * @see + * PropertiesFileCredentialsProvider + */ +public class FileCredentialsStrategy extends AbstractCredentialsStrategy { + + public FileCredentialsStrategy() { + super("Credentials File", new PropertyDescriptor[] { + CredentialPropertyDescriptors.CREDENTIALS_FILE + }); + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + String credsFile = properties.get(CredentialPropertyDescriptors.CREDENTIALS_FILE); + return new PropertiesFileCredentialsProvider(credsFile); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/ImplicitDefaultCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/ImplicitDefaultCredentialsStrategy.java new file mode 100644 index 0000000000..d9717b31d9 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/ImplicitDefaultCredentialsStrategy.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; + + +/** + * Supports AWS Default Credentials. Compared to ExplicitDefaultCredentialsStrategy, this strategy is always + * willing to provide primary credentials, regardless of user input. It is intended to be used as an invisible + * fallback or default strategy. + */ +public class ImplicitDefaultCredentialsStrategy extends AbstractCredentialsStrategy { + + public ImplicitDefaultCredentialsStrategy() { + super("Default Credentials", new PropertyDescriptor[]{}); + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + return new DefaultAWSCredentialsProviderChain(); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/NamedProfileCredentialsStrategy.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/NamedProfileCredentialsStrategy.java new file mode 100644 index 0000000000..294f2ed073 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/factory/strategies/NamedProfileCredentialsStrategy.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory.strategies; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; + +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.profile.ProfileCredentialsProvider; + + +/** + * Supports AWS Credentials using a named profile configured in the credentials file (typically ~/.aws/credentials). + * + * @see + * ProfileCredentialsProvider + */ +public class NamedProfileCredentialsStrategy extends AbstractCredentialsStrategy { + + public NamedProfileCredentialsStrategy() { + super("Named Profile", new PropertyDescriptor[] { + CredentialPropertyDescriptors.PROFILE_NAME + }); + } + + @Override + public AWSCredentialsProvider getCredentialsProvider(Map properties) { + String profileName = properties.get(CredentialPropertyDescriptors.PROFILE_NAME); + return new ProfileCredentialsProvider(profileName); + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java index ecc64e26cd..7c09bd46fe 100644 --- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/credentials/provider/service/AWSCredentialsProviderControllerService.java @@ -15,10 +15,12 @@ * limitations under the License. */ package org.apache.nifi.processors.aws.credentials.provider.service; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; @@ -29,72 +31,55 @@ import org.apache.nifi.components.ValidationResult; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.controller.ConfigurationContext; import org.apache.nifi.processor.exception.ProcessException; -import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors; +import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialsProviderFactory; import org.apache.nifi.reporting.InitializationException; -import static org.apache.nifi.processors.aws.AbstractAWSProcessor.ACCESS_KEY; -import static org.apache.nifi.processors.aws.AbstractAWSProcessor.SECRET_KEY; -import static org.apache.nifi.processors.aws.AbstractAWSProcessor.CREDENTIALS_FILE; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ACCESS_KEY; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.SECRET_KEY; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.CREDENTIALS_FILE; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.PROFILE_NAME; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_EXTERNAL_ID; import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; -import com.amazonaws.auth.PropertiesFileCredentialsProvider; -import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; -import com.amazonaws.internal.StaticCredentialsProvider; /** * Implementation of AWSCredentialsProviderService interface * * @see AWSCredentialsProviderService */ -@CapabilityDescription("Defines credentials for Amazon Web Services processors.") +@CapabilityDescription("Defines credentials for Amazon Web Services processors. " + + "Uses default credentials without configuration. " + + "Default credentials support EC2 instance profile/role, default user profile, environment variables, etc. " + + "Additional options include access key / secret key pairs, credentials file, named profile, and assume role credentials.") @Tags({ "aws", "credentials","provider" }) public class AWSCredentialsProviderControllerService extends AbstractControllerService implements AWSCredentialsProviderService { - /** - * AWS Role Arn used for cross account access - * - * @see AWS ARN - */ - public static final PropertyDescriptor ASSUME_ROLE_ARN = new PropertyDescriptor.Builder().name("Assume Role ARN") - .expressionLanguageSupported(false).required(false).addValidator(StandardValidators.NON_EMPTY_VALIDATOR) - .sensitive(false).description("The AWS Role ARN for cross account access. This is used in conjunction with role name and session timeout").build(); - - /** - * The role name while creating aws role - */ - public static final PropertyDescriptor ASSUME_ROLE_NAME = new PropertyDescriptor.Builder().name("Assume Role Session Name") - .expressionLanguageSupported(false).required(false).addValidator(StandardValidators.NON_EMPTY_VALIDATOR) - .sensitive(false).description("The aws role name for cross account access. This is used in conjunction with role arn and session time out").build(); - - /** - * Max session time for role based credentials. The range is between 900 and 3600 seconds. - */ - public static final PropertyDescriptor MAX_SESSION_TIME = new PropertyDescriptor.Builder() - .name("Session Time") - .description("Session time for role based session (between 900 and 3600 seconds). This is used in conjunction with role arn and name") - .defaultValue("3600") - .required(false) - .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR) - .sensitive(false) - .build(); + public static final PropertyDescriptor ASSUME_ROLE_ARN = CredentialPropertyDescriptors.ASSUME_ROLE_ARN; + public static final PropertyDescriptor ASSUME_ROLE_NAME = CredentialPropertyDescriptors.ASSUME_ROLE_NAME; + public static final PropertyDescriptor MAX_SESSION_TIME = CredentialPropertyDescriptors.MAX_SESSION_TIME; private static final List properties; static { final List props = new ArrayList<>(); + props.add(USE_DEFAULT_CREDENTIALS); props.add(ACCESS_KEY); props.add(SECRET_KEY); props.add(CREDENTIALS_FILE); + props.add(PROFILE_NAME); + props.add(USE_ANONYMOUS_CREDENTIALS); props.add(ASSUME_ROLE_ARN); props.add(ASSUME_ROLE_NAME); props.add(MAX_SESSION_TIME); - + props.add(ASSUME_ROLE_EXTERNAL_ID); properties = Collections.unmodifiableList(props); } private volatile AWSCredentialsProvider credentialsProvider; + protected final CredentialsProviderFactory credentialsProviderFactory = new CredentialsProviderFactory(); @Override protected List getSupportedPropertyDescriptors() { @@ -108,82 +93,16 @@ public class AWSCredentialsProviderControllerService extends AbstractControllerS @Override protected Collection customValidate(final ValidationContext validationContext) { - - final boolean accessKeySet = validationContext.getProperty(ACCESS_KEY).isSet(); - final boolean secretKeySet = validationContext.getProperty(SECRET_KEY).isSet(); - final boolean assumeRoleArnIsSet = validationContext.getProperty(ASSUME_ROLE_ARN).isSet(); - final boolean assumeRoleNameIsSet = validationContext.getProperty(ASSUME_ROLE_NAME).isSet(); - final Integer maxSessionTime = validationContext.getProperty(MAX_SESSION_TIME).asInteger(); - - final boolean credentialsFileSet = validationContext.getProperty(CREDENTIALS_FILE).isSet(); - - final Collection validationFailureResults = new ArrayList<>(); - - // both keys are required if present - if ((accessKeySet && !secretKeySet) || (secretKeySet && !accessKeySet)) { - validationFailureResults.add(new ValidationResult.Builder().input("Access Key").valid(false) - .explanation("If setting Secret Key or Access Key, must set both").build()); - } - - // Either keys or creds file is valid - if ((secretKeySet || accessKeySet) && credentialsFileSet) { - validationFailureResults.add(new ValidationResult.Builder().input("Access Key").valid(false) - .explanation("Cannot set both Credentials File and Secret Key/Access Key").build()); - } - - // Both role and arn name are req if present - if (assumeRoleArnIsSet ^ assumeRoleNameIsSet ) { - validationFailureResults.add(new ValidationResult.Builder().input("Assume Role Arn and Name") - .valid(false).explanation("Assume role requires both arn and name to be set").build()); - } - - // Session time only b/w 900 to 3600 sec (see sts session class) - if ( maxSessionTime < 900 || maxSessionTime > 3600 ) - validationFailureResults.add(new ValidationResult.Builder().valid(false).input(maxSessionTime + "") - .subject(MAX_SESSION_TIME.getDisplayName() + - " can have value only between 900 and 3600 seconds").build()); - + final Collection validationFailureResults = + credentialsProviderFactory.validate(validationContext); return validationFailureResults; } @OnEnabled public void onConfigured(final ConfigurationContext context) throws InitializationException { - - final String accessKey = context.getProperty(ACCESS_KEY).evaluateAttributeExpressions().getValue(); - final String secretKey = context.getProperty(SECRET_KEY).evaluateAttributeExpressions().getValue(); - final String assumeRoleArn = context.getProperty(ASSUME_ROLE_ARN).getValue(); - final Integer maxSessionTime = context.getProperty(MAX_SESSION_TIME).asInteger(); - final String assumeRoleName = context.getProperty(ASSUME_ROLE_NAME).getValue(); - final String credentialsFile = context.getProperty(CREDENTIALS_FILE).getValue(); - - // Create creds provider from file or keys - if (credentialsFile != null) { - try { - getLogger().debug("Creating properties file credentials provider"); - credentialsProvider = new PropertiesFileCredentialsProvider(credentialsFile); - } catch (final Exception ioe) { - throw new ProcessException("Could not read Credentials File", ioe); - } - } - - if (credentialsProvider == null && accessKey != null && secretKey != null) { - getLogger().debug("Creating static credentials provider"); - credentialsProvider = new StaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)); - } - - // If no credentials explicitly provided, then create default one - if (credentialsProvider == null) { - getLogger().debug("Creating default credentials provider"); - credentialsProvider = new DefaultAWSCredentialsProviderChain(); - } - - if (credentialsProvider != null && assumeRoleArn != null && assumeRoleName != null) { - getLogger().debug("Creating sts assume role session credentials provider"); - - credentialsProvider = new STSAssumeRoleSessionCredentialsProvider.Builder(assumeRoleArn, assumeRoleName) - .withLongLivedCredentialsProvider(credentialsProvider) - .withRoleSessionDurationSeconds(maxSessionTime).build(); - } + final Map properties = context.getProperties(); + credentialsProvider = credentialsProviderFactory.getCredentialsProvider(context.getProperties()); + getLogger().debug("Using credentials provider: " + credentialsProvider.getClass()); } @Override diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java new file mode 100644 index 0000000000..d76db53628 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/MockAWSProcessor.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; + +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.PROFILE_NAME; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_ARN; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_NAME; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.MAX_SESSION_TIME; +import static org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors.ASSUME_ROLE_EXTERNAL_ID; + +import com.amazonaws.auth.AWSCredentialsProvider; +import org.apache.nifi.processors.aws.AbstractAWSCredentialsProviderProcessor; + + +/** + * Mock Processor implementation used to test CredentialsProviderFactory. + */ +public class MockAWSProcessor extends AbstractAWSCredentialsProviderProcessor { + + public final List properties = Arrays.asList( + USE_DEFAULT_CREDENTIALS, + ACCESS_KEY, + SECRET_KEY, + CREDENTIALS_FILE, + PROFILE_NAME, + USE_ANONYMOUS_CREDENTIALS, + ASSUME_ROLE_ARN, + ASSUME_ROLE_NAME, + MAX_SESSION_TIME, + ASSUME_ROLE_EXTERNAL_ID + ); + + @Override + protected List getSupportedPropertyDescriptors() { + return properties; + } + + @Override + public void onTrigger(final ProcessContext context, final ProcessSession session) { + + } + + @Override + protected Collection customValidate(final ValidationContext validationContext) { + CredentialsProviderFactory credsFactory = new CredentialsProviderFactory(); + final Collection validationFailureResults = credsFactory.validate(validationContext); + return validationFailureResults; + } + + /** + * Create client using credentials provider. This is the preferred way for creating clients + */ + @Override + protected AmazonS3Client createClient(final ProcessContext context, final AWSCredentialsProvider credentialsProvider, final ClientConfiguration config) { + getLogger().info("Creating client with credentials provider"); + final AmazonS3Client s3 = new AmazonS3Client(credentialsProvider, config); + return s3; + } + + /** + * Create client using AWSCredentials + * + * @deprecated use {@link #createClient(ProcessContext, AWSCredentialsProvider, ClientConfiguration)} instead + */ + @Override + protected AmazonS3Client createClient(final ProcessContext context, final AWSCredentials credentials, final ClientConfiguration config) { + getLogger().info("Creating client with awd credentials"); + + final AmazonS3Client s3 = new AmazonS3Client(credentials, config); + + return s3; + } + +} diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java new file mode 100644 index 0000000000..ccdffe38c1 --- /dev/null +++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/test/java/org/apache/nifi/processors/aws/credentials/provider/factory/TestCredentialsProviderFactory.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.nifi.processors.aws.credentials.provider.factory; + +import java.util.Map; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.processors.aws.s3.FetchS3Object; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; + +import com.amazonaws.auth.AnonymousAWSCredentials; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.auth.PropertiesFileCredentialsProvider; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; +import com.amazonaws.auth.profile.ProfileCredentialsProvider; +import com.amazonaws.internal.StaticCredentialsProvider; + +import org.junit.Assert; +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +/** + * Tests of the validation and credentials provider capabilities of CredentialsProviderFactory. + */ +public class TestCredentialsProviderFactory { + + @Test + public void testImpliedDefaultCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + assertEquals("credentials provider should be equal", DefaultAWSCredentialsProviderChain.class, + credentialsProvider.getClass()); + } + + @Test + public void testExplicitDefaultCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS, "true"); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + assertEquals("credentials provider should be equal", DefaultAWSCredentialsProviderChain.class, + credentialsProvider.getClass()); + } + + @Test + public void testExplicitDefaultCredentialsExclusive() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS, "true"); + runner.setProperty(CredentialPropertyDescriptors.ACCESS_KEY, "BogusAccessKey"); + runner.assertNotValid(); + } + + @Test + public void testAccessKeyPairCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS, "false"); + runner.setProperty(CredentialPropertyDescriptors.ACCESS_KEY, "BogusAccessKey"); + runner.setProperty(CredentialPropertyDescriptors.SECRET_KEY, "BogusSecretKey"); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + assertEquals("credentials provider should be equal", StaticCredentialsProvider.class, + credentialsProvider.getClass()); + } + + @Test + public void testAccessKeyPairIncomplete() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.ACCESS_KEY, "BogusAccessKey"); + runner.assertNotValid(); + } + + @Test + public void testAccessKeyPairIncompleteS3() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(FetchS3Object.class); + runner.setProperty(CredentialPropertyDescriptors.ACCESS_KEY, "BogusAccessKey"); + runner.assertNotValid(); + } + + @Test + public void testFileCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties"); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + assertEquals("credentials provider should be equal", PropertiesFileCredentialsProvider.class, + credentialsProvider.getClass()); + } + + @Test + public void testAssumeRoleCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties"); + runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn"); + runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession"); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + assertEquals("credentials provider should be equal", STSAssumeRoleSessionCredentialsProvider.class, + credentialsProvider.getClass()); + } + + @Test + public void testAssumeRoleCredentialsMissingARN() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties"); + runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession"); + runner.assertNotValid(); + } + + @Test + public void testAssumeRoleCredentialsInvalidSessionTime() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties"); + runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_ARN, "BogusArn"); + runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_NAME, "BogusSession"); + runner.setProperty(CredentialPropertyDescriptors.MAX_SESSION_TIME, "10"); + runner.assertNotValid(); + } + + @Test + public void testAssumeRoleExternalIdMissingArnAndName() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.CREDENTIALS_FILE, "src/test/resources/mock-aws-credentials.properties"); + runner.setProperty(CredentialPropertyDescriptors.ASSUME_ROLE_EXTERNAL_ID, "BogusExternalId"); + runner.assertNotValid(); + } + + @Test + public void testAnonymousCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS, "true"); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + final AWSCredentials creds = credentialsProvider.getCredentials(); + assertEquals("credentials should be equal", AnonymousAWSCredentials.class, creds.getClass()); + } + + @Test + public void testAnonymousAndDefaultCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS, "true"); + runner.setProperty(CredentialPropertyDescriptors.USE_ANONYMOUS_CREDENTIALS, "true"); + runner.assertNotValid(); + } + + @Test + public void testNamedProfileCredentials() throws Throwable { + final TestRunner runner = TestRunners.newTestRunner(MockAWSProcessor.class); + runner.setProperty(CredentialPropertyDescriptors.USE_DEFAULT_CREDENTIALS, "false"); + runner.setProperty(CredentialPropertyDescriptors.PROFILE_NAME, "BogusProfile"); + runner.assertValid(); + + Map properties = runner.getProcessContext().getProperties(); + final CredentialsProviderFactory factory = new CredentialsProviderFactory(); + final AWSCredentialsProvider credentialsProvider = factory.getCredentialsProvider(properties); + Assert.assertNotNull(credentialsProvider); + assertEquals("credentials provider should be equal", ProfileCredentialsProvider.class, + credentialsProvider.getClass()); + } +}