Skip to content

Commit

Permalink
Add infrastructure ConfigValidator for checking VPC IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
stoyanr committed Aug 27, 2021
1 parent e5b6a32 commit fe581cc
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 54 deletions.
51 changes: 24 additions & 27 deletions pkg/aws/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ func (c *Client) GetAccountID(ctx context.Context) (string, error) {
return *getCallerIdentityOutput.Account, nil
}

// GetInternetGateway returns the ID of the internet gateway attached to the given VPC <vpcID>.
// GetVPCInternetGateway returns the ID of the internet gateway attached to the given VPC <vpcID>.
// If there is no internet gateway attached, the returned string will be empty.
func (c *Client) GetInternetGateway(ctx context.Context, vpcID string) (string, error) {
func (c *Client) GetVPCInternetGateway(ctx context.Context, vpcID string) (string, error) {
describeInternetGatewaysInput := &ec2.DescribeInternetGatewaysInput{
Filters: []*ec2.Filter{
{
Expand All @@ -115,34 +115,27 @@ func (c *Client) GetInternetGateway(ctx context.Context, vpcID string) (string,
return "", err
}

if describeInternetGatewaysOutput.InternetGateways != nil {
if *describeInternetGatewaysOutput.InternetGateways[0].InternetGatewayId == "" {
return "", fmt.Errorf("no attached internet gateway found for vpc %s", vpcID)
}
return *describeInternetGatewaysOutput.InternetGateways[0].InternetGatewayId, nil
if len(describeInternetGatewaysOutput.InternetGateways) > 0 {
return aws.StringValue(describeInternetGatewaysOutput.InternetGateways[0].InternetGatewayId), nil
}
return "", fmt.Errorf("no attached internet gateway found for vpc %s", vpcID)
return "", nil
}

// VerifyVPCAttributes checks whether the VPC attributes are correct.
func (c *Client) VerifyVPCAttributes(ctx context.Context, vpcID string) error {
vpcAttribute, err := c.EC2.DescribeVpcAttributeWithContext(ctx, &ec2.DescribeVpcAttributeInput{VpcId: &vpcID, Attribute: aws.String("enableDnsSupport")})
// GetVPCAttribute returns the value of the specified VPC attribute.
func (c *Client) GetVPCAttribute(ctx context.Context, vpcID string, attribute string) (bool, error) {
vpcAttribute, err := c.EC2.DescribeVpcAttributeWithContext(ctx, &ec2.DescribeVpcAttributeInput{VpcId: &vpcID, Attribute: aws.String(attribute)})
if err != nil {
return err
}
if vpcAttribute.EnableDnsSupport == nil || vpcAttribute.EnableDnsSupport.Value == nil || !*vpcAttribute.EnableDnsSupport.Value {
return fmt.Errorf("invalid VPC attributes: `enableDnsSupport` must be set to `true`")
return false, err
}

vpcAttribute, err = c.EC2.DescribeVpcAttributeWithContext(ctx, &ec2.DescribeVpcAttributeInput{VpcId: &vpcID, Attribute: aws.String("enableDnsHostnames")})
if err != nil {
return err
}
if vpcAttribute.EnableDnsHostnames == nil || vpcAttribute.EnableDnsHostnames.Value == nil || !*vpcAttribute.EnableDnsHostnames.Value {
return fmt.Errorf("invalid VPC attributes: `enableDnsHostnames` must be set to `true`")
switch attribute {
case "enableDnsSupport":
return vpcAttribute.EnableDnsSupport != nil && vpcAttribute.EnableDnsSupport.Value != nil && *vpcAttribute.EnableDnsSupport.Value, nil
case "enableDnsHostnames":
return vpcAttribute.EnableDnsHostnames != nil && vpcAttribute.EnableDnsHostnames.Value != nil && *vpcAttribute.EnableDnsHostnames.Value, nil
default:
return false, nil
}

return nil
}

// DeleteObjectsWithPrefix deletes the s3 objects with the specific <prefix> from <bucket>. If it does not exist,
Expand Down Expand Up @@ -427,11 +420,15 @@ func (c *Client) DeleteSecurityGroup(ctx context.Context, id string) error {
return ignoreNotFound(err)
}

func ignoreNotFound(err error) error {
if err == nil {
return nil
func IsNotFoundError(err error) bool {
if aerr, ok := err.(awserr.Error); ok && (aerr.Code() == elb.ErrCodeAccessPointNotFoundException || aerr.Code() == "InvalidGroup.NotFound" || aerr.Code() == "InvalidVpcID.NotFound") {
return true
}
if aerr, ok := err.(awserr.Error); ok && (aerr.Code() == elb.ErrCodeAccessPointNotFoundException || aerr.Code() == "InvalidGroup.NotFound") {
return false
}

func ignoreNotFound(err error) error {
if err == nil || IsNotFoundError(err) {
return nil
}
return err
Expand Down
41 changes: 21 additions & 20 deletions pkg/aws/client/mock/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/aws/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const (
// Interface is an interface which must be implemented by AWS clients.
type Interface interface {
GetAccountID(ctx context.Context) (string, error)
GetInternetGateway(ctx context.Context, vpcID string) (string, error)
VerifyVPCAttributes(ctx context.Context, vpcID string) error
GetVPCInternetGateway(ctx context.Context, vpcID string) (string, error)
GetVPCAttribute(ctx context.Context, vpcID string, attribute string) (bool, error)

// S3 wrappers
DeleteObjectsWithPrefix(ctx context.Context, bucket, prefix string) error
Expand Down
5 changes: 1 addition & 4 deletions pkg/controller/infrastructure/actuator_reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ func generateTerraformInfraConfig(ctx context.Context, infrastructure *extension
case infrastructureConfig.Networks.VPC.ID != nil:
createVPC = false
existingVpcID := *infrastructureConfig.Networks.VPC.ID
if err := awsClient.VerifyVPCAttributes(ctx, existingVpcID); err != nil {
return nil, err
}
existingInternetGatewayID, err := awsClient.GetInternetGateway(ctx, existingVpcID)
existingInternetGatewayID, err := awsClient.GetVPCInternetGateway(ctx, existingVpcID)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/infrastructure/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package infrastructure

import (
"github.com/gardener/gardener-extension-provider-aws/pkg/aws"
awsclient "github.com/gardener/gardener-extension-provider-aws/pkg/aws/client"
"github.com/gardener/gardener/extensions/pkg/controller/infrastructure"
"sigs.k8s.io/controller-runtime/pkg/log"

"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/manager"
Expand All @@ -40,6 +42,7 @@ type AddOptions struct {
func AddToManagerWithOptions(mgr manager.Manager, opts AddOptions) error {
return infrastructure.Add(mgr, infrastructure.AddArgs{
Actuator: NewActuator(),
ConfigValidator: NewConfigValidator(awsclient.FactoryFunc(awsclient.NewInterface), log.Log),
ControllerOptions: opts.Controller,
Predicates: infrastructure.DefaultPredicates(opts.IgnoreOperationAnnotation),
Type: aws.Type,
Expand Down
112 changes: 112 additions & 0 deletions pkg/controller/infrastructure/configvalidator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file
//
// 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 infrastructure

import (
"context"
"fmt"

"github.com/gardener/gardener-extension-provider-aws/pkg/apis/aws/helper"
"github.com/gardener/gardener-extension-provider-aws/pkg/aws"
awsclient "github.com/gardener/gardener-extension-provider-aws/pkg/aws/client"

"github.com/gardener/gardener/extensions/pkg/controller/common"
"github.com/gardener/gardener/extensions/pkg/controller/infrastructure"
extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// configValidator implements ConfigValidator for aws infrastructure resources.
type configValidator struct {
common.ClientContext
awsClientFactory awsclient.Factory
logger logr.Logger
}

// NewConfigValidator creates a new ConfigValidator.
func NewConfigValidator(awsClientFactory awsclient.Factory, logger logr.Logger) infrastructure.ConfigValidator {
return &configValidator{
awsClientFactory: awsClientFactory,
logger: logger.WithName("aws-infrastructure-config-validator"),
}
}

// Validate validates the provider config of the given infrastructure resource with the cloud provider.
func (c *configValidator) Validate(ctx context.Context, infra *extensionsv1alpha1.Infrastructure) field.ErrorList {
allErrs := field.ErrorList{}

logger := c.logger.WithValues("infrastructure", client.ObjectKeyFromObject(infra))

// Get provider config from the infrastructure resource
config, err := helper.InfrastructureConfigFromInfrastructure(infra)
if err != nil {
allErrs = append(allErrs, field.InternalError(nil, err))
return allErrs
}

// Create AWS client
credentials, err := aws.GetCredentialsFromSecretRef(ctx, c.Client(), infra.Spec.SecretRef, false)
if err != nil {
allErrs = append(allErrs, field.InternalError(nil, fmt.Errorf("could not get AWS credentials: %+v", err)))
return allErrs
}
awsClient, err := c.awsClientFactory.NewClient(string(credentials.AccessKeyID), string(credentials.SecretAccessKey), infra.Spec.Region)
if err != nil {
allErrs = append(allErrs, field.InternalError(nil, fmt.Errorf("could not create AWS client: %+v", err)))
return allErrs
}

// Validate infrastructure config
if config.Networks.VPC.ID != nil {
logger.Info("Validating infrastructure networks.vpc.id")
allErrs = append(allErrs, c.validateVPC(ctx, awsClient, *config.Networks.VPC.ID, field.NewPath("networks", "vpc", "id"))...)
}

return allErrs
}

func (c *configValidator) validateVPC(ctx context.Context, awsClient awsclient.Interface, vpcID string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

// Verify that the VPC exists and the enableDnsSupport and enableDnsHostnames VPC attributes are both true
for _, attribute := range []string{"enableDnsSupport", "enableDnsHostnames"} {
value, err := awsClient.GetVPCAttribute(ctx, vpcID, attribute)
if err != nil {
if awsclient.IsNotFoundError(err) {
allErrs = append(allErrs, field.NotFound(fldPath, vpcID))
} else {
allErrs = append(allErrs, field.InternalError(fldPath, fmt.Errorf("could not get VPC attribute %s for VPC %s: %w", attribute, vpcID, err)))
}
return allErrs
}
if !value {
allErrs = append(allErrs, field.Invalid(fldPath, vpcID, fmt.Sprintf("VPC attribute %s must be set to true", attribute)))
}
}

// Verify that there is an internet gateway attached to the VPC
internetGatewayID, err := awsClient.GetVPCInternetGateway(ctx, vpcID)
if err != nil {
allErrs = append(allErrs, field.InternalError(fldPath, fmt.Errorf("could not get internet gateway for VPC %s: %w", vpcID, err)))
return allErrs
}
if internetGatewayID == "" {
allErrs = append(allErrs, field.Invalid(fldPath, vpcID, "no attached internet gateway found"))
}

return allErrs
}
Loading

0 comments on commit fe581cc

Please sign in to comment.