11package agent
22
33import (
4+ "errors"
45 "fmt"
56 "io"
67 "net/url"
@@ -10,7 +11,6 @@ import (
1011
1112 "github.com/go-logr/logr"
1213 "github.com/hashicorp/go-multierror"
13- "github.com/pkg/errors"
1414 "github.com/spf13/cobra"
1515 "gopkg.in/yaml.v3"
1616 "k8s.io/client-go/rest"
@@ -39,7 +39,7 @@ type Config struct {
3939 // https://preflight.jetstack.io in Jetstack Secure OAuth and Jetstack
4040 // Secure API Token modes, and https://api.venafi.cloud in Venafi Cloud Key
4141 // Pair Service Account mode. It is ignored in Venafi Cloud VenafiConnection
42- // mode.
42+ // mode and in MachineHub mode .
4343 Server string `yaml:"server"`
4444
4545 // OrganizationID is only used in Jetstack Secure OAuth and Jetstack Secure
@@ -62,6 +62,9 @@ type Config struct {
6262 ExcludeAnnotationKeysRegex []string `yaml:"exclude-annotation-keys-regex"`
6363 // Skips label keys that match the given set of regular expressions.
6464 ExcludeLabelKeysRegex []string `yaml:"exclude-label-keys-regex"`
65+
66+ // MachineHub holds config specific to MachineHub mode.
67+ MachineHub MachineHubConfig `yaml:"machineHub"`
6568}
6669
6770type Endpoint struct {
@@ -89,6 +92,33 @@ type VenafiCloudConfig struct {
8992 UploadPath string `yaml:"upload_path,omitempty"`
9093}
9194
95+ // MachineHubConfig holds configuration values specific to the CyberArk Machine Hub integration
96+ type MachineHubConfig struct {
97+ // Subdomain is the subdomain indicating where data should be pushed. Used
98+ // for querying the Service Discovery Service to discover the Identity API
99+ // URL.
100+ Subdomain string `yaml:"subdomain"`
101+
102+ // CredentialsSecretName is the name of a Kubernetes Secret in the same
103+ // namespace as the agent, which will be watched for a username and password
104+ // to send to CyberArk Identity for authentication.
105+ CredentialsSecretName string `yaml:"credentialsSecretName"`
106+ }
107+
108+ func (mhc MachineHubConfig ) Validate () error {
109+ var errs []error
110+
111+ if mhc .Subdomain == "" {
112+ errs = append (errs , fmt .Errorf ("subdomain must not be empty in MachineHub mode" ))
113+ }
114+
115+ if mhc .CredentialsSecretName == "" {
116+ errs = append (errs , fmt .Errorf ("credentialsSecretName must not be empty in MachineHub mode" ))
117+ }
118+
119+ return errors .Join (errs ... )
120+ }
121+
92122type AgentCmdFlags struct {
93123 // ConfigFilePath (--config-file, -c) is the path to the agent configuration
94124 // YAML file.
@@ -103,6 +133,9 @@ type AgentCmdFlags struct {
103133 // --credentials-file.
104134 VenafiCloudMode bool
105135
136+ // MachineHubMode configures the agent to send data to CyberArk Machine Hub.
137+ MachineHubMode bool
138+
106139 // ClientID (--client-id) is the clientID in case of Venafi Cloud Key Pair
107140 // Service Account mode.
108141 ClientID string
@@ -303,30 +336,48 @@ func InitAgentCmdFlags(c *cobra.Command, cfg *AgentCmdFlags) {
303336 )
304337 c .PersistentFlags ().MarkDeprecated ("disable-compression" , "no longer has an effect" )
305338
339+ // This is a hidden feature flag we use to build the "Machine Hub" feature
340+ // gradually without impacting customers. Once the feature is GA, we will
341+ // turn this flag "on" by default.
342+ c .PersistentFlags ().BoolVar (
343+ & cfg .MachineHubMode ,
344+ "machine-hub" ,
345+ false ,
346+ "Enables the MachineHub mode. The agent will push data to CyberArk MachineHub." ,
347+ )
348+ c .PersistentFlags ().MarkHidden ("machine-hub" )
349+
306350}
307351
308- type AuthMode string
352+ // TLSPKMode controls how to authenticate to TLSPK / Jetstack Secure. Only one
353+ // TLSPKMode may be provided if using using those backends.
354+ type TLSPKMode string
309355
310356const (
311- JetstackSecureOAuth AuthMode = "Jetstack Secure OAuth"
312- JetstackSecureAPIToken AuthMode = "Jetstack Secure API Token"
313- VenafiCloudKeypair AuthMode = "Venafi Cloud Key Pair Service Account"
314- VenafiCloudVenafiConnection AuthMode = "Venafi Cloud VenafiConnection"
357+ JetstackSecureOAuth TLSPKMode = "Jetstack Secure OAuth"
358+ JetstackSecureAPIToken TLSPKMode = "Jetstack Secure API Token"
359+ VenafiCloudKeypair TLSPKMode = "Venafi Cloud Key Pair Service Account"
360+ VenafiCloudVenafiConnection TLSPKMode = "Venafi Cloud VenafiConnection"
361+
362+ // It is possible to push to both MachineHub and TLSPK. With this mode, the
363+ // agent will only push to MachineHub and not to TLSPK.
364+ Off TLSPKMode = "MachineHub only"
315365)
316366
317367// The command-line flags and the config file are combined into this struct by
318368// ValidateAndCombineConfig.
319369type CombinedConfig struct {
320- AuthMode AuthMode
321-
322- // Used by all modes.
323- ClusterID string
324370 DataGatherers []DataGatherer
325371 Period time.Duration
326372 BackoffMaxTime time.Duration
373+ InstallNS string
327374 StrictMode bool
328375 OneShot bool
329- InstallNS string
376+
377+ TLSPKMode TLSPKMode
378+
379+ // Used by all TLSPK modes.
380+ ClusterID string
330381
331382 // Used by JetstackSecureOAuth, JetstackSecureAPIToken, and
332383 // VenafiCloudKeypair. Ignored in VenafiCloudVenafiConnection mode.
@@ -351,6 +402,11 @@ type CombinedConfig struct {
351402 // Only used for testing purposes.
352403 OutputPath string
353404 InputPath string
405+
406+ // MachineHub-related settings.
407+ MachineHubMode bool
408+ MachineHubSubdomain string
409+ MachineHubCredentialsSecretName string
354410}
355411
356412// ValidateAndCombineConfig combines and validates the input configuration with
@@ -366,9 +422,23 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
366422 res := CombinedConfig {}
367423 var errs error
368424
425+ if flags .MachineHubMode {
426+ err := cfg .MachineHub .Validate ()
427+ if err != nil {
428+ return CombinedConfig {}, nil , fmt .Errorf ("invalid MachineHub config provided: %w" , err )
429+ }
430+
431+ res .MachineHubMode = true
432+ res .MachineHubSubdomain = cfg .MachineHub .Subdomain
433+ res .MachineHubCredentialsSecretName = cfg .MachineHub .CredentialsSecretName
434+
435+ keysAndValues := []any {"credentialsSecretName" , res .MachineHubCredentialsSecretName }
436+ log .V (logs .Info ).Info ("Will push to CyberArk MachineHub using a username and password loaded from a Kubernetes Secret" , keysAndValues ... )
437+ }
438+
369439 {
370440 var (
371- mode AuthMode
441+ mode TLSPKMode
372442 reason string
373443 keysAndValues []any
374444 )
@@ -396,19 +466,30 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
396466 mode = JetstackSecureOAuth
397467 reason = "--credentials-file was specified without --venafi-cloud"
398468 default :
399- return CombinedConfig {}, nil , fmt .Errorf ("no auth mode specified. You can use one of four auth modes:\n " +
400- " - Use (--venafi-cloud with --credentials-file) or (--client-id with --private-key-path) to use the " + string (VenafiCloudKeypair ) + " mode.\n " +
401- " - Use --venafi-connection for the " + string (VenafiCloudVenafiConnection ) + " mode.\n " +
402- " - Use --credentials-file alone if you want to use the " + string (JetstackSecureOAuth ) + " mode.\n " +
403- " - Use --api-token if you want to use the " + string (JetstackSecureAPIToken ) + " mode.\n " )
469+ if ! flags .MachineHubMode {
470+ return CombinedConfig {}, nil , fmt .Errorf ("no TLSPK mode specified and MachineHub mode is disabled. You must either enable the MachineHub mode (using --machine-hub), or enable one of the TLSPK modes.\n " +
471+ "To enable one of the TLSPK modes, you can:\n " +
472+ " - Use (--venafi-cloud with --credentials-file) or (--client-id with --private-key-path) to use the " + string (VenafiCloudKeypair ) + " mode.\n " +
473+ " - Use --venafi-connection for the " + string (VenafiCloudVenafiConnection ) + " mode.\n " +
474+ " - Use --credentials-file alone if you want to use the " + string (JetstackSecureOAuth ) + " mode.\n " +
475+ " - Use --api-token if you want to use the " + string (JetstackSecureAPIToken ) + " mode.\n " +
476+ "Note that it is possible to use one of the TLSPK modes along with the MachineHub mode (--machine-hub)." )
477+ }
478+
479+ mode = Off
404480 }
405- res . AuthMode = mode
481+
406482 keysAndValues = append (keysAndValues , "mode" , mode , "reason" , reason )
407- log .V (logs .Debug ).Info ("Authentication mode" , keysAndValues ... )
483+ if mode != Off {
484+ log .V (logs .Debug ).Info ("Configured to push to Venafi" , keysAndValues ... )
485+ }
486+
487+ res .TLSPKMode = mode
408488 }
409489
410490 // Validation and defaulting of `server` and the deprecated `endpoint.path`.
411- {
491+ if res .TLSPKMode != Off {
492+ // Only relevant if using TLSPK backends
412493 hasEndpointField := cfg .Endpoint .Host != "" && cfg .Endpoint .Path != ""
413494 hasServerField := cfg .Server != ""
414495 var server string
@@ -430,7 +511,7 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
430511 endpointPath = cfg .Endpoint .Path
431512 case ! hasServerField && ! hasEndpointField :
432513 server = "https://preflight.jetstack.io"
433- if res .AuthMode == VenafiCloudKeypair {
514+ if res .TLSPKMode == VenafiCloudKeypair {
434515 // The VenafiCloudVenafiConnection mode doesn't need a server.
435516 server = client .VenafiCloudProdURL
436517 }
@@ -439,7 +520,7 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
439520 if urlErr != nil || url .Hostname () == "" {
440521 errs = multierror .Append (errs , fmt .Errorf ("server %q is not a valid URL" , server ))
441522 }
442- if res .AuthMode == VenafiCloudVenafiConnection && server != "" {
523+ if res .TLSPKMode == VenafiCloudVenafiConnection && server != "" {
443524 log .Info (fmt .Sprintf ("ignoring the server field specified in the config file. In %s mode, this field is not needed." , VenafiCloudVenafiConnection ))
444525 server = ""
445526 }
@@ -451,9 +532,9 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
451532 {
452533 var uploadPath string
453534 switch {
454- case res .AuthMode == VenafiCloudKeypair :
535+ case res .TLSPKMode == VenafiCloudKeypair :
455536 if cfg .VenafiCloud == nil || cfg .VenafiCloud .UploadPath == "" {
456- errs = multierror .Append (errs , fmt .Errorf ("the venafi-cloud.upload_path field is required when using the %s mode" , res .AuthMode ))
537+ errs = multierror .Append (errs , fmt .Errorf ("the venafi-cloud.upload_path field is required when using the %s mode" , res .TLSPKMode ))
457538 break // Skip to the end of the switch statement.
458539 }
459540 _ , urlErr := url .Parse (cfg .VenafiCloud .UploadPath )
@@ -463,14 +544,14 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
463544 }
464545
465546 uploadPath = cfg .VenafiCloud .UploadPath
466- case res .AuthMode == VenafiCloudVenafiConnection :
547+ case res .TLSPKMode == VenafiCloudVenafiConnection :
467548 // The venafi-cloud.upload_path was initially meant to let users
468549 // configure HTTP proxies, but it has never been used since HTTP
469550 // proxies don't rewrite paths. Thus, we've disabled the ability to
470551 // change this value with the new --venafi-connection flag, and this
471552 // field is simply ignored.
472553 if cfg .VenafiCloud != nil && cfg .VenafiCloud .UploadPath != "" {
473- log .Info (fmt .Sprintf (`ignoring the venafi-cloud.upload_path field in the config file. In %s mode, this field is not needed.` , res .AuthMode ))
554+ log .Info (fmt .Sprintf (`ignoring the venafi-cloud.upload_path field in the config file. In %s mode, this field is not needed.` , res .TLSPKMode ))
474555 }
475556 uploadPath = ""
476557 }
@@ -488,26 +569,26 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
488569 // https://venafi.atlassian.net/browse/VC-35385 is done.
489570 {
490571 if cfg .VenafiCloud != nil && cfg .VenafiCloud .UploaderID != "" {
491- log .Info (fmt .Sprintf (`ignoring the venafi-cloud.uploader_id field in the config file. This field is not needed in %s mode.` , res .AuthMode ))
572+ log .Info (fmt .Sprintf (`ignoring the venafi-cloud.uploader_id field in the config file. This field is not needed in %s mode.` , res .TLSPKMode ))
492573 }
493574 }
494575
495576 // Validation of `cluster_id` and `organization_id`.
496- {
577+ if res . TLSPKMode != Off {
497578 var clusterID string
498579 var organizationID string // Only used by the old jetstack-secure mode.
499580 switch {
500- case res .AuthMode == VenafiCloudKeypair :
581+ case res .TLSPKMode == VenafiCloudKeypair :
501582 if cfg .ClusterID == "" {
502- errs = multierror .Append (errs , fmt .Errorf ("cluster_id is required in %s mode" , res .AuthMode ))
583+ errs = multierror .Append (errs , fmt .Errorf ("cluster_id is required in %s mode" , res .TLSPKMode ))
503584 }
504585 clusterID = cfg .ClusterID
505- case res .AuthMode == VenafiCloudVenafiConnection :
586+ case res .TLSPKMode == VenafiCloudVenafiConnection :
506587 if cfg .ClusterID == "" {
507- errs = multierror .Append (errs , fmt .Errorf ("cluster_id is required in %s mode" , res .AuthMode ))
588+ errs = multierror .Append (errs , fmt .Errorf ("cluster_id is required in %s mode" , res .TLSPKMode ))
508589 }
509590 clusterID = cfg .ClusterID
510- case res .AuthMode == JetstackSecureOAuth || res .AuthMode == JetstackSecureAPIToken :
591+ case res .TLSPKMode == JetstackSecureOAuth || res .TLSPKMode == JetstackSecureAPIToken :
511592 if cfg .OrganizationID == "" {
512593 errs = multierror .Append (errs , fmt .Errorf ("organization_id is required" ))
513594 }
@@ -567,7 +648,7 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
567648 res .InstallNS = installNS
568649
569650 // Validation of --venafi-connection and --venafi-connection-namespace.
570- if res .AuthMode == VenafiCloudVenafiConnection {
651+ if res .TLSPKMode == VenafiCloudVenafiConnection {
571652 res .VenConnName = flags .VenConnName
572653 var venConnNS string = flags .VenConnNS
573654 if flags .VenConnNS == "" {
@@ -634,7 +715,7 @@ func validateCredsAndCreateClient(log logr.Logger, flagCredentialsPath, flagClie
634715 var preflightClient client.Client
635716 metadata := & api.AgentMetadata {Version : version .PreflightVersion , ClusterID : cfg .ClusterID }
636717 switch {
637- case cfg .AuthMode == JetstackSecureOAuth :
718+ case cfg .TLSPKMode == JetstackSecureOAuth :
638719 // Note that there are no command line flags to configure the
639720 // JetstackSecureOAuth mode.
640721 credsBytes , err := readCredentialsFile (flagCredentialsPath )
@@ -653,7 +734,7 @@ func validateCredsAndCreateClient(log logr.Logger, flagCredentialsPath, flagClie
653734 if err != nil {
654735 errs = multierror .Append (errs , err )
655736 }
656- case cfg .AuthMode == VenafiCloudKeypair :
737+ case cfg .TLSPKMode == VenafiCloudKeypair :
657738 var creds client.Credentials
658739
659740 if flagClientID != "" && flagCredentialsPath != "" {
@@ -696,7 +777,7 @@ func validateCredsAndCreateClient(log logr.Logger, flagCredentialsPath, flagClie
696777 if err != nil {
697778 errs = multierror .Append (errs , err )
698779 }
699- case cfg .AuthMode == VenafiCloudVenafiConnection :
780+ case cfg .TLSPKMode == VenafiCloudVenafiConnection :
700781 var restCfg * rest.Config
701782 restCfg , err := kubeconfig .LoadRESTConfig ("" )
702783 if err != nil {
@@ -708,18 +789,20 @@ func validateCredsAndCreateClient(log logr.Logger, flagCredentialsPath, flagClie
708789 if err != nil {
709790 errs = multierror .Append (errs , err )
710791 }
711- case cfg .AuthMode == JetstackSecureAPIToken :
792+ case cfg .TLSPKMode == JetstackSecureAPIToken :
712793 var err error
713794 preflightClient , err = client .NewAPITokenClient (metadata , flagAPIToken , cfg .Server )
714795 if err != nil {
715796 errs = multierror .Append (errs , err )
716797 }
798+ case cfg .TLSPKMode == Off :
799+ // No client needed in this mode.
717800 default :
718- panic (fmt .Errorf ("programmer mistake: auth mode not implemented: %s" , cfg .AuthMode ))
801+ panic (fmt .Errorf ("programmer mistake: auth mode not implemented: %s" , cfg .TLSPKMode ))
719802 }
720803
721804 if errs != nil {
722- return nil , fmt .Errorf ("failed loading config using the %s mode: %w" , cfg .AuthMode , errs )
805+ return nil , fmt .Errorf ("failed loading config using the %s mode: %w" , cfg .TLSPKMode , errs )
723806 }
724807
725808 return preflightClient , nil
@@ -756,7 +839,7 @@ func createCredentialClient(log logr.Logger, credentials client.Credentials, cfg
756839 uploaderID := "no"
757840
758841 var uploadPath string
759- if cfg .AuthMode == VenafiCloudKeypair {
842+ if cfg .TLSPKMode == VenafiCloudKeypair {
760843 // We don't do this for the VenafiCloudVenafiConnection mode because
761844 // the upload_path field is ignored in that mode.
762845 log .Info ("Loading upload_path from \" venafi-cloud\" configuration." )
@@ -846,7 +929,7 @@ func (c *Config) Dump() (string, error) {
846929 d , err := yaml .Marshal (& c )
847930
848931 if err != nil {
849- return "" , errors . Wrap ( err , "failed to generate YAML dump of config" )
932+ return "" , fmt . Errorf ( "failed to generate YAML dump of config: %w" , err )
850933 }
851934
852935 return string (d ), nil
0 commit comments