Skip to content

Commit

Permalink
Add 'wif-config' flag as cluster create option
Browse files Browse the repository at this point in the history
  • Loading branch information
JakobGray committed Aug 5, 2024
1 parent 9645301 commit 78317e9
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 46 deletions.
191 changes: 162 additions & 29 deletions cmd/ocm/create/cluster/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ var args struct {
clusterWideProxy c.ClusterWideProxy
gcpServiceAccountFile arguments.FilePath
gcpSecureBoot c.GcpSecurity
gcpAuthentication c.GcpAuthentication
gcpWifConfig string
etcdEncryption bool
subscriptionType string
marketplaceGcpTerms bool
Expand Down Expand Up @@ -125,6 +127,14 @@ func parseSubscriptionType(subscriptionTypeOption string) string {
return strings.Split(subscriptionTypeOption, " ")[0]
}

func setWifConfigOption(id, name string) string {
return fmt.Sprintf("%s (%s)", name, id)
}

func parseWifConfigOption(wifConfigOption string) string {
return strings.Split(wifConfigOption, " ")[0]
}

// Cmd Constant:
var Cmd = &cobra.Command{
Use: "cluster [flags] NAME",
Expand Down Expand Up @@ -364,6 +374,24 @@ func init() {
"Secure Boot enables the use of Shielded VMs in the Google Cloud Platform.",
)
arguments.SetQuestion(fs, "secure-boot-for-shielded-vms", "Secure boot support for Shielded VMs:")

fs.StringVar(
&args.gcpAuthentication.Type,
"gcp-auth-type",
c.AuthenticationWif,
"Method of authenticating GCP cluster",
)
arguments.SetQuestion(fs, "gcp-auth-type", "Authentication method:")
fs.MarkHidden("gcp-auth-type")

fs.StringVar(
&args.gcpWifConfig,
"wif-config",
"",
"Specifies the GCP Workload Identity Federation config used to authenticate.",
)
arguments.SetQuestion(fs, "wif-config", "WIF Configuration:")
Cmd.RegisterFlagCompletionFunc("wif-config", arguments.MakeCompleteFunc(getWifConfigNameOptions))
}

func osdProviderOptions(_ *sdk.Connection) ([]arguments.Option, error) {
Expand All @@ -373,6 +401,13 @@ func osdProviderOptions(_ *sdk.Connection) ([]arguments.Option, error) {
}, nil
}

func gcpAuthenticationOptions(_ *sdk.Connection) ([]arguments.Option, error) {
return []arguments.Option{
{Value: c.AuthenticationWif, Description: ""},
{Value: c.AuthenticationKey, Description: ""},
}, nil
}

func getRegionOptions(connection *sdk.Connection) ([]arguments.Option, error) {
regions, err := provider.GetRegions(connection.ClustersMgmt().V1(), args.provider, args.ccs)
if err != nil {
Expand Down Expand Up @@ -499,6 +534,27 @@ func getMachineTypeOptions(connection *sdk.Connection) ([]arguments.Option, erro
args.provider, args.ccs.Enabled)
}

func getWifConfigOptions(wifConfigs []*cmv1.WifConfig) ([]arguments.Option, error) {
options := []arguments.Option{}
for _, wc := range wifConfigs {
option := wifConfigOption(wc.ID(), wc.DisplayName())
options = append(options, option)
}
return options, nil
}

func wifConfigOption(id string, name string) arguments.Option {
return arguments.Option{
Value: setWifConfigOption(id, name),
}
}

// getWifConfigNameOptions returns the wif config options for the cluster
// with display name as the value
func getWifConfigNameOptions(connection *sdk.Connection) ([]arguments.Option, error) {
return provider.GetWifConfigNameOptions(connection.ClustersMgmt().V1())
}

func networkTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{c.NetworkTypeSDN, c.NetworkTypeOVN}, cobra.ShellCompDirectiveDefault
}
Expand Down Expand Up @@ -589,6 +645,11 @@ func preRun(cmd *cobra.Command, argv []string) error {
return err
}

err = promptAuthentication(fs, connection)
if err != nil {
return err
}

err = arguments.PromptBool(fs, "multi-az")
if err != nil {
return err
Expand Down Expand Up @@ -754,6 +815,7 @@ func run(cmd *cobra.Command, argv []string) error {
DefaultIngress: defaultIngress,
SubscriptionType: args.subscriptionType,
GcpSecurity: args.gcpSecureBoot,
GcpAuthentication: args.gcpAuthentication,
}

cluster, err := c.CreateCluster(connection.ClustersMgmt().V1(), clusterConfig, args.dryRun)
Expand Down Expand Up @@ -1258,43 +1320,114 @@ func promptCCS(fs *pflag.FlagSet, presetCCS bool) error {
if err != nil {
return err
}
if args.ccs.Enabled {
switch args.provider {
case c.ProviderAWS:
err = arguments.PromptString(fs, "aws-account-id")
if err != nil {
return err
}

err = arguments.PromptString(fs, "aws-access-key-id")
if err != nil {
return err
}
err = arguments.CheckIgnoredCCSFlags(args.ccs)
if err != nil {
return err
}
return nil
}

err = arguments.PromptPassword(fs, "aws-secret-access-key")
if err != nil {
return err
}
case c.ProviderGCP:
// TODO: re-prompt when selected file is not readable / invalid JSON
err = arguments.PromptFilePath(fs, "service-account-file", true)
if err != nil {
return err
}
func promptAuthentication(fs *pflag.FlagSet, connection *sdk.Connection) error {
var err error
if !args.ccs.Enabled {
return nil
}
switch args.provider {
case c.ProviderAWS:
err = arguments.PromptString(fs, "aws-account-id")
if err != nil {
return err
}

if args.gcpServiceAccountFile == "" {
return fmt.Errorf("A valid GCP service account file must be specified for CCS clusters")
}
err = constructGCPCredentials(args.gcpServiceAccountFile, &args.ccs)
if err != nil {
return err
}
err = arguments.PromptString(fs, "aws-access-key-id")
if err != nil {
return err
}

err = arguments.PromptPassword(fs, "aws-secret-access-key")
if err != nil {
return err
}
case c.ProviderGCP:
err = promptGcpAuth(fs, connection)
if err != nil {
return err
}
}
err = arguments.CheckIgnoredCCSFlags(args.ccs)
return nil
}

func promptGcpAuth(fs *pflag.FlagSet, connection *sdk.Connection) error {
var err error

isWif := fs.Changed("wif-config")
isNonWif := fs.Changed("service-account-file")
if isWif && isNonWif {
return fmt.Errorf("can't use both wif-config and GCP service account file at the same time")
}

if !isWif && !isNonWif {
options, _ := gcpAuthenticationOptions(connection)
err = arguments.PromptOneOf(fs, "gcp-auth-type", options)
if err != nil {
return err
}
}
if isWif {
args.gcpAuthentication.Type = c.AuthenticationWif
} else if isNonWif {
args.gcpAuthentication.Type = c.AuthenticationKey
}

switch args.gcpAuthentication.Type {
case c.AuthenticationWif:
err = promptWifConfig(fs, connection)
if err != nil {
return err
}
case c.AuthenticationKey:
// TODO: re-prompt when selected file is not readable / invalid JSON
err = arguments.PromptFilePath(fs, "service-account-file", true)
if err != nil {
return err
}

if args.gcpServiceAccountFile == "" {
return fmt.Errorf("a valid GCP service account file must be specified for CCS clusters")
}
err = constructGCPCredentials(args.gcpServiceAccountFile, &args.ccs)
if err != nil {
return err
}
}
return nil
}

func promptWifConfig(fs *pflag.FlagSet, connection *sdk.Connection) error {
wifConfigs, err := provider.GetWifConfigs(connection.ClustersMgmt().V1())
if err != nil {
return err
}
options, err := getWifConfigOptions(wifConfigs)
if err != nil {
return err
}
err = arguments.PromptOneOf(fs, "wif-config", options)
if err != nil {
return err
}
if args.interactive {
args.gcpWifConfig = parseWifConfigOption(args.gcpWifConfig)
}

// map wif name to wif id
wifMapping := map[string]string{}
for _, wc := range wifConfigs {
wifMapping[wc.DisplayName()] = wc.ID()
}

args.gcpAuthentication.Id = wifMapping[args.gcpWifConfig]
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/nwidger/jsoncolor v0.3.2
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.8
github.com/openshift-online/ocm-sdk-go v0.1.422
github.com/openshift-online/ocm-sdk-go v0.1.433
github.com/openshift/rosa v1.2.24
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,8 @@ github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
github.com/openshift-online/ocm-sdk-go v0.1.422 h1:NWXLNTg7sLgUJRM3tyuk/QuVbUCRuMH+aLlbCKNzXWc=
github.com/openshift-online/ocm-sdk-go v0.1.422/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/openshift-online/ocm-sdk-go v0.1.433 h1:8i0Si9KrFkMprMBGR1a4ppmgVezMBjjXXkvhv28OVUk=
github.com/openshift-online/ocm-sdk-go v0.1.433/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/openshift/rosa v1.2.24 h1:vv0yYnWHx6CCPEAau/0rS54P2ksaf+uWXb1TQPWxiYE=
github.com/openshift/rosa v1.2.24/go.mod h1:MVXB27O3PF8WoOic23I03mmq6/9kVxpFx6FKyLMCyrQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
Expand Down
52 changes: 38 additions & 14 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const (

NetworkTypeSDN = "OpenShiftSDN"
NetworkTypeOVN = "OVNKubernetes"

AuthenticationWif = "Workload Identity Federation (WIF)"
AuthenticationKey = "Service account"
)

type DefaultIngressSpec struct {
Expand Down Expand Up @@ -92,6 +95,9 @@ type Spec struct {

// Gcp-specific settings
GcpSecurity GcpSecurity

// GCP Authentication settings
GcpAuthentication GcpAuthentication
}

type Autoscaling struct {
Expand Down Expand Up @@ -151,6 +157,11 @@ type GcpSecurity struct {
SecureBoot bool `json:"secure_boot,omitempty"`
}

type GcpAuthentication struct {
Type string
Id string
}

type AddOnItem struct {
ID string
Name string
Expand Down Expand Up @@ -404,21 +415,34 @@ func CreateCluster(cmv1Client *cmv1.Client, config Spec, dryRun bool) (*cmv1.Clu
}
clusterBuilder = clusterBuilder.AWS(awsBuilder)
case ProviderGCP:
if config.CCS.GCP.Type == "" || config.CCS.GCP.ClientEmail == "" ||
config.CCS.GCP.ProjectID == "" {
return nil, fmt.Errorf("Missing credentials for GCP CCS cluster")
switch config.GcpAuthentication.Type {
case AuthenticationWif:
if config.GcpAuthentication.Id == "" {
return nil, fmt.Errorf("missing WIF config ID")
}
gcpAuth := cmv1.NewGcpAuthentication().
Kind(cmv1.WifConfigKind).
Id(config.GcpAuthentication.Id)
gcpBuilder.Authentication(gcpAuth)
case AuthenticationKey:
if config.CCS.GCP.Type == "" || config.CCS.GCP.ClientEmail == "" ||
config.CCS.GCP.ProjectID == "" {
return nil, fmt.Errorf("missing credentials for GCP CCS cluster")
}
gcpBuilder.
Type(config.CCS.GCP.Type).
ProjectID(config.CCS.GCP.ProjectID).
PrivateKeyID(config.CCS.GCP.PrivateKeyID).
PrivateKey(config.CCS.GCP.PrivateKey).
ClientEmail(config.CCS.GCP.ClientEmail).
ClientID(config.CCS.GCP.ClientID).
AuthURI(config.CCS.GCP.AuthURI).
TokenURI(config.CCS.GCP.TokenURI).
AuthProviderX509CertURL(config.CCS.GCP.AuthProviderX509CertURL).
ClientX509CertURL(config.CCS.GCP.ClientX509CertURL)
default:
return nil, fmt.Errorf("unexpected GCP authentication method %q", config.GcpAuthentication.Type)
}
gcpBuilder.
Type(config.CCS.GCP.Type).
ProjectID(config.CCS.GCP.ProjectID).
PrivateKeyID(config.CCS.GCP.PrivateKeyID).
PrivateKey(config.CCS.GCP.PrivateKey).
ClientEmail(config.CCS.GCP.ClientEmail).
ClientID(config.CCS.GCP.ClientID).
AuthURI(config.CCS.GCP.AuthURI).
TokenURI(config.CCS.GCP.TokenURI).
AuthProviderX509CertURL(config.CCS.GCP.AuthProviderX509CertURL).
ClientX509CertURL(config.CCS.GCP.ClientX509CertURL)

if isGCPNetworkExists(config.ExistingVPC) {
gcpNetwork := cmv1.NewGCPNetwork().VPCName(config.ExistingVPC.VPCName).
Expand Down
Loading

0 comments on commit 78317e9

Please sign in to comment.