Skip to content

Commit

Permalink
feat(titus): Lookup server group names when converting TitusDeployDes…
Browse files Browse the repository at this point in the history
…cription (#4654)

-Additionally, removes requiresAuthorization from `ResourcesNameable` and removes `BasicAmazonDeployDescription` implementation of `ResourcesNameable`.  We will instead be requiring authorization on these resources via an implementation of `AllowedAccountsValidator` which runs prior to `DescriptionAuthorizer`.

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
jonsie and mergify[bot] committed Jun 5, 2020
1 parent e7152e0 commit 0763864
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ import com.netflix.spinnaker.clouddriver.aws.model.AmazonBlockDevice
import com.netflix.spinnaker.clouddriver.deploy.DeployDescription
import com.netflix.spinnaker.clouddriver.orchestration.events.OperationEvent
import com.netflix.spinnaker.clouddriver.security.resources.ApplicationNameable
import com.netflix.spinnaker.clouddriver.security.resources.ResourcesNameable
import groovy.transform.AutoClone
import groovy.transform.Canonical

@AutoClone
@Canonical
class BasicAmazonDeployDescription extends AbstractAmazonCredentialsDescription implements
DeployDescription, ApplicationNameable, ResourcesNameable {
DeployDescription, ApplicationNameable {
String application
String amiName
String stack
Expand Down Expand Up @@ -117,16 +116,6 @@ class BasicAmazonDeployDescription extends AbstractAmazonCredentialsDescription
return [application]
}

@Override
Collection<String> getNames() {
return securityGroupNames ?: []
}

@Override
boolean requiresAuthorization() {
return false
}

@Canonical
static class Capacity {
Integer min
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,16 +346,12 @@ class CloudDriverConfig {

@Bean
DescriptionAuthorizer descriptionAuthorizer(Registry registry,
ObjectMapper objectMapper,
Optional<FiatPermissionEvaluator> fiatPermissionEvaluator,
SecurityConfig.OperationsSecurityConfigurationProperties opsSecurityConfigProps,
DynamicConfigService dynamicConfigService) {
SecurityConfig.OperationsSecurityConfigurationProperties opsSecurityConfigProps) {
return new DescriptionAuthorizer(
registry,
objectMapper,
fiatPermissionEvaluator,
opsSecurityConfigProps,
dynamicConfigService
opsSecurityConfigProps
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@

import static java.lang.String.format;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Registry;
import com.netflix.spinnaker.clouddriver.security.config.SecurityConfig;
import com.netflix.spinnaker.clouddriver.security.resources.AccountNameable;
import com.netflix.spinnaker.clouddriver.security.resources.ApplicationNameable;
import com.netflix.spinnaker.clouddriver.security.resources.ResourcesNameable;
import com.netflix.spinnaker.fiat.shared.FiatPermissionEvaluator;
import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand All @@ -44,26 +41,20 @@ public class DescriptionAuthorizer<T> {
private final Logger log = LoggerFactory.getLogger(getClass());

private final Registry registry;
private final ObjectMapper objectMapper;
private final FiatPermissionEvaluator fiatPermissionEvaluator;
private final SecurityConfig.OperationsSecurityConfigurationProperties opsSecurityConfigProps;
private final DynamicConfigService dynamicConfigService;

private final Id skipAuthorizationId;
private final Id missingApplicationId;
private final Id authorizationId;

public DescriptionAuthorizer(
Registry registry,
ObjectMapper objectMapper,
Optional<FiatPermissionEvaluator> fiatPermissionEvaluator,
SecurityConfig.OperationsSecurityConfigurationProperties opsSecurityConfigProps,
DynamicConfigService dynamicConfigService) {
SecurityConfig.OperationsSecurityConfigurationProperties opsSecurityConfigProps) {
this.registry = registry;
this.objectMapper = objectMapper;
this.fiatPermissionEvaluator = fiatPermissionEvaluator.orElse(null);
this.opsSecurityConfigProps = opsSecurityConfigProps;
this.dynamicConfigService = dynamicConfigService;

this.skipAuthorizationId = registry.createId("authorization.skipped");
this.missingApplicationId = registry.createId("authorization.missingApplication");
Expand Down Expand Up @@ -114,29 +105,11 @@ public void authorize(T description, Errors errors) {
if (description instanceof ResourcesNameable) {
ResourcesNameable resourcesNameable = (ResourcesNameable) description;

if (!resourcesNameable.requiresAuthorization()
|| dynamicConfigService.isEnabled("aws.fiat.authorize.resources-nameable", false)) {
registry
.counter(
skipAuthorizationId.withTag(
"descriptionClass", description.getClass().getSimpleName()))
.increment();

Collection<String> resourceNames =
Optional.ofNullable(resourcesNameable.getNames()).orElse(Collections.emptyList());

log.info(
"Skipping authorization for operation={}, resource names={}, resource applications={}",
description.getClass().getSimpleName(),
resourceNames,
resourcesNameable.getResourceApplications().toString());
} else {
applications.addAll(
Optional.ofNullable(resourcesNameable.getResourceApplications())
.orElse(Collections.emptyList()).stream()
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}
applications.addAll(
Optional.ofNullable(resourcesNameable.getResourceApplications())
.orElse(Collections.emptyList()).stream()
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}

boolean hasPermission = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ class DescriptionAuthorizerSpec extends Specification {
def registry = new NoopRegistry()
def evaluator = Mock(FiatPermissionEvaluator)
def opsSecurityConfigProps
def dynamicConfigService = Mock(DynamicConfigService)

@Subject
DescriptionAuthorizer authorizer

def setup() {
opsSecurityConfigProps = new SecurityConfig.OperationsSecurityConfigurationProperties()
authorizer = new DescriptionAuthorizer(registry, new ObjectMapper(), Optional.of(evaluator), opsSecurityConfigProps, dynamicConfigService)
authorizer = new DescriptionAuthorizer(registry, Optional.of(evaluator), opsSecurityConfigProps)
}

def "should authorize passed description"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,4 @@ default Collection<String> getResourceApplications() {
.map(name -> Names.parseName(name).getApp())
.collect(Collectors.toList());
}

/**
* Determine if the resource should be authz evaluated by the {@link
* com.netflix.spinnaker.fiat.shared.FiatPermissionEvaluator}.
*
* @return boolean
*/
default boolean requiresAuthorization() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.netflix.spinnaker.clouddriver.aws.provider.view.AmazonSecurityGroupPr
import com.netflix.spinnaker.clouddriver.aws.provider.view.AmazonVpcProvider
import com.netflix.spinnaker.clouddriver.aws.security.AmazonCredentials
import com.netflix.spinnaker.clouddriver.aws.services.RegionScopedProviderFactory
import com.netflix.spinnaker.clouddriver.aws.services.SecurityGroupService
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import com.netflix.spinnaker.clouddriver.titus.client.model.Job
import com.netflix.spinnaker.clouddriver.titus.credentials.NetflixTitusCredentials
Expand Down Expand Up @@ -74,6 +75,26 @@ class AwsLookupUtil {
awsSecurityGroupProvider.getIdByName(awsDetails.awsAccount, region, providedSecurityGroup, awsDetails.vpcId)
}

/**
* Converts security groups to security group names. This handles the case wherein the list of
* security groups may include both IDs and names.
*/
List<String> convertSecurityGroupsToNames(String account, String region, List<String> securityGroups) {
Map awsDetails = awsAccountLookup.find {
it.titusAccount == account && it.region == region
}

RegionScopedProviderFactory.RegionScopedProvider regionScopedProvider = regionScopedProviderFactory.forRegion(accountCredentialsProvider.all.find {
it instanceof AmazonCredentials && it.name == awsDetails.awsAccount
}, region)

SecurityGroupService securityGroupService = regionScopedProvider.getSecurityGroupService()

return securityGroupService.resolveSecurityGroupNamesByStrategy(securityGroups) { List<String> ids ->
securityGroupService.getSecurityGroupNamesFromIds(ids)
}
}

String createSecurityGroupForApplication(account, region, application) {
Map awsDetails = awsAccountLookup.find {
it.titusAccount == account && it.region == region
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations
import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport
import com.netflix.spinnaker.clouddriver.titus.TitusOperation
import com.netflix.spinnaker.clouddriver.titus.caching.utils.AwsLookupUtil
import com.netflix.spinnaker.clouddriver.titus.deploy.description.TitusDeployDescription
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

@TitusOperation(AtomicOperations.CREATE_SERVER_GROUP)
@Component
class TitusDeployAtomicOperationConverter extends AbstractAtomicOperationsCredentialsSupport {

@Autowired
AwsLookupUtil awsLookupUtil

@Override
AtomicOperation convertOperation(Map input) {
new DeployAtomicOperation(convertDescription(input))
Expand All @@ -49,6 +54,13 @@ class TitusDeployAtomicOperationConverter extends AbstractAtomicOperationsCreden

def converted = objectMapper.convertValue(input, TitusDeployDescription)
converted.credentials = getCredentialsObject(input.credentials as String)

if (converted.securityGroups != null && !converted.securityGroups.isEmpty()) {
converted.setSecurityGroupNames(
awsLookupUtil.convertSecurityGroupsToNames(converted.account, converted.region, converted.securityGroups)
)
}

converted
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class TitusDeployDescription extends AbstractTitusCredentialsDescription
private String subnet;
private List<String> zones = new ArrayList<>();
private List<String> securityGroups = new ArrayList<>();
private List<String> securityGroupNames = new ArrayList<>();
private List<String> targetGroups = new ArrayList<>();
private List<String> softConstraints;
private List<String> hardConstraints;
Expand Down Expand Up @@ -239,6 +240,14 @@ public void setSecurityGroups(List<String> securityGroups) {
this.securityGroups = securityGroups;
}

public List<String> getSecurityGroupNames() {
return securityGroupNames;
}

public void setSecurityGroupNames(List<String> securityGroupNames) {
this.securityGroupNames = securityGroupNames;
}

public List<String> getTargetGroups() {
return targetGroups;
}
Expand Down

0 comments on commit 0763864

Please sign in to comment.