From 818b3500a7f3d20e91f223aa303be1279cb34007 Mon Sep 17 00:00:00 2001 From: Joel Diaz Date: Fri, 1 Feb 2019 15:21:28 -0500 Subject: [PATCH] add credential validation do a pre-flight check of permissions using cloud-credentials-operator validation to do a check on the creds being used for installation the initial list of permissions that gathers the AWS actions needed to perform an installation are taken verbatim from the IAM group permissions the hive team has been using to perform installation/uninstallation with (there absolutely could be some excess actions that used to be needed, but may no longer be needed) note that the permissions checks are done with the assumption of IAM policies consisting of 'Resource: "*"'. so a list of ["ec2:CreateRoute", "ec2:CreateSubnet"] is evaluated as whether we can peform ` { "Statement": [ { "Action": [ "ec2:CreateRoute", "ec2:CreateSubnet" ], "Effect": "Allow", "Resource": "*" } ] } ` --- pkg/asset/installconfig/aws/permissions.go | 222 +++++++++++++++++- pkg/asset/installconfig/platformcredscheck.go | 10 +- 2 files changed, 229 insertions(+), 3 deletions(-) diff --git a/pkg/asset/installconfig/aws/permissions.go b/pkg/asset/installconfig/aws/permissions.go index 4e0b0d1123c..b3b86ce06c3 100644 --- a/pkg/asset/installconfig/aws/permissions.go +++ b/pkg/asset/installconfig/aws/permissions.go @@ -2,6 +2,224 @@ package aws import ( - _ "github.com/openshift/cloud-credential-operator/pkg/aws" - _ "github.com/openshift/cloud-credential-operator/pkg/controller/utils" + "github.com/aws/aws-sdk-go/aws/session" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + ccaws "github.com/openshift/cloud-credential-operator/pkg/aws" + credvalidator "github.com/openshift/cloud-credential-operator/pkg/controller/utils" ) + +var installPermissions = []string{ + // EC2 related perms + "ec2:AllocateAddress", + "ec2:AssociateAddress", + "ec2:AssociateDhcpOptions", + "ec2:AssociateRouteTable", + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateDhcpOptions", + "ec2:CreateInternetGateway", + "ec2:CreateNatGateway", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", + "ec2:CreateVolume", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeDhcpOptions", + "ec2:DescribeImages", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceCreditSpecifications", + "ec2:DescribeInstances", + "ec2:DescribeInternetGateways", + "ec2:DescribeKeyPairs", + "ec2:DescribeNatGateways", + "ec2:DescribeNetworkAcls", + "ec2:DescribePrefixLists", + "ec2:DescribeRegions", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeVpcs", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVolumes", + "ec2:DescribeVpcClassicLink", + "ec2:DescribeVpcClassicLinkDnsSupport", + "ec2:ModifyInstanceAttribute", + "ec2:ModifySubnetAttribute", + "ec2:ModifyVpcAttribute", + "ec2:RevokeSecurityGroupEgress", + "ec2:RunInstances", + "ec2:TerminateInstances", + "ec2:DeleteDhcpOptions", + "ec2:DeleteRoute", + "ec2:RevokeSecurityGroupIngress", + "ec2:DisassociateRouteTable", + "ec2:ReplaceRouteTableAssociation", + "ec2:DeleteRouteTable", + "ec2:DeleteSubnet", + "ec2:DescribeNetworkInterfaces", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:DeleteNatGateway", + "ec2:DeleteSecurityGroup", + "ec2:DetachInternetGateway", + "ec2:DeleteInternetGateway", + "ec2:ReleaseAddress", + "ec2:DeleteVpc", + + // ELB related perms + "elasticloadbalancing:AddTags", + "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer", + "elasticloadbalancing:AttachLoadBalancerToSubnets", + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateLoadBalancerListeners", + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:ConfigureHealthCheck", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:SetLoadBalancerPoliciesOfListener", + + // IAM related perms + "iam:AddRoleToInstanceProfile", + "iam:CreateInstanceProfile", + "iam:CreateRole", + "iam:DeleteInstanceProfile", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:GetInstanceProfile", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:GetUser", + "iam:ListInstanceProfilesForRole", + "iam:ListRoles", + "iam:ListUsers", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:RemoveRoleFromInstanceProfile", + "iam:SimulatePrincipalPolicy", + "iam:TagRole", + + // Route53 related perms + "route53:ChangeResourceRecordSets", + "route53:ChangeTagsForResource", + "route53:GetChange", + "route53:GetHostedZone", + "route53:CreateHostedZone", + "route53:DeleteHostedZone", + "route53:ListHostedZones", + "route53:ListHostedZonesByName", + "route53:ListResourceRecordSets", + "route53:ListTagsForResource", + "route53:UpdateHostedZoneComment", + + // S3 related perms + "s3:CreateBucket", + "s3:ListBucket", + "s3:GetBucketCors", + "s3:GetBucketWebsite", + "s3:GetBucketVersioning", + "s3:GetAccelerateConfiguration", + "s3:GetEncryptionConfiguration", + "s3:GetBucketRequestPayment", + "s3:GetBucketLogging", + "s3:GetLifecycleConfiguration", + "s3:GetBucketReplication", + "s3:GetReplicationConfiguration", + "s3:GetBucketLocation", + "s3:GetBucketTagging", + "s3:DeleteBucket", + "s3:PutBucketAcl", + "s3:PutBucketTagging", + "s3:PutEncryptionConfiguration", + + // More S3 (would be nice to limit 'Resource' to just the bucket we actualy interact with...) + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectTagging", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectTagging", + "s3:GetObjectVersion", + "s3:DeleteObject", + + // Uninstall-specific perms + "autoscaling:DescribeAutoScalingGroups", + "ec2:DeleteNetworkInterface", + "ec2:DeleteVolume", + "ec2:DeleteVpcEndpoints", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DeleteTargetGroup", + "iam:ListInstanceProfiles", + "iam:ListRolePolicies", + "iam:ListUserPolicies", + "tag:GetResources", +} + +// ValidateCreds will try to create an AWS session, and also verify that the current credentials +// are sufficient to perform an installation, and that they can be used for cluster runtime +// as either capable of creating new credentials for components that interact with the cloud or +// being able to be passed through as-is to the components that need cloud credentials +func ValidateCreds(ssn *session.Session) error { + creds, err := ssn.Config.Credentials.Get() + if err != nil { + return errors.Wrap(err, "getting creds from session") + } + + client, err := ccaws.NewClient([]byte(creds.AccessKeyID), []byte(creds.SecretAccessKey)) + if err != nil { + return errors.Wrap(err, "initialize cloud-credentials client") + } + + // Check whether we can do an installation + logger := logrus.New() + canInstall, err := credvalidator.CheckPermissionsAgainstActions(client, installPermissions, logger) + if err != nil { + return errors.Wrap(err, "checking install permissions") + } + if !canInstall { + return errors.New("current credentials insufficient for performing cluster installation") + } + + // Check whether we can mint new creds for cluster services needing to interact with the cloud + canMint, err := credvalidator.CheckCloudCredCreation(client, logger) + if err != nil { + return errors.Wrap(err, "mint credentials check") + } + + // Check whether we can use the current credentials in passthrough mode to satisfy + // cluster services needing to interact with the cloud + canPassthrough, err := credvalidator.CheckCloudCredPassthrough(client, logger) + if err != nil { + return errors.Wrap(err, "passthrough credentials check") + } + + if !canMint && !canPassthrough { + return errors.New("AWS credentials cannot be used to either create new creds or use as-is") + } + + return nil +} diff --git a/pkg/asset/installconfig/platformcredscheck.go b/pkg/asset/installconfig/platformcredscheck.go index 671ae141974..160f96c6f4f 100644 --- a/pkg/asset/installconfig/platformcredscheck.go +++ b/pkg/asset/installconfig/platformcredscheck.go @@ -10,6 +10,7 @@ import ( "github.com/openshift/installer/pkg/types/libvirt" "github.com/openshift/installer/pkg/types/none" "github.com/openshift/installer/pkg/types/openstack" + "github.com/pkg/errors" ) // PlatformCredsCheck is an asset that checks the platform credentials, asks for them or errors out if invalid @@ -35,7 +36,14 @@ func (a *PlatformCredsCheck) Generate(dependencies asset.Parents) error { platform := ic.Config.Platform.Name() switch platform { case aws.Name: - _, err = awsconfig.GetSession() + ssn, err := awsconfig.GetSession() + if err != nil { + return errors.Wrap(err, "creating AWS session") + } + err = awsconfig.ValidateCreds(ssn) + if err != nil { + return errors.Wrap(err, "validate AWS credentials") + } case libvirt.Name: case none.Name: case openstack.Name: