Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCM-12363 | feat: HCP sharedVPC functionality to create/operatoroles #2617

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
OCM-12363 | feat: HCP sharedVPC functionality to create/operatoroles
  • Loading branch information
hunterkepley committed Nov 11, 2024
commit 4be740e0ec7315d549f56a0de44bc4a005299a25
51 changes: 41 additions & 10 deletions cmd/create/operatorroles/by_prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func handleOperatorRolesPrefixOptions(r *rosa.Runtime, cmd *cobra.Command) {
func handleOperatorRoleCreationByPrefix(r *rosa.Runtime, env string,
permissionsBoundary string, mode string,
policies map[string]*cmv1.AWSSTSPolicy,
defaultPolicyVersion string) error {
defaultPolicyVersion string, isSharedVpc bool) error {
oidcConfig, err := r.OCMClient.GetOidcConfig(args.oidcConfigId)
if err != nil {
r.Reporter.Errorf("There was a problem retrieving OIDC Config '%s': %v", args.oidcConfigId, err)
Expand All @@ -88,7 +88,8 @@ func handleOperatorRoleCreationByPrefix(r *rosa.Runtime, env string,
operatorRolesPrefix := args.prefix
oidcEndpointUrl := oidcConfig.IssuerUrl()
installerRoleArn := args.installerRoleArn
sharedVpcRoleArn := args.sharedVpcRoleArn
sharedVpcRoleArn := args.vpcEndpointRoleArn
sharedVpcEndpointRoleArn := args.sharedVpcRoleArn

validateArgumentsOperatorRolesCreationByPrefix(r, operatorRolesPrefix, oidcEndpointUrl, installerRoleArn)

Expand Down Expand Up @@ -125,11 +126,6 @@ func handleOperatorRoleCreationByPrefix(r *rosa.Runtime, env string,
r.Reporter.Errorf("Failed to determine if cluster has managed policies: %v", err)
os.Exit(1)
}
if managedPolicies && sharedVpcRoleArn != "" {
r.Reporter.Errorf("Installer role '%s' has managed policies, the 'shared-vpc-role-arn' flag is not "+
"supported for managed policies", installerRoleArn)
os.Exit(1)
}
awsCreator, err := r.AWSClient.GetCreator()
if err != nil {
r.Reporter.Errorf("Unable to get IAM credentials: %v", err)
Expand Down Expand Up @@ -180,7 +176,8 @@ func handleOperatorRoleCreationByPrefix(r *rosa.Runtime, env string,
defaultPolicyVersion, policies,
credRequests, managedPolicies,
path, operatorIAMRoleList,
oidcEndpointUrl, hostedCPPolicies, sharedVpcRoleArn)
oidcEndpointUrl, hostedCPPolicies, sharedVpcRoleArn, sharedVpcEndpointRoleArn,
isSharedVpc)
if err != nil {
r.Reporter.Errorf("There was an error creating the operator roles: %s", err)
isThrottle := "false"
Expand Down Expand Up @@ -293,8 +290,8 @@ func createRolesByPrefix(r *rosa.Runtime, prefix string, permissionsBoundary str
policies map[string]*cmv1.AWSSTSPolicy, credRequests map[string]*cmv1.STSOperator,
managedPolicies bool, path string,
operatorIAMRoleList []*cmv1.OperatorIAMRole,
oidcEndpointUrl string, hostedCPPolicies bool, sharedVpcRoleArn string) error {
isSharedVpc := sharedVpcRoleArn != ""
oidcEndpointUrl string, hostedCPPolicies bool, sharedVpcRoleArn string, sharedVpcEndpointRoleArn string,
isSharedVpc bool) error {

for credrequest, operator := range credRequests {
roleArn := aws.FindOperatorRoleBySTSOperator(operatorIAMRoleList, operator)
Expand All @@ -313,6 +310,21 @@ func createRolesByPrefix(r *rosa.Runtime, prefix string, permissionsBoundary str
if err != nil {
return err
}
if isSharedVpc {
if credrequest == aws.IngressOperatorCloudCredentialsRoleType {
policyArn, err = attachHcpSharedVpcPolicy(r, sharedVpcRoleArn, roleName, operator,
path, defaultPolicyVersion)
if err != nil {
return err
}
} else if credrequest == aws.ControlPlaneCloudCredentialsRoleType {
policyArn, err = attachHcpSharedVpcPolicy(r, sharedVpcEndpointRoleArn, roleName,
operator, path, defaultPolicyVersion)
if err != nil {
return err
}
}
}
} else {
policyArn = aws.GetOperatorPolicyARN(r.Creator.Partition, r.Creator.AccountID, prefix, operator.Namespace(),
operator.Name(), path)
Expand Down Expand Up @@ -503,3 +515,22 @@ func buildCommandsFromPrefix(r *rosa.Runtime, env string,
}
return awscb.JoinCommands(commands), nil
}

func attachHcpSharedVpcPolicy(r *rosa.Runtime, roleArn string, roleName string,
operator *cmv1.STSOperator, path string, defaultPolicyVersion string) (string, error) {
policyDetails := "{\n \"Version\": \"2012-10-17\",\n \"Statement\": {\n \"Effect\": \"Allow\",\n " +
"\"Action\": \"sts:AssumeRole\",\n \"Resource\": \"%{shared_vpc_role_arn}\"\n }\n}\n"
policyDetails = aws.InterpolatePolicyDocument(r.Creator.Partition, policyDetails, map[string]string{
"shared_vpc_role_arn": roleArn,
})
policy := aws.GetOperatorPolicyARN(r.Creator.Partition, r.Creator.AccountID,
"rosa-assume-role-"+roleName, operator.Namespace(), operator.Name(), path)

var err error
policyArn, err := r.AWSClient.EnsurePolicy(policy, policyDetails, defaultPolicyVersion,
map[string]string{tags.RedHatManaged: helper.True}, path)
if err != nil {
return policyArn, err
}
return policyArn, nil
}
41 changes: 36 additions & 5 deletions cmd/create/operatorroles/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package operatorroles

import (
"fmt"
"os"

cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
Expand All @@ -31,10 +32,12 @@ import (
)

const (
PrefixFlag = "prefix"
HostedCpFlag = "hosted-cp"
OidcConfigIdFlag = "oidc-config-id"
InstallerRoleArnFlag = "role-arn"
PrefixFlag = "prefix"
HostedCpFlag = "hosted-cp"
OidcConfigIdFlag = "oidc-config-id"
InstallerRoleArnFlag = "role-arn"
vpcEndpointRoleArnFlag = "vpc-endpoint-role-arn"
hostedZoneRoleArnFlag = "route53-role-arn"
)

var args struct {
Expand All @@ -46,6 +49,7 @@ var args struct {
oidcConfigId string
sharedVpcRoleArn string
channelGroup string
vpcEndpointRoleArn string
}

var Cmd = &cobra.Command{
Expand Down Expand Up @@ -129,6 +133,26 @@ func init() {
)
flags.MarkHidden("channel-group")

flags.StringVar(
&args.vpcEndpointRoleArn,
vpcEndpointRoleArnFlag,
"",
"AWS IAM Role ARN with policy attached, associated with the shared VPC."+
" Grants permissions necessary to communicate with and handle a cross-account VPC. "+
"This flag deprecates '--shared-vpc-role-arn'.",
)

flags.StringVar(
&args.sharedVpcRoleArn,
hostedZoneRoleArnFlag,
"",
"AWS IAM Role Arn with policy attached, associated with the shared VPC used for Hosted Control Plane clusters."+
" Grants permission necessary to handle route53 operations associated with a cross-acount VPC.",
)

flags.MarkDeprecated("shared-vpc-role-arn", fmt.Sprintf("'--shared-vpc-role-arn' will be replaced with %s "+
"in future versions of ROSA", hostedZoneRoleArnFlag))

interactive.AddModeFlag(Cmd)
confirm.AddFlag(flags)
interactive.AddFlag(flags)
Expand All @@ -151,6 +175,13 @@ func run(cmd *cobra.Command, argv []string) {
}
}

isHcpSharedVpc, err := validateSharedVpcInputs(args.hostedCp, args.vpcEndpointRoleArn,
args.sharedVpcRoleArn)
if err != nil {
r.Reporter.Errorf("Invalid configuration: %s", err)
os.Exit(1)
}

env, err := ocm.GetEnv()
if err != nil {
r.Reporter.Errorf("Failed to determine OCM environment: %v", err)
Expand Down Expand Up @@ -266,7 +297,7 @@ func run(cmd *cobra.Command, argv []string) {
os.Exit(1)
}
err = handleOperatorRoleCreationByPrefix(r, env, permissionsBoundary,
mode, policies, latestPolicyVersion)
mode, policies, latestPolicyVersion, isHcpSharedVpc)
if err != nil {
r.Reporter.Errorf("Error creating operator roles: %s", err)
os.Exit(1)
Expand Down
13 changes: 13 additions & 0 deletions cmd/create/operatorroles/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package operatorroles

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestDnsDomain(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Operator role suite")
}
32 changes: 32 additions & 0 deletions cmd/create/operatorroles/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package operatorroles

import errors "github.com/zgalor/weberr"

func validateSharedVpcInputs(hostedCp bool, vpcEndpointRoleArn string,
route53RoleArn string) (bool, error) {

if !hostedCp {
if vpcEndpointRoleArn != "" {
return false, errors.UserErrorf("Can only use '%s' flag for Hosted Control Plane operator roles",
vpcEndpointRoleArnFlag)
}
} else {
if vpcEndpointRoleArn != "" && route53RoleArn == "" {
return false, errors.UserErrorf(
"Must supply '%s' flag when using the '%s' flag",
hostedZoneRoleArnFlag,
vpcEndpointRoleArnFlag,
)
}

if route53RoleArn != "" && vpcEndpointRoleArn == "" {
return false, errors.UserErrorf(
"Must supply '%s' flag when using the '%s' flag",
vpcEndpointRoleArnFlag,
hostedZoneRoleArnFlag,
)
}
}

return hostedCp && vpcEndpointRoleArn != "" && route53RoleArn != "", nil
}
76 changes: 76 additions & 0 deletions cmd/create/operatorroles/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package operatorroles

import (
"go.uber.org/mock/gomock"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Create dns domain", func() {
var ctrl *gomock.Controller

BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
})
AfterEach(func() {
ctrl.Finish()
})

Context("validateSharedVpcInputs", func() {
When("Validate flags properly for shared VPC for HCP op roles", func() {
It("OK: Should pass with no error, for classic (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(false, "", "")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
})
It("OK: Should pass with no error, for HCP (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(true, "", "")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
})
It("KO: Should error when using classic and the first flag (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(false, "123", "")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Can only use '%s' flag for Hosted Control Plane operator "+
"roles", vpcEndpointRoleArnFlag,
))
})
It("KO: Should error when using classic and the vpc endpoint flag (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(false, "123", "")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Can only use '%s' flag for Hosted Control Plane operator "+
"roles", vpcEndpointRoleArnFlag,
))
})
It("KO: Should error when using classic and both flags (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(false, "123", "123")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Can only use '%s' flag for Hosted Control Plane operator "+
"roles", vpcEndpointRoleArnFlag,
))
})
It("KO: Should error when using HCP and the first flag but not the second (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(true, "123", "")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Must supply '%s' flag when using the '%s' flag",
hostedZoneRoleArnFlag,
vpcEndpointRoleArnFlag,
))
})
It("KO: Should error when using HCP and the second flag but not the first (return false)", func() {
usingSharedVpc, err := validateSharedVpcInputs(true, "", "123")
Expect(usingSharedVpc).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("Must supply '%s' flag when using the '%s' flag",
vpcEndpointRoleArnFlag,
hostedZoneRoleArnFlag,
))
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@
- name: role-arn
- name: shared-vpc-role-arn
- name: "yes"
- name: vpc-endpoint-role-arn
- name: route53-role-arn
1 change: 1 addition & 0 deletions pkg/aws/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ const (
HCPSuffixPattern = "HCP-ROSA"

IngressOperatorCloudCredentialsRoleType = "ingress_operator_cloud_credentials"
ControlPlaneCloudCredentialsRoleType = "control_plane_operator_credentials"

TrueString = "true"
)
Expand Down