diff --git a/internal/commands/use/use.go b/internal/commands/use/use.go index 27844442..d7d81c50 100644 --- a/internal/commands/use/use.go +++ b/internal/commands/use/use.go @@ -220,9 +220,6 @@ func addConfig(cs config.ConfigurationSet, registration *registry.DiscoveryPlugi if _, err := cs.Bool("set-current", true, "Sets the current context in the kubeconfig to the selected cluster"); err != nil { return fmt.Errorf("adding set-current config: %w", err) } - if err := common.AddCommonIdentityConfig(cs); err != nil { - return fmt.Errorf("adding common identity config items: %w", err) - } if err := common.AddCommonClusterConfig(cs); err != nil { return fmt.Errorf("adding common cluster config items: %w", err) } diff --git a/pkg/app/to.go b/pkg/app/to.go index 6d3cba35..02639e84 100644 --- a/pkg/app/to.go +++ b/pkg/app/to.go @@ -213,9 +213,6 @@ func (a *App) buildConnectToConfig(configFile string, discoveryProvider string, if err := cs.AddSet(discoCfg); err != nil { return nil, fmt.Errorf("adding cluster provider config items: %w", err) } - if err := common.AddCommonIdentityConfig(cs); err != nil { - return nil, fmt.Errorf("adding common identity config items: %w", err) - } if err := common.AddCommonClusterConfig(cs); err != nil { return nil, fmt.Errorf("adding common cluster config items: %w", err) } diff --git a/pkg/app/use.go b/pkg/app/use.go index a5ea1690..a1720952 100644 --- a/pkg/app/use.go +++ b/pkg/app/use.go @@ -70,10 +70,23 @@ func (a *App) Use(ctx context.Context, input *UseInput) error { return fmt.Errorf("using identity provider %s: %w", input.IdentityProvider, ErrUnsuportedIdpProtocol) } + err = identityProvider.CheckPreReqs() + if err != nil { + fmt.Fprintf(os.Stderr, "\033[33m%s\033[0m\n", err.Error()) + return fmt.Errorf("checking identity provider pre-reqs: %w", err) + } + err = clusterProvider.CheckPreReqs() if err != nil { - //TODO: how to report this??? fmt.Fprintf(os.Stderr, "\033[33m%s\033[0m\n", err.Error()) + return fmt.Errorf("checking discovery provider pre-reqs: %w", err) + } + + if err := identityProvider.Resolve(input.ConfigSet, nil); err != nil { + return fmt.Errorf("resolving identity config items: %w", err) + } + if err := identityProvider.Validate(input.ConfigSet); err != nil { + return fmt.Errorf("validating identity config items: %w", err) } authOutput, err := identityProvider.Authenticate(ctx, &identity.AuthenticateInput{ @@ -84,7 +97,10 @@ func (a *App) Use(ctx context.Context, input *UseInput) error { } if err := clusterProvider.Resolve(input.ConfigSet, authOutput.Identity); err != nil { - return fmt.Errorf("resolving config items: %w", err) + return fmt.Errorf("resolving discovery config items: %w", err) + } + if err := clusterProvider.Validate(input.ConfigSet); err != nil { + return fmt.Errorf("validating discovery config items: %w", err) } if !input.IgnoreAlias { @@ -153,7 +169,7 @@ func (a *App) Use(ctx context.Context, input *UseInput) error { return nil } -func (a *App) discoverCluster(ctx context.Context, clusterProvider discovery.Provider, identity identity.Identity, params *UseInput) (*discovery.Cluster, error) { +func (a *App) discoverCluster(ctx context.Context, clusterProvider discovery.Provider, identity provider.Identity, params *UseInput) (*discovery.Cluster, error) { a.logger.Infow("discovering clusters", "provider", params.DiscoveryProvider) discoverOutput, err := clusterProvider.Discover(ctx, &discovery.DiscoverInput{ @@ -177,7 +193,7 @@ func (a *App) discoverCluster(ctx context.Context, clusterProvider discovery.Pro return cluster, nil } -func (a *App) getCluster(ctx context.Context, clusterProvider discovery.Provider, identity identity.Identity, params *UseInput) (*discovery.Cluster, error) { +func (a *App) getCluster(ctx context.Context, clusterProvider discovery.Provider, identity provider.Identity, params *UseInput) (*discovery.Cluster, error) { a.logger.Infow("getting cluster details", "id", *params.ClusterID, "provider", params.DiscoveryProvider) output, err := clusterProvider.GetCluster(ctx, &discovery.GetClusterInput{ diff --git a/pkg/aws/store.go b/pkg/aws/store.go index 84e63a95..60ebe937 100644 --- a/pkg/aws/store.go +++ b/pkg/aws/store.go @@ -21,11 +21,11 @@ import ( "github.com/versent/saml2aws/pkg/awsconfig" - "github.com/fidelity/kconnect/pkg/provider/identity" + "github.com/fidelity/kconnect/pkg/provider" ) // NewIdentityStore will create a new AWS identity store -func NewIdentityStore(profile, idProviderName string) (identity.Store, error) { +func NewIdentityStore(profile, idProviderName string) (provider.Store, error) { return &awsIdentityStore{ configProvider: awsconfig.NewSharedCredentials(profile), idProviderName: idProviderName, @@ -41,7 +41,7 @@ func (s *awsIdentityStore) CredsExists() (bool, error) { return s.configProvider.CredsExists() } -func (s *awsIdentityStore) Save(userID identity.Identity) error { +func (s *awsIdentityStore) Save(userID provider.Identity) error { awsIdentity, ok := userID.(*Identity) if !ok { return fmt.Errorf("expected AWSIdentity but got a %T: %w", userID, ErrUnexpectedIdentity) @@ -51,7 +51,7 @@ func (s *awsIdentityStore) Save(userID identity.Identity) error { return s.configProvider.Save(awsCreds) } -func (s *awsIdentityStore) Load() (identity.Identity, error) { +func (s *awsIdentityStore) Load() (provider.Identity, error) { creds, err := s.configProvider.Load() if err != nil { return nil, fmt.Errorf("loading credentials: %w", err) diff --git a/pkg/config/validate.go b/pkg/config/validate.go new file mode 100644 index 00000000..fc564382 --- /dev/null +++ b/pkg/config/validate.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 The kconnect Authors. + +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 config + +import ( + "fmt" + + kerrs "github.com/fidelity/kconnect/pkg/errors" +) + +const ( + requiredFormat = "%s is required" +) + +// ValidateRequired will perform a required field validation on the config set +func ValidateRequired(cfg ConfigurationSet) error { + validationErrs := []string{} + for _, item := range cfg.GetAll() { + if item.Required && !item.HasValue() { + validationErrs = append(validationErrs, fmt.Sprintf(requiredFormat, item.Name)) + } + } + if len(validationErrs) > 0 { + return kerrs.New(validationErrs) + } + + return nil +} diff --git a/pkg/errors/validation.go b/pkg/errors/validation.go index 44c2ebd9..02aef6c5 100644 --- a/pkg/errors/validation.go +++ b/pkg/errors/validation.go @@ -21,6 +21,12 @@ import ( "strings" ) +func New(errors []string) *ValidationFailed { + return &ValidationFailed{ + validationErrors: errors, + } +} + type ValidationFailed struct { validationErrors []string } diff --git a/pkg/plugins/discovery/aws/provider.go b/pkg/plugins/discovery/aws/provider.go index f35ba2a0..0bf23d18 100644 --- a/pkg/plugins/discovery/aws/provider.go +++ b/pkg/plugins/discovery/aws/provider.go @@ -27,7 +27,6 @@ import ( "github.com/fidelity/kconnect/pkg/provider" "github.com/fidelity/kconnect/pkg/provider/common" "github.com/fidelity/kconnect/pkg/provider/discovery" - "github.com/fidelity/kconnect/pkg/provider/identity" "github.com/fidelity/kconnect/pkg/provider/registry" "github.com/fidelity/kconnect/pkg/utils" ) @@ -91,7 +90,7 @@ func (p *eksClusterProvider) Name() string { return ProviderName } -func (p *eksClusterProvider) setup(cs config.ConfigurationSet, userID identity.Identity) error { +func (p *eksClusterProvider) setup(cs config.ConfigurationSet, userID provider.Identity) error { cfg := &eksClusteProviderConfig{} if err := config.Unmarshall(cs, cfg); err != nil { return fmt.Errorf("unmarshalling config items into eksClusteProviderConfig: %w", err) diff --git a/pkg/plugins/discovery/aws/resolver.go b/pkg/plugins/discovery/aws/resolver.go index 7f085169..0e191c99 100644 --- a/pkg/plugins/discovery/aws/resolver.go +++ b/pkg/plugins/discovery/aws/resolver.go @@ -19,9 +19,10 @@ package aws import ( "fmt" + kaws "github.com/fidelity/kconnect/pkg/aws" "github.com/fidelity/kconnect/pkg/config" kerrors "github.com/fidelity/kconnect/pkg/errors" - "github.com/fidelity/kconnect/pkg/provider/identity" + "github.com/fidelity/kconnect/pkg/provider" ) func (p *eksClusterProvider) Validate(cfg config.ConfigurationSet) error { @@ -42,6 +43,12 @@ func (p *eksClusterProvider) Validate(cfg config.ConfigurationSet) error { // Resolve will resolve the values for the AWS specific flags that have no value. It will // query AWS and interactively ask the user for selections. -func (p *eksClusterProvider) Resolve(config config.ConfigurationSet, userID identity.Identity) error { +func (p *eksClusterProvider) Resolve(cfg config.ConfigurationSet, userID provider.Identity) error { + if err := kaws.ResolvePartition(cfg); err != nil { + return fmt.Errorf("resolving partition: %w", err) + } + if err := kaws.ResolveRegion(cfg); err != nil { + return fmt.Errorf("resolving region: %w", err) + } return nil } diff --git a/pkg/plugins/discovery/azure/config.go b/pkg/plugins/discovery/azure/config.go index b5059314..49661131 100644 --- a/pkg/plugins/discovery/azure/config.go +++ b/pkg/plugins/discovery/azure/config.go @@ -29,8 +29,8 @@ import ( azclient "github.com/fidelity/kconnect/pkg/azure/client" "github.com/fidelity/kconnect/pkg/azure/id" azid "github.com/fidelity/kconnect/pkg/azure/identity" + "github.com/fidelity/kconnect/pkg/provider" "github.com/fidelity/kconnect/pkg/provider/discovery" - "github.com/fidelity/kconnect/pkg/provider/identity" ) const ( @@ -110,7 +110,7 @@ func (p *aksClusterProvider) addKubelogin(cfg *api.Config) { } } -func (p *aksClusterProvider) addTokenToAuthProvider(cfg *api.Config, userID identity.Identity) error { +func (p *aksClusterProvider) addTokenToAuthProvider(cfg *api.Config, userID provider.Identity) error { id, ok := userID.(*azid.ActiveDirectoryIdentity) if !ok { return ErrTokenNeedsAD diff --git a/pkg/plugins/discovery/azure/provider.go b/pkg/plugins/discovery/azure/provider.go index 74e27f6b..d6d4483e 100644 --- a/pkg/plugins/discovery/azure/provider.go +++ b/pkg/plugins/discovery/azure/provider.go @@ -22,7 +22,6 @@ import ( "go.uber.org/zap" "github.com/Azure/go-autorest/autorest" - "github.com/go-playground/validator/v10" azid "github.com/fidelity/kconnect/pkg/azure/identity" "github.com/fidelity/kconnect/pkg/config" @@ -30,7 +29,6 @@ import ( "github.com/fidelity/kconnect/pkg/provider" "github.com/fidelity/kconnect/pkg/provider/common" "github.com/fidelity/kconnect/pkg/provider/discovery" - "github.com/fidelity/kconnect/pkg/provider/identity" "github.com/fidelity/kconnect/pkg/provider/registry" "github.com/fidelity/kconnect/pkg/utils" ) @@ -101,16 +99,11 @@ func (p *aksClusterProvider) Name() string { return ProviderName } -func (p *aksClusterProvider) setup(cs config.ConfigurationSet, userID identity.Identity) error { +func (p *aksClusterProvider) setup(cs config.ConfigurationSet, userID provider.Identity) error { cfg := &aksClusterProviderConfig{} if err := config.Unmarshall(cs, cfg); err != nil { return fmt.Errorf("unmarshalling config items into eksClusteProviderConfig: %w", err) } - validate := validator.New() - if err := validate.Struct(cfg); err != nil { - return fmt.Errorf("validating config struct: %w", err) - } - p.config = cfg // TODO: should we just return a AuthorizerIdentity from the aad provider? diff --git a/pkg/plugins/discovery/azure/resolver.go b/pkg/plugins/discovery/azure/resolver.go index 9c54d0b0..f3c37b88 100644 --- a/pkg/plugins/discovery/azure/resolver.go +++ b/pkg/plugins/discovery/azure/resolver.go @@ -24,7 +24,7 @@ import ( "github.com/fidelity/kconnect/pkg/config" kerrors "github.com/fidelity/kconnect/pkg/errors" "github.com/fidelity/kconnect/pkg/prompt" - "github.com/fidelity/kconnect/pkg/provider/identity" + "github.com/fidelity/kconnect/pkg/provider" ) func (p *aksClusterProvider) Validate(cfg config.ConfigurationSet) error { @@ -45,7 +45,7 @@ func (p *aksClusterProvider) Validate(cfg config.ConfigurationSet) error { // Resolve will resolve the values for the AWS specific flags that have no value. It will // query AWS and interactively ask the user for selections. -func (p *aksClusterProvider) Resolve(cfg config.ConfigurationSet, userID identity.Identity) error { +func (p *aksClusterProvider) Resolve(cfg config.ConfigurationSet, userID provider.Identity) error { if err := p.setup(cfg, userID); err != nil { return fmt.Errorf("setting up aks provider: %w", err) } diff --git a/pkg/plugins/discovery/rancher/provider.go b/pkg/plugins/discovery/rancher/provider.go index 818c93d4..69d39ff3 100644 --- a/pkg/plugins/discovery/rancher/provider.go +++ b/pkg/plugins/discovery/rancher/provider.go @@ -86,7 +86,7 @@ func (p *rancherClusterProvider) Name() string { return ProviderName } -func (p *rancherClusterProvider) setup(cs config.ConfigurationSet, userID identity.Identity) error { +func (p *rancherClusterProvider) setup(cs config.ConfigurationSet, userID provider.Identity) error { cfg := &rancherClusterProviderConfig{} if err := config.Unmarshall(cs, cfg); err != nil { return fmt.Errorf("unmarshalling config items into rancherClusterProviderConfig: %w", err) diff --git a/pkg/plugins/discovery/rancher/resolver.go b/pkg/plugins/discovery/rancher/resolver.go index 9341ff9a..734dc856 100644 --- a/pkg/plugins/discovery/rancher/resolver.go +++ b/pkg/plugins/discovery/rancher/resolver.go @@ -21,7 +21,7 @@ import ( "github.com/fidelity/kconnect/pkg/config" kerrors "github.com/fidelity/kconnect/pkg/errors" - "github.com/fidelity/kconnect/pkg/provider/identity" + "github.com/fidelity/kconnect/pkg/provider" rshared "github.com/fidelity/kconnect/pkg/rancher" ) @@ -43,7 +43,7 @@ func (p *rancherClusterProvider) Validate(cfg config.ConfigurationSet) error { // Resolve will resolve the values for the AWS specific flags that have no value. It will // query AWS and interactively ask the user for selections. -func (p *rancherClusterProvider) Resolve(cfg config.ConfigurationSet, identity identity.Identity) error { +func (p *rancherClusterProvider) Resolve(cfg config.ConfigurationSet, identity provider.Identity) error { if err := p.setup(cfg, identity); err != nil { return fmt.Errorf("setting up rancher provider: %w", err) } diff --git a/pkg/plugins/identity/aws/iam/provider.go b/pkg/plugins/identity/aws/iam/provider.go index a1da3412..dd4872eb 100644 --- a/pkg/plugins/identity/aws/iam/provider.go +++ b/pkg/plugins/identity/aws/iam/provider.go @@ -85,10 +85,6 @@ func (p *iamIdentityProvider) Authenticate(ctx context.Context, input *identity. return nil, fmt.Errorf("unmarshalling config into providerConfig: %w", err) } - if err := p.validateConfig(cfg); err != nil { - return nil, err - } - sess, err := kaws.NewSession(cfg.Region, cfg.Profile, cfg.AccessKey, cfg.SecretKey, cfg.SessionToken) if err != nil { return nil, fmt.Errorf("creating aws session: %w", err) @@ -113,23 +109,42 @@ func (p *iamIdentityProvider) Authenticate(ctx context.Context, input *identity. }, nil } -func (p *iamIdentityProvider) validateConfig(cfg *providerConfig) error { - if cfg.Profile != "" && cfg.AccessKey != "" { +// Validate is used to validate the config items and return any errors +func (p *iamIdentityProvider) Validate(cfg config.ConfigurationSet) error { + hasProfile := cfg.ExistsWithValue(kaws.ProfileConfigItem) + hasAccessKey := cfg.ExistsWithValue(kaws.AccessKeyConfigItem) + hasSecretKey := cfg.ExistsWithValue(kaws.SecretKeyConfigItem) + + if hasProfile && hasAccessKey { return ErrProfileWithAccessKey } - if cfg.Profile != "" && cfg.SecretKey != "" { + if hasProfile && hasSecretKey { return ErrProfileWithSecretKey } - if cfg.AccessKey != "" && cfg.SecretKey == "" { + if hasAccessKey && !hasSecretKey { return ErrAccessAndSecretRequired } - if cfg.AccessKey == "" && cfg.SecretKey != "" { + if !hasAccessKey && hasSecretKey { return ErrAccessAndSecretRequired } return nil } +// Resolve will resolve the values for the supplied config items. It will interactively +// resolve the values by asking the user for selections. +func (p *iamIdentityProvider) Resolve(config config.ConfigurationSet, identity provider.Identity) error { + return nil +} + +func (p *iamIdentityProvider) ListPreReqs() []*provider.PreReq { + return []*provider.PreReq{} +} + +func (p *iamIdentityProvider) CheckPreReqs() error { + return nil +} + // ConfigurationItems will return the configuration items for the intentity plugin based // of the cluster provider that its being used in conjunction with func ConfigurationItems(scopeTo string) (config.ConfigurationSet, error) { diff --git a/pkg/plugins/identity/azure/aad/aad.go b/pkg/plugins/identity/azure/aad/aad.go index 28922f29..5e7ec4bf 100644 --- a/pkg/plugins/identity/azure/aad/aad.go +++ b/pkg/plugins/identity/azure/aad/aad.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" - "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/fidelity/kconnect/pkg/azure/identity" @@ -78,9 +77,9 @@ type aadIdentityProvider struct { type aadConfig struct { common.IdentityProviderConfig - TenantID string `json:"tenant-id" validate:"required"` - ClientID string `json:"client-id" validate:"required"` - AADHost identity.AADHost `json:"aad-host" validate:"required"` + TenantID string `json:"tenant-id"` + ClientID string `json:"client-id"` + AADHost identity.AADHost `json:"aad-host"` } func (p *aadIdentityProvider) Name() string { @@ -91,19 +90,11 @@ func (p *aadIdentityProvider) Name() string { func (p *aadIdentityProvider) Authenticate(ctx context.Context, input *provid.AuthenticateInput) (*provid.AuthenticateOutput, error) { p.logger.Info("authenticating user") - if err := p.resolveConfig(input.ConfigSet); err != nil { - return nil, fmt.Errorf("resolving config: %w", err) - } - cfg := &aadConfig{} if err := config.Unmarshall(input.ConfigSet, cfg); err != nil { return nil, fmt.Errorf("unmarshalling config into use aadconfig: %w", err) } - if err := p.validateConfig(cfg); err != nil { - return nil, err - } - authCfg := &identity.AuthenticationConfig{ Authority: &identity.AuthorityConfig{ Tenant: cfg.TenantID, @@ -135,11 +126,11 @@ func (p *aadIdentityProvider) Authenticate(ctx context.Context, input *provid.Au }, nil } -func (p *aadIdentityProvider) validateConfig(cfg *aadConfig) error { - validate := validator.New() - if err := validate.Struct(cfg); err != nil { - return fmt.Errorf("validating aad config: %w", err) - } +func (p *aadIdentityProvider) ListPreReqs() []*provider.PreReq { + return []*provider.PreReq{} +} + +func (p *aadIdentityProvider) CheckPreReqs() error { return nil } @@ -158,6 +149,8 @@ func ConfigurationItems(scopeTo string) (config.ConfigurationSet, error) { cs.SetShort(azure.TenantIDConfigItem, "t") //nolint: errcheck cs.SetRequired(azure.TenantIDConfigItem) //nolint: errcheck + cs.SetRequired(azure.ClientIDConfigItem) //nolint: errcheck + cs.SetRequired(azure.AADHostConfigItem) //nolint: errcheck return cs, nil } diff --git a/pkg/plugins/identity/azure/aad/resolver.go b/pkg/plugins/identity/azure/aad/resolver.go index 23314a61..4d745c79 100644 --- a/pkg/plugins/identity/azure/aad/resolver.go +++ b/pkg/plugins/identity/azure/aad/resolver.go @@ -24,9 +24,10 @@ import ( "github.com/fidelity/kconnect/pkg/defaults" "github.com/fidelity/kconnect/pkg/plugins/discovery/azure" "github.com/fidelity/kconnect/pkg/prompt" + "github.com/fidelity/kconnect/pkg/provider" ) -func (p *aadIdentityProvider) resolveConfig(cfg config.ConfigurationSet) error { +func (p *aadIdentityProvider) Resolve(cfg config.ConfigurationSet, _ provider.Identity) error { if !p.interactive { p.logger.Debug("skipping configuration resolution as runnning non-interactive") return nil @@ -51,6 +52,10 @@ func (p *aadIdentityProvider) resolveConfig(cfg config.ConfigurationSet) error { return nil } +func (p *aadIdentityProvider) Validate(cfg config.ConfigurationSet) error { + return config.ValidateRequired(cfg) +} + func aadHostOptions() (map[string]string, error) { return map[string]string{ "Worldwide (recommended)": string(identity.AADHostWorldwide), diff --git a/pkg/plugins/identity/azure/env/provider.go b/pkg/plugins/identity/azure/env/provider.go index 5f5406f9..0bbbe2ef 100644 --- a/pkg/plugins/identity/azure/env/provider.go +++ b/pkg/plugins/identity/azure/env/provider.go @@ -98,6 +98,22 @@ func (p *envIdentityProvider) Authenticate(ctx context.Context, input *provid.Au }, nil } +func (p *envIdentityProvider) Validate(cfg config.ConfigurationSet) error { + return nil +} + +func (p *envIdentityProvider) Resolve(config config.ConfigurationSet, identity provider.Identity) error { + return nil +} + +func (p *envIdentityProvider) ListPreReqs() []*provider.PreReq { + return []*provider.PreReq{} +} + +func (p *envIdentityProvider) CheckPreReqs() error { + return nil +} + // ConfigurationItems will return the configuration items for the intentity plugin based // of the cluster provider that its being used in conjunction with func ConfigurationItems(scopeTo string) (config.ConfigurationSet, error) { diff --git a/pkg/plugins/identity/rancher/activedirectory/activedirectory.go b/pkg/plugins/identity/rancher/activedirectory/activedirectory.go index 248c2309..f5e6e042 100644 --- a/pkg/plugins/identity/rancher/activedirectory/activedirectory.go +++ b/pkg/plugins/identity/rancher/activedirectory/activedirectory.go @@ -23,7 +23,6 @@ import ( "fmt" "net/http" - "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/fidelity/kconnect/pkg/config" @@ -92,19 +91,11 @@ func (p *radIdentityProvider) Name() string { func (p *radIdentityProvider) Authenticate(ctx context.Context, input *identity.AuthenticateInput) (*identity.AuthenticateOutput, error) { p.logger.Info("authenticating user") - if err := p.resolveConfig(input.ConfigSet); err != nil { - return nil, fmt.Errorf("resolving config: %w", err) - } - cfg := &radConfig{} if err := config.Unmarshall(input.ConfigSet, cfg); err != nil { return nil, fmt.Errorf("unmarshalling config into use radConfig: %w", err) } - if err := p.validateConfig(cfg); err != nil { - return nil, err - } - resolver, err := rancher.NewStaticEndpointsResolver(cfg.APIEndpoint) if err != nil { return nil, fmt.Errorf("vreating endpoint resolver: %w", err) @@ -145,11 +136,11 @@ func (p *radIdentityProvider) Authenticate(ctx context.Context, input *identity. }, nil } -func (p *radIdentityProvider) validateConfig(cfg *radConfig) error { - validate := validator.New() - if err := validate.Struct(cfg); err != nil { - return fmt.Errorf("validating aad config: %w", err) - } +func (p *radIdentityProvider) ListPreReqs() []*provider.PreReq { + return []*provider.PreReq{} +} + +func (p *radIdentityProvider) CheckPreReqs() error { return nil } diff --git a/pkg/plugins/identity/rancher/activedirectory/resolver.go b/pkg/plugins/identity/rancher/activedirectory/resolver.go index 9de70c6c..01d6ec21 100644 --- a/pkg/plugins/identity/rancher/activedirectory/resolver.go +++ b/pkg/plugins/identity/rancher/activedirectory/resolver.go @@ -22,10 +22,11 @@ import ( "github.com/fidelity/kconnect/pkg/config" "github.com/fidelity/kconnect/pkg/defaults" "github.com/fidelity/kconnect/pkg/prompt" + "github.com/fidelity/kconnect/pkg/provider" rshared "github.com/fidelity/kconnect/pkg/rancher" ) -func (p *radIdentityProvider) resolveConfig(cfg config.ConfigurationSet) error { +func (p *radIdentityProvider) Resolve(cfg config.ConfigurationSet, identity provider.Identity) error { if !p.interactive { p.logger.Debug("skipping configuration resolution as runnning non-interactive") return nil @@ -43,3 +44,7 @@ func (p *radIdentityProvider) resolveConfig(cfg config.ConfigurationSet) error { return nil } + +func (p *radIdentityProvider) Validate(cfg config.ConfigurationSet) error { + return config.ValidateRequired(cfg) +} diff --git a/pkg/plugins/identity/saml/resolve.go b/pkg/plugins/identity/saml/resolve.go new file mode 100644 index 00000000..b7c68922 --- /dev/null +++ b/pkg/plugins/identity/saml/resolve.go @@ -0,0 +1,45 @@ +/* +Copyright 2021 The kconnect Authors. + +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 saml + +import ( + "fmt" + + "github.com/fidelity/kconnect/pkg/config" + "github.com/fidelity/kconnect/pkg/provider" +) + +func (p *samlIdentityProvider) Resolve(cfg config.ConfigurationSet, identity provider.Identity) error { + sp := p.serviceProvider + + if p.interactive { + p.logger.Debug("resolving SAML provider flags") + if err := sp.ResolveConfiguration(cfg); err != nil { + return fmt.Errorf("resolving flags: %w", err) + } + } else { + p.logger.Debug("skipping configuration resolution as runnning non-interactive") + } + + return nil +} + +func (p *samlIdentityProvider) Validate(cfg config.ConfigurationSet) error { + //NOTE: we haven't included the service provider validation here + // as its not initialized yet. + return config.ValidateRequired(cfg) +} diff --git a/pkg/plugins/identity/saml/saml.go b/pkg/plugins/identity/saml/saml.go index d8054bd4..73979166 100644 --- a/pkg/plugins/identity/saml/saml.go +++ b/pkg/plugins/identity/saml/saml.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" - "github.com/go-playground/validator/v10" "github.com/versent/saml2aws" "github.com/versent/saml2aws/pkg/cfg" "github.com/versent/saml2aws/pkg/creds" @@ -32,6 +31,7 @@ import ( "github.com/fidelity/kconnect/pkg/plugins/identity/saml/sp" "github.com/fidelity/kconnect/pkg/plugins/identity/saml/sp/aws" "github.com/fidelity/kconnect/pkg/provider" + "github.com/fidelity/kconnect/pkg/provider/common" "github.com/fidelity/kconnect/pkg/provider/identity" "github.com/fidelity/kconnect/pkg/provider/registry" ) @@ -98,19 +98,18 @@ func (p *samlIdentityProvider) Name() string { func (p *samlIdentityProvider) Authenticate(ctx context.Context, input *identity.AuthenticateInput) (*identity.AuthenticateOutput, error) { p.logger.Info("authenticating user") - sp, err := createServiceProvider(p.scopedToDiscovery, p.itemSelector) + serviceProvider, err := createServiceProvider(p.scopedToDiscovery, p.itemSelector) if err != nil { return nil, fmt.Errorf("creating saml service provider: %w", err) } - p.serviceProvider = sp + p.serviceProvider = serviceProvider - if err := p.resolveConfig(input.ConfigSet); err != nil { - return nil, err + spConfig := &sp.ProviderConfig{} + if err := config.Unmarshall(input.ConfigSet, spConfig); err != nil { + return nil, fmt.Errorf("unmarshalling configuration: %w", err) } + p.config = spConfig - if err := p.bindAndValidateConfig(input.ConfigSet); err != nil { - return nil, fmt.Errorf("binding and validation config: %w", err) - } if err := p.serviceProvider.Validate(input.ConfigSet); err != nil { return nil, fmt.Errorf("validating service provider: %w", err) } @@ -165,23 +164,6 @@ func (p *samlIdentityProvider) Authenticate(ctx context.Context, input *identity }, nil } -func (p *samlIdentityProvider) bindAndValidateConfig(cs config.ConfigurationSet) error { - spConfig := &sp.ProviderConfig{} - - if err := config.Unmarshall(cs, spConfig); err != nil { - return fmt.Errorf("unmarshalling configuration: %w", err) - } - - validate := validator.New() - if err := validate.Struct(spConfig); err != nil { - return fmt.Errorf("validating config struct: %w", err) - } - - p.config = spConfig - - return nil -} - func (p *samlIdentityProvider) createAccount(cs config.ConfigurationSet) (*cfg.IDPAccount, error) { account := &cfg.IDPAccount{ URL: p.config.IdpEndpoint, @@ -196,23 +178,8 @@ func (p *samlIdentityProvider) createAccount(cs config.ConfigurationSet) (*cfg.I return account, nil } -func (p *samlIdentityProvider) resolveConfig(cfg config.ConfigurationSet) error { - sp := p.serviceProvider - - if p.interactive { - p.logger.Debug("resolving SAML provider flags") - if err := sp.ResolveConfiguration(cfg); err != nil { - return fmt.Errorf("resolving flags: %w", err) - } - } else { - p.logger.Debug("skipping configuration resolution as runnning non-interactive") - } - - return nil -} - -func (p *samlIdentityProvider) createIdentityStore(cfg config.ConfigurationSet) (identity.Store, error) { - var store identity.Store +func (p *samlIdentityProvider) createIdentityStore(cfg config.ConfigurationSet) (provider.Store, error) { + var store provider.Store var err error switch p.scopedToDiscovery { @@ -233,6 +200,14 @@ func (p *samlIdentityProvider) createIdentityStore(cfg config.ConfigurationSet) return store, nil } +func (p *samlIdentityProvider) ListPreReqs() []*provider.PreReq { + return []*provider.PreReq{} +} + +func (p *samlIdentityProvider) CheckPreReqs() error { + return nil +} + func createServiceProvider(clusterProviderName string, itemSelector provider.SelectItemFunc) (sp.ServiceProvider, error) { switch clusterProviderName { case "eks": @@ -245,6 +220,10 @@ func createServiceProvider(clusterProviderName string, itemSelector provider.Sel func ConfigurationItems(scopedToDiscovery string) (config.ConfigurationSet, error) { cs := config.NewConfigurationSet() + if err := common.AddCommonIdentityConfig(cs); err != nil { + return nil, fmt.Errorf("adding common identity config items: %w", err) + } + cs.String("idp-endpoint", "", "identity provider endpoint provided by your IT team") //nolint: errcheck cs.String("idp-provider", "", "the name of the idp provider") //nolint: errcheck cs.SetRequired("idp-endpoint") //nolint: errcheck diff --git a/pkg/plugins/identity/saml/sp/aws/provider.go b/pkg/plugins/identity/saml/sp/aws/provider.go index e0ee299c..61ea1ce1 100644 --- a/pkg/plugins/identity/saml/sp/aws/provider.go +++ b/pkg/plugins/identity/saml/sp/aws/provider.go @@ -23,7 +23,6 @@ import ( "strings" "github.com/beevik/etree" - "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/aws/aws-sdk-go/aws" @@ -38,7 +37,6 @@ import ( "github.com/fidelity/kconnect/pkg/config" "github.com/fidelity/kconnect/pkg/plugins/identity/saml/sp" "github.com/fidelity/kconnect/pkg/provider" - "github.com/fidelity/kconnect/pkg/provider/identity" ) const ( @@ -52,13 +50,6 @@ var ( ErrMissingResponseElement = errors.New("missing response element") ) -type awsProviderConfig struct { - sp.ProviderConfig - - Partition string `json:"partition" validate:"required"` - Region string `json:"region" validate:"required"` -} - func NewServiceProvider(itemSelector provider.SelectItemFunc) sp.ServiceProvider { return &ServiceProvider{ logger: zap.S().With("provider", "saml", "sp", "aws"), @@ -89,7 +80,7 @@ func (p *ServiceProvider) PopulateAccount(account *cfg.IDPAccount, cfg config.Co return nil } -func (p *ServiceProvider) ProcessAssertions(account *cfg.IDPAccount, samlAssertions string, cfg config.ConfigurationSet) (identity.Identity, error) { +func (p *ServiceProvider) ProcessAssertions(account *cfg.IDPAccount, samlAssertions string, cfg config.ConfigurationSet) (provider.Identity, error) { data, err := base64.StdEncoding.DecodeString(samlAssertions) if err != nil { return nil, fmt.Errorf("decoding SAMLAssertion: %w", err) @@ -324,18 +315,7 @@ func (p *ServiceProvider) extractDestinationURL(data []byte) (string, error) { } func (p *ServiceProvider) Validate(configItems config.ConfigurationSet) error { - cfg := &awsProviderConfig{} - - if err := config.Unmarshall(configItems, cfg); err != nil { - return fmt.Errorf("unmarshlling config set: %w", err) - } - - validate := validator.New() - if err := validate.Struct(cfg); err != nil { - return fmt.Errorf("validating config struct: %w", err) - } - - return nil + return config.ValidateRequired(configItems) } func (p *ServiceProvider) ConfigurationItems() config.ConfigurationSet { diff --git a/pkg/plugins/identity/saml/sp/types.go b/pkg/plugins/identity/saml/sp/types.go index bff1bdb6..b3074128 100644 --- a/pkg/plugins/identity/saml/sp/types.go +++ b/pkg/plugins/identity/saml/sp/types.go @@ -20,14 +20,14 @@ import ( "github.com/versent/saml2aws/pkg/cfg" "github.com/fidelity/kconnect/pkg/config" + "github.com/fidelity/kconnect/pkg/provider" "github.com/fidelity/kconnect/pkg/provider/common" - "github.com/fidelity/kconnect/pkg/provider/identity" ) type ProviderConfig struct { common.IdentityProviderConfig - IdpEndpoint string `json:"idp-endpoint" validate:"required"` - IdpProvider string `json:"idp-provider" validate:"required"` + IdpEndpoint string `json:"idp-endpoint"` + IdpProvider string `json:"idp-provider"` } type ServiceProvider interface { @@ -36,5 +36,5 @@ type ServiceProvider interface { Validate(configItems config.ConfigurationSet) error ResolveConfiguration(configItems config.ConfigurationSet) error PopulateAccount(account *cfg.IDPAccount, configItems config.ConfigurationSet) error - ProcessAssertions(account *cfg.IDPAccount, samlAssertions string, configItems config.ConfigurationSet) (identity.Identity, error) + ProcessAssertions(account *cfg.IDPAccount, samlAssertions string, configItems config.ConfigurationSet) (provider.Identity, error) } diff --git a/pkg/plugins/identity/static/token/resolver.go b/pkg/plugins/identity/static/token/resolver.go new file mode 100644 index 00000000..d5de3e41 --- /dev/null +++ b/pkg/plugins/identity/static/token/resolver.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The kconnect Authors. + +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 token + +import ( + "fmt" + + "github.com/fidelity/kconnect/pkg/config" + "github.com/fidelity/kconnect/pkg/prompt" + "github.com/fidelity/kconnect/pkg/provider" +) + +func (p *staticTokenIdentityProvider) Resolve(cfg config.ConfigurationSet, identity provider.Identity) error { + if !p.interactive { + p.logger.Debug("skipping configuration resolution as runnning non-interactive") + } + + if err := prompt.InputAndSet(cfg, tokenConfigItem, "Enter authentication token", true); err != nil { + return fmt.Errorf("resolving %s: %w", tokenConfigItem, err) + } + + return nil +} + +func (p *staticTokenIdentityProvider) Validate(cfg config.ConfigurationSet) error { + return config.ValidateRequired(cfg) +} diff --git a/pkg/plugins/identity/static/token/token.go b/pkg/plugins/identity/static/token/token.go index b5e3d493..624aa95f 100644 --- a/pkg/plugins/identity/static/token/token.go +++ b/pkg/plugins/identity/static/token/token.go @@ -20,11 +20,9 @@ import ( "context" "fmt" - "github.com/go-playground/validator/v10" "go.uber.org/zap" "github.com/fidelity/kconnect/pkg/config" - "github.com/fidelity/kconnect/pkg/prompt" "github.com/fidelity/kconnect/pkg/provider" "github.com/fidelity/kconnect/pkg/provider/identity" "github.com/fidelity/kconnect/pkg/provider/registry" @@ -62,7 +60,7 @@ type staticTokenIdentityProvider struct { } type providerConfig struct { - Token string `json:"token" validate:"required"` + Token string `json:"token"` } func (p *staticTokenIdentityProvider) Name() string { @@ -73,42 +71,22 @@ func (p *staticTokenIdentityProvider) Name() string { func (p *staticTokenIdentityProvider) Authenticate(ctx context.Context, input *identity.AuthenticateInput) (*identity.AuthenticateOutput, error) { p.logger.Info("using static token for authentication") - if err := p.resolveConfig(input.ConfigSet); err != nil { - return nil, fmt.Errorf("resolving config: %w", err) - } - cfg := &providerConfig{} if err := config.Unmarshall(input.ConfigSet, cfg); err != nil { return nil, fmt.Errorf("unmarshalling config into providerConfig: %w", err) } - if err := p.validateConfig(cfg); err != nil { - return nil, err - } - return &identity.AuthenticateOutput{ Identity: identity.NewTokenIdentity("static-token", cfg.Token, ProviderName), }, nil } -func (p *staticTokenIdentityProvider) validateConfig(cfg *providerConfig) error { - validate := validator.New() - if err := validate.Struct(cfg); err != nil { - return fmt.Errorf("validating aad config: %w", err) - } - return nil +func (p *staticTokenIdentityProvider) ListPreReqs() []*provider.PreReq { + return []*provider.PreReq{} } -func (p *staticTokenIdentityProvider) resolveConfig(cfg config.ConfigurationSet) error { - if !p.interactive { - p.logger.Debug("skipping configuration resolution as runnning non-interactive") - } - - if err := prompt.InputAndSet(cfg, tokenConfigItem, "Enter authentication token", true); err != nil { - return fmt.Errorf("resolving %s: %w", tokenConfigItem, err) - } - +func (p *staticTokenIdentityProvider) CheckPreReqs() error { return nil } diff --git a/pkg/provider/common/common.go b/pkg/provider/common/common.go index cf47ed95..500fc688 100644 --- a/pkg/provider/common/common.go +++ b/pkg/provider/common/common.go @@ -22,6 +22,14 @@ import ( "github.com/fidelity/kconnect/pkg/config" ) +const ( + ClusterIDConfigItem = "cluster-id" + AliasConfigItem = "alias" + UsernameConfigItem = "username" + PasswordConfigItem = "password" + IdpProtocolConfigItem = "idp-protocol" +) + // ClusterProviderConfig represents the base configuration for // a cluster provider type ClusterProviderConfig struct { @@ -36,30 +44,30 @@ type ClusterProviderConfig struct { // IdentityProviderConfig represents the base configuration for an // identity provider. type IdentityProviderConfig struct { - Username string `json:"username" validate:"required"` - Password string `json:"password" validate:"required"` - IdpProtocol string `json:"idp-protocol" validate:"required"` + Username string `json:"username"` + Password string `json:"password"` + IdpProtocol string `json:"idp-protocol"` } func AddCommonClusterConfig(cs config.ConfigurationSet) error { - if _, err := cs.String("cluster-id", "", "Id of the cluster to use."); err != nil { + if _, err := cs.String(ClusterIDConfigItem, "", "Id of the cluster to use."); err != nil { return fmt.Errorf("adding cluster-id setting: %w", err) } - if _, err := cs.String("alias", "", "Friendly name to give to give the connection"); err != nil { + if _, err := cs.String(AliasConfigItem, "", "Friendly name to give to give the connection"); err != nil { return fmt.Errorf("adding alias setting: %w", err) } - if err := cs.SetShort("cluster-id", "c"); err != nil { + if err := cs.SetShort(ClusterIDConfigItem, "c"); err != nil { return fmt.Errorf("setting shorthand for cluster-id setting: %w", err) } - if err := cs.SetShort("alias", "a"); err != nil { + if err := cs.SetShort(AliasConfigItem, "a"); err != nil { return fmt.Errorf("setting shorthand for alias setting: %w", err) } - if err := cs.SetSensitive("alias"); err != nil { + if err := cs.SetSensitive(AliasConfigItem); err != nil { return fmt.Errorf("setting alias as sensitive: %w", err) } - cs.SetHistoryIgnore("alias") //nolint + cs.SetHistoryIgnore(AliasConfigItem) //nolint return nil } @@ -78,10 +86,15 @@ func AddCommonIdentityConfig(cs config.ConfigurationSet) error { // IdentityConfig creates a configset with the common identity config items func IdentityConfig() config.ConfigurationSet { cs := config.NewConfigurationSet() - cs.String("username", "", "The username used for authentication") //nolint: errcheck - cs.String("password", "", "The password to use for authentication") //nolint: errcheck - cs.String("idp-protocol", "", "The idp protocol to use (e.g. saml). Each protocol has its own flags.") //nolint: errcheck - cs.SetSensitive("password") //nolint: errcheck + cs.String(UsernameConfigItem, "", "The username used for authentication") //nolint: errcheck + cs.String(PasswordConfigItem, "", "The password to use for authentication") //nolint: errcheck + cs.String(IdpProtocolConfigItem, "", "The idp protocol to use (e.g. saml). Each protocol has its own flags.") //nolint: errcheck + + cs.SetSensitive(PasswordConfigItem) //nolint: errcheck + + cs.SetRequired(UsernameConfigItem) //nolint: errcheck + cs.SetRequired(PasswordConfigItem) //nolint: errcheck + cs.SetRequired(IdpProtocolConfigItem) //nolint: errcheck return cs } diff --git a/pkg/provider/config/config.go b/pkg/provider/config/config.go index 8f4bcb32..d934c25f 100644 --- a/pkg/provider/config/config.go +++ b/pkg/provider/config/config.go @@ -2,7 +2,7 @@ package config import ( "github.com/fidelity/kconnect/pkg/config" - "github.com/fidelity/kconnect/pkg/provider/identity" + "github.com/fidelity/kconnect/pkg/provider" ) // Resolver is an interface that indicates that the plugin can resolve and validate configuration @@ -13,5 +13,5 @@ type Resolver interface { // Resolve will resolve the values for the supplied config items. It will interactively // resolve the values by asking the user for selections. - Resolve(config config.ConfigurationSet, identity identity.Identity) error + Resolve(config config.ConfigurationSet, identity provider.Identity) error } diff --git a/pkg/provider/discovery/discovery.go b/pkg/provider/discovery/discovery.go index e219803c..3bf58307 100644 --- a/pkg/provider/discovery/discovery.go +++ b/pkg/provider/discovery/discovery.go @@ -8,7 +8,6 @@ import ( "github.com/fidelity/kconnect/pkg/config" "github.com/fidelity/kconnect/pkg/provider" provcfg "github.com/fidelity/kconnect/pkg/provider/config" - "github.com/fidelity/kconnect/pkg/provider/identity" ) // Provider is the interface that is used to implement providers @@ -37,7 +36,7 @@ type ProviderCreatorFun func(input *provider.PluginCreationInput) (Provider, err // DiscoverInput is the input to Discover type DiscoverInput struct { ConfigSet config.ConfigurationSet - Identity identity.Identity + Identity provider.Identity } // DiscoverOutput holds details of the output of the Discover @@ -52,7 +51,7 @@ type DiscoverOutput struct { type GetClusterInput struct { ClusterID string ConfigSet config.ConfigurationSet - Identity identity.Identity + Identity provider.Identity } // GetClusterOutput is the output to GetCluster @@ -63,7 +62,7 @@ type GetClusterOutput struct { type GetConfigInput struct { Cluster *Cluster Namespace *string - Identity identity.Identity + Identity provider.Identity } type GetConfigOutput struct { diff --git a/pkg/provider/identity/identity.go b/pkg/provider/identity/identity.go index e5dc9031..33defd01 100644 --- a/pkg/provider/identity/identity.go +++ b/pkg/provider/identity/identity.go @@ -5,12 +5,15 @@ import ( "github.com/fidelity/kconnect/pkg/config" "github.com/fidelity/kconnect/pkg/provider" + provcfg "github.com/fidelity/kconnect/pkg/provider/config" ) // Provider represents the interface used to implement an identity provider // plugin. It provides authentication functionality. type Provider interface { provider.Plugin + provider.PluginPreReqs + provcfg.Resolver // Authenticate will authenticate a user and return details of their identity. Authenticate(ctx context.Context, input *AuthenticateInput) (*AuthenticateOutput, error) @@ -23,22 +26,5 @@ type AuthenticateInput struct { } type AuthenticateOutput struct { - Identity Identity -} - -// Identity represents a users identity for use with discovery. -// NOTE: details of this need finalising -type Identity interface { - Type() string - Name() string - IsExpired() bool - IdentityProviderName() string -} - -// Store represents an way to store and retrieve credentials -type Store interface { - CredsExists() (bool, error) - Save(identity Identity) error - Load() (Identity, error) - Expired() bool + Identity provider.Identity } diff --git a/pkg/provider/types.go b/pkg/provider/types.go index e1b47957..d796e6d2 100644 --- a/pkg/provider/types.go +++ b/pkg/provider/types.go @@ -55,3 +55,20 @@ type PluginCreationInput struct { // ConfigurationItemsFunc is a function type that gets configuration items. // The scopeTo will indicate if there is additional scope that is needed type ConfigurationItemsFunc func(scopeTo string) (config.ConfigurationSet, error) + +// Identity represents a users identity for use with discovery. +// NOTE: details of this need finalising +type Identity interface { + Type() string + Name() string + IsExpired() bool + IdentityProviderName() string +} + +// Store represents an way to store and retrieve credentials +type Store interface { + CredsExists() (bool, error) + Save(identity Identity) error + Load() (Identity, error) + Expired() bool +} diff --git a/pkg/rancher/config.go b/pkg/rancher/config.go index af985be4..491327ad 100644 --- a/pkg/rancher/config.go +++ b/pkg/rancher/config.go @@ -30,7 +30,7 @@ const ( // CommonConfig represents the common configuration for Rancher type CommonConfig struct { // APIEndpoint is the URL for the rancher API endpoint - APIEndpoint string `json:"api-endpoint" validate:"required"` + APIEndpoint string `json:"api-endpoint"` } // AddCommonConfig adds the Rancher common configuration to a configuration set