Skip to content

Commit bc5648a

Browse files
Add MachineHub output mode and a cyberark client
Signed-off-by: Richard Wall <[email protected]>
1 parent 57d3435 commit bc5648a

File tree

6 files changed

+204
-63
lines changed

6 files changed

+204
-63
lines changed

pkg/agent/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ const (
334334
VenafiCloudKeypair OutputMode = "Venafi Cloud Key Pair Service Account"
335335
VenafiCloudVenafiConnection OutputMode = "Venafi Cloud VenafiConnection"
336336
LocalFile OutputMode = "Local File"
337+
MachineHub OutputMode = "MachineHub"
337338
)
338339

339340
// The command-line flags and the config file are combined into this struct by
@@ -420,6 +421,9 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
420421
case !flags.VenafiCloudMode && flags.CredentialsPath != "":
421422
mode = JetstackSecureOAuth
422423
reason = "--credentials-file was specified without --venafi-cloud"
424+
case flags.MachineHubMode:
425+
mode = MachineHub
426+
reason = "--machine-hub was specified"
423427
case flags.OutputPath != "":
424428
mode = LocalFile
425429
reason = "--output-path was specified"
@@ -433,6 +437,7 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags)
433437
" - Use --venafi-connection for the " + string(VenafiCloudVenafiConnection) + " mode.\n" +
434438
" - Use --credentials-file alone if you want to use the " + string(JetstackSecureOAuth) + " mode.\n" +
435439
" - Use --api-token if you want to use the " + string(JetstackSecureAPIToken) + " mode.\n" +
440+
" - Use --machine-hub if you want to use the " + string(MachineHub) + " mode.\n" +
436441
" - Use --output-path or output-path in the config file for " + string(LocalFile) + " mode.")
437442
}
438443

@@ -762,6 +767,12 @@ func validateCredsAndCreateClient(log logr.Logger, flagCredentialsPath, flagClie
762767
}
763768
case LocalFile:
764769
outputClient = client.NewFileClient(cfg.OutputPath)
770+
case MachineHub:
771+
var err error
772+
outputClient, err = client.NewCyberArk()
773+
if err != nil {
774+
errs = multierror.Append(errs, err)
775+
}
765776
default:
766777
panic(fmt.Errorf("programmer mistake: output mode not implemented: %s", cfg.OutputMode))
767778
}

pkg/agent/config_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func Test_ValidateAndCombineConfig(t *testing.T) {
199199
- Use --venafi-connection for the Venafi Cloud VenafiConnection mode.
200200
- Use --credentials-file alone if you want to use the Jetstack Secure OAuth mode.
201201
- Use --api-token if you want to use the Jetstack Secure API Token mode.
202+
- Use --machine-hub if you want to use the MachineHub mode.
202203
- Use --output-path or output-path in the config file for Local File mode.`))
203204
assert.Nil(t, cl)
204205
})
@@ -617,6 +618,40 @@ func Test_ValidateAndCombineConfig(t *testing.T) {
617618
assert.Equal(t, VenafiCloudVenafiConnection, got.OutputMode)
618619
})
619620

621+
t.Run("--machine-hub selects MachineHub mode", func(t *testing.T) {
622+
t.Setenv("POD_NAMESPACE", "venafi")
623+
t.Setenv("KUBECONFIG", withFile(t, fakeKubeconfig))
624+
t.Setenv("ARK_PLATFORM_DOMAIN", "cyberark.cloud")
625+
t.Setenv("ARK_SUBDOMAIN", "tlspk")
626+
t.Setenv("ARK_USERNAME", "[email protected]")
627+
t.Setenv("ARK_SECRET", "test-secret")
628+
got, cl, err := ValidateAndCombineConfig(discardLogs(),
629+
withConfig(""),
630+
withCmdLineFlags("--period", "1m", "--machine-hub"))
631+
require.NoError(t, err)
632+
assert.Equal(t, MachineHub, got.OutputMode)
633+
assert.IsType(t, &client.CyberArkClient{}, cl)
634+
})
635+
636+
t.Run("--machine-hub without required environment variables", func(t *testing.T) {
637+
t.Setenv("POD_NAMESPACE", "venafi")
638+
t.Setenv("KUBECONFIG", withFile(t, fakeKubeconfig))
639+
t.Setenv("ARK_PLATFORM_DOMAIN", "")
640+
t.Setenv("ARK_SUBDOMAIN", "")
641+
t.Setenv("ARK_USERNAME", "")
642+
t.Setenv("ARK_SECRET", "")
643+
got, cl, err := ValidateAndCombineConfig(discardLogs(),
644+
withConfig(""),
645+
withCmdLineFlags("--period", "1m", "--machine-hub"))
646+
assert.Equal(t, CombinedConfig{}, got)
647+
assert.Nil(t, cl)
648+
assert.EqualError(t, err, testutil.Undent(`
649+
validating creds: failed loading config using the MachineHub mode: 1 error occurred:
650+
* missing environment variables: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET
651+
652+
`))
653+
})
654+
620655
t.Run("argument: --output-file selects local file mode", func(t *testing.T) {
621656
log, gotLog := recordLogs(t)
622657
got, outputClient, err := ValidateAndCombineConfig(log,

pkg/client/client_cyberark.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
package client
22

33
import (
4+
"context"
5+
6+
"github.com/jetstack/preflight/api"
7+
"github.com/jetstack/preflight/pkg/internal/cyberark"
48
"github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
59
)
610

7-
type CyberArkClient = dataupload.CyberArkClient
11+
type CyberArkClient struct {
12+
configLoader cyberark.ClientConfigLoader
13+
}
14+
15+
var _ Client = &CyberArkClient{}
16+
17+
func NewCyberArk() (*CyberArkClient, error) {
18+
configLoader := cyberark.LoadClientConfigFromEnvironment
19+
_, err := configLoader()
20+
if err != nil {
21+
return nil, err
22+
}
23+
return &CyberArkClient{
24+
configLoader: configLoader,
25+
}, nil
26+
}
27+
28+
// An API token is obtained by authenticating with the ARK_USERNAME and ARK_SECRET from the environment.
29+
// ARK_SUBDOMAIN should be your tenant subdomain.
30+
// ARK_PLATFORM_DOMAIN should be either integration-cyberark.cloud or cyberark.cloud
31+
func (o *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, readings []*api.DataReading, options Options) error {
32+
cfg, err := o.configLoader()
33+
if err != nil {
34+
return err
35+
}
36+
datauploadClient, err := cyberark.NewDatauploadClient(ctx, cfg)
37+
if err != nil {
38+
return err
39+
}
840

9-
var NewCyberArkClient = dataupload.NewCyberArkClient
41+
err = datauploadClient.PostDataReadingsWithOptions(ctx, api.DataReadingsPost{}, dataupload.Options{
42+
ClusterName: "bb068932-c80d-460d-88df-34bc7f3f3297",
43+
})
44+
if err != nil {
45+
return err
46+
}
47+
return nil
48+
}

pkg/client/client_cyberark_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package client
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
"k8s.io/klog/v2"
9+
"k8s.io/klog/v2/ktesting"
10+
11+
"github.com/jetstack/preflight/api"
12+
13+
_ "k8s.io/klog/v2/ktesting/init"
14+
)
15+
16+
// TestCyberArkClient_PostDataReadingsWithOptions_RealAPI demonstrates that the
17+
// dataupload code works with the real inventory API.
18+
//
19+
// To enable verbose request logging:
20+
//
21+
// go test ./pkg/internal/cyberark/dataupload/... \
22+
// -v -count 1 -run TestPostDataReadingsWithOptionsWithRealAPI -args -testing.v 6
23+
func TestCyberArkClient_PostDataReadingsWithOptions_RealAPI(t *testing.T) {
24+
platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN")
25+
subdomain := os.Getenv("ARK_SUBDOMAIN")
26+
username := os.Getenv("ARK_USERNAME")
27+
secret := os.Getenv("ARK_SECRET")
28+
29+
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
30+
t.Skip("Skipping because one of the following environment variables is unset or empty: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
31+
return
32+
}
33+
34+
logger := ktesting.NewLogger(t, ktesting.DefaultConfig)
35+
ctx := klog.NewContext(t.Context(), logger)
36+
37+
c, err := NewCyberArk()
38+
require.NoError(t, err)
39+
var readings []*api.DataReading
40+
err = c.PostDataReadingsWithOptions(ctx, readings, Options{})
41+
require.NoError(t, err)
42+
}

pkg/internal/cyberark/client.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package cyberark
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"os"
8+
9+
"github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
10+
"github.com/jetstack/preflight/pkg/internal/cyberark/identity"
11+
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
12+
)
13+
14+
type ClientConfig struct {
15+
platformDomain string
16+
subdomain string
17+
username string
18+
secret string
19+
}
20+
21+
type ClientConfigLoader func() (ClientConfig, error)
22+
23+
func LoadClientConfigFromEnvironment() (ClientConfig, error) {
24+
platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN")
25+
subdomain := os.Getenv("ARK_SUBDOMAIN")
26+
username := os.Getenv("ARK_USERNAME")
27+
secret := os.Getenv("ARK_SECRET")
28+
29+
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
30+
return ClientConfig{}, errors.New(
31+
"missing environment variables: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
32+
}
33+
34+
return ClientConfig{
35+
platformDomain: platformDomain,
36+
subdomain: subdomain,
37+
username: username,
38+
secret: secret,
39+
}, nil
40+
41+
}
42+
43+
func NewDatauploadClient(ctx context.Context, cfg ClientConfig) (*dataupload.CyberArkClient, error) {
44+
const (
45+
discoveryContextServiceName = "inventory"
46+
separator = "."
47+
)
48+
49+
serviceURL := fmt.Sprintf("https://%s%s%s.%s", cfg.subdomain, separator, discoveryContextServiceName, cfg.platformDomain)
50+
51+
var (
52+
identityClient *identity.Client
53+
err error
54+
)
55+
if cfg.platformDomain == "cyberark.cloud" {
56+
identityClient, err = identity.New(ctx, cfg.subdomain)
57+
} else {
58+
discoveryClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
59+
identityClient, err = identity.NewWithDiscoveryClient(ctx, discoveryClient, cfg.subdomain)
60+
}
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
err = identityClient.LoginUsernamePassword(ctx, cfg.username, []byte(cfg.secret))
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
cyberArkClient, err := dataupload.NewCyberArkClient(nil, serviceURL, identityClient.AuthenticateRequest)
71+
if err != nil {
72+
return nil, err
73+
}
74+
return cyberArkClient, nil
75+
}

pkg/internal/cyberark/dataupload/dataupload_test.go

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,13 @@ import (
55
"encoding/pem"
66
"fmt"
77
"net/http"
8-
"os"
98
"testing"
109
"time"
1110

1211
"github.com/stretchr/testify/require"
13-
"k8s.io/klog/v2"
14-
"k8s.io/klog/v2/ktesting"
1512

1613
"github.com/jetstack/preflight/api"
1714
"github.com/jetstack/preflight/pkg/internal/cyberark/dataupload"
18-
"github.com/jetstack/preflight/pkg/internal/cyberark/identity"
19-
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
20-
21-
_ "k8s.io/klog/v2/ktesting/init"
2215
)
2316

2417
func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
@@ -123,57 +116,3 @@ func TestCyberArkClient_PostDataReadingsWithOptions(t *testing.T) {
123116
})
124117
}
125118
}
126-
127-
// TestPostDataReadingsWithOptionsWithRealAPI demonstrates that the dataupload code works with the real inventory API.
128-
// An API token is obtained by authenticating with the ARK_USERNAME and ARK_SECRET from the environment.
129-
// ARK_SUBDOMAIN should be your tenant subdomain.
130-
// ARK_PLATFORM_DOMAIN should be either integration-cyberark.cloud or cyberark.cloud
131-
//
132-
// To enable verbose request logging:
133-
//
134-
// go test ./pkg/internal/cyberark/dataupload/... \
135-
// -v -count 1 -run TestPostDataReadingsWithOptionsWithRealAPI -args -testing.v 6
136-
func TestPostDataReadingsWithOptionsWithRealAPI(t *testing.T) {
137-
platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN")
138-
subdomain := os.Getenv("ARK_SUBDOMAIN")
139-
username := os.Getenv("ARK_USERNAME")
140-
secret := os.Getenv("ARK_SECRET")
141-
142-
if platformDomain == "" || subdomain == "" || username == "" || secret == "" {
143-
t.Skip("Skipping because one of the following environment variables is unset or empty: ARK_PLATFORM_DOMAIN, ARK_SUBDOMAIN, ARK_USERNAME, ARK_SECRET")
144-
return
145-
}
146-
147-
logger := ktesting.NewLogger(t, ktesting.DefaultConfig)
148-
ctx := klog.NewContext(t.Context(), logger)
149-
150-
const (
151-
discoveryContextServiceName = "inventory"
152-
separator = "."
153-
)
154-
155-
serviceURL := fmt.Sprintf("https://%s%s%s.%s", subdomain, separator, discoveryContextServiceName, platformDomain)
156-
157-
var (
158-
identityClient *identity.Client
159-
err error
160-
)
161-
if platformDomain == "cyberark.cloud" {
162-
identityClient, err = identity.New(ctx, subdomain)
163-
} else {
164-
discoveryClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
165-
identityClient, err = identity.NewWithDiscoveryClient(ctx, discoveryClient, subdomain)
166-
}
167-
require.NoError(t, err)
168-
169-
err = identityClient.LoginUsernamePassword(ctx, username, []byte(secret))
170-
require.NoError(t, err)
171-
172-
cyberArkClient, err := dataupload.NewCyberArkClient(nil, serviceURL, identityClient.AuthenticateRequest)
173-
require.NoError(t, err)
174-
175-
err = cyberArkClient.PostDataReadingsWithOptions(ctx, api.DataReadingsPost{}, dataupload.Options{
176-
ClusterName: "bb068932-c80d-460d-88df-34bc7f3f3297",
177-
})
178-
require.NoError(t, err)
179-
}

0 commit comments

Comments
 (0)