Skip to content

Commit 3a10eeb

Browse files
authored
Merge pull request #655 from jetstack/cyberark-identity
feat: Initial work on CyberArk Identity client
2 parents 8c2091c + c633902 commit 3a10eeb

19 files changed

+1412
-97
lines changed

LICENSES

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ github.com/asaskevich/govalidator,https://github.com/asaskevich/govalidator/blob
66
github.com/aymerick/douceur,https://github.com/aymerick/douceur/blob/v0.2.0/LICENSE,MIT
77
github.com/beorn7/perks/quantile,https://github.com/beorn7/perks/blob/v1.0.1/LICENSE,MIT
88
github.com/blang/semver/v4,https://github.com/blang/semver/blob/v4.0.0/v4/LICENSE,MIT
9-
github.com/cenkalti/backoff,https://github.com/cenkalti/backoff/blob/v2.2.1/LICENSE,MIT
9+
github.com/cenkalti/backoff/v5,https://github.com/cenkalti/backoff/blob/v5.0.2/LICENSE,MIT
1010
github.com/cespare/xxhash/v2,https://github.com/cespare/xxhash/blob/v2.3.0/LICENSE.txt,MIT
1111
github.com/davecgh/go-spew/spew,https://github.com/davecgh/go-spew/blob/d8f796af33cc/LICENSE,ISC
1212
github.com/emicklei/go-restful/v3,https://github.com/emicklei/go-restful/blob/v3.11.2/LICENSE,MIT

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.23.4
44

55
require (
66
github.com/Venafi/vcert/v5 v5.8.1
7-
github.com/cenkalti/backoff v2.2.1+incompatible
7+
github.com/cenkalti/backoff/v5 v5.0.2
88
github.com/d4l3k/messagediff v1.2.1
99
github.com/fatih/color v1.17.0
1010
github.com/google/uuid v1.6.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1616
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
1717
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
1818
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
19-
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
20-
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
2119
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
2220
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
21+
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
22+
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
2323
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2424
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
2525
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=

pkg/agent/run.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"strings"
1515
"time"
1616

17-
"github.com/cenkalti/backoff"
17+
"github.com/cenkalti/backoff/v5"
1818
"github.com/go-logr/logr"
1919
"github.com/hashicorp/go-multierror"
2020
"github.com/prometheus/client_golang/prometheus"
@@ -330,14 +330,17 @@ func gatherAndOutputData(ctx context.Context, eventf Eventf, config CombinedConf
330330
backOff := backoff.NewExponentialBackOff()
331331
backOff.InitialInterval = 30 * time.Second
332332
backOff.MaxInterval = 3 * time.Minute
333-
backOff.MaxElapsedTime = config.BackoffMaxTime
334-
post := func() error {
335-
return postData(klog.NewContext(ctx, log), config, preflightClient, readings)
333+
334+
post := func() (any, error) {
335+
return struct{}{}, postData(klog.NewContext(ctx, log), config, preflightClient, readings)
336336
}
337-
err := backoff.RetryNotify(post, backOff, func(err error, t time.Duration) {
337+
338+
notificationFunc := backoff.Notify(func(err error, t time.Duration) {
338339
eventf("Warning", "PushingErr", "retrying in %v after error: %s", t, err)
339340
log.Info("Warning: PushingErr: retrying", "in", t, "reason", err)
340341
})
342+
343+
_, err := backoff.Retry(ctx, post, backoff.WithBackOff(backOff), backoff.WithNotify(notificationFunc), backoff.WithMaxElapsedTime(config.BackoffMaxTime))
341344
if err != nil {
342345
return fmt.Errorf("Exiting due to fatal error uploading: %v", err)
343346
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package identity
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
9+
)
10+
11+
func Test_IdentityAdvanceAuthentication(t *testing.T) {
12+
tests := map[string]struct {
13+
username string
14+
password []byte
15+
advanceBody advanceAuthenticationRequestBody
16+
17+
expectedError error
18+
}{
19+
"success": {
20+
username: successUser,
21+
password: []byte(successPassword),
22+
advanceBody: advanceAuthenticationRequestBody{
23+
Action: ActionAnswer,
24+
MechanismID: successMechanismID,
25+
SessionID: successSessionID,
26+
TenantID: "foo",
27+
PersistantLogin: true,
28+
},
29+
30+
expectedError: nil,
31+
},
32+
"incorrect password": {
33+
username: successUser,
34+
password: []byte("foo"),
35+
advanceBody: advanceAuthenticationRequestBody{
36+
Action: ActionAnswer,
37+
MechanismID: successMechanismID,
38+
SessionID: successSessionID,
39+
TenantID: "foo",
40+
PersistantLogin: true,
41+
},
42+
43+
expectedError: fmt.Errorf(`got a failure response from request to advance authentication: message="Authentication (login or challenge) has failed. Please try again or contact your system administrator.", error="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:55555555555555555555555555555555"`),
44+
},
45+
"bad action": {
46+
username: successUser,
47+
password: []byte(successPassword),
48+
advanceBody: advanceAuthenticationRequestBody{
49+
Action: "foo",
50+
MechanismID: successMechanismID,
51+
SessionID: successSessionID,
52+
TenantID: "foo",
53+
PersistantLogin: true,
54+
},
55+
56+
expectedError: fmt.Errorf(`got a failure response from request to advance authentication: message="Authentication (login or challenge) has failed. Please try again or contact your system administrator.", error="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:55555555555555555555555555555555"`),
57+
},
58+
"bad mechanism id": {
59+
username: successUser,
60+
password: []byte(successPassword),
61+
advanceBody: advanceAuthenticationRequestBody{
62+
Action: ActionAnswer,
63+
MechanismID: "foo",
64+
SessionID: successSessionID,
65+
TenantID: "foo",
66+
PersistantLogin: true,
67+
},
68+
69+
expectedError: fmt.Errorf(`got a failure response from request to advance authentication: message="Authentication (login or challenge) has failed. Please try again or contact your system administrator.", error="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:55555555555555555555555555555555"`),
70+
},
71+
"bad session id": {
72+
username: successUser,
73+
password: []byte(successPassword),
74+
advanceBody: advanceAuthenticationRequestBody{
75+
Action: ActionAnswer,
76+
MechanismID: successMechanismID,
77+
SessionID: "foo",
78+
TenantID: "foo",
79+
PersistantLogin: true,
80+
},
81+
82+
expectedError: fmt.Errorf(`got a failure response from request to advance authentication: message="Authentication (login or challenge) has failed. Please try again or contact your system administrator.", error="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:55555555555555555555555555555555"`),
83+
},
84+
"persistant login not set": {
85+
username: successUser,
86+
password: []byte(successPassword),
87+
advanceBody: advanceAuthenticationRequestBody{
88+
Action: ActionAnswer,
89+
MechanismID: successMechanismID,
90+
SessionID: successSessionID,
91+
TenantID: "foo",
92+
PersistantLogin: false,
93+
},
94+
95+
expectedError: fmt.Errorf("got unexpected status code 403 Forbidden from request to advance authentication in CyberArk Identity API"),
96+
},
97+
}
98+
99+
for name, testSpec := range tests {
100+
t.Run(name, func(t *testing.T) {
101+
ctx := context.Background()
102+
103+
identityServer := MockIdentityServer()
104+
defer identityServer.Close()
105+
106+
mockDiscoveryServer := servicediscovery.MockDiscoveryServerWithCustomAPIURL(identityServer.Server.URL)
107+
defer mockDiscoveryServer.Close()
108+
109+
discoveryClient := servicediscovery.New(servicediscovery.WithCustomEndpoint(mockDiscoveryServer.Server.URL))
110+
111+
client, err := NewWithDiscoveryClient(ctx, discoveryClient, servicediscovery.MockDiscoverySubdomain)
112+
if err != nil {
113+
t.Errorf("failed to create identity client: %s", err)
114+
return
115+
}
116+
117+
err = client.doAdvanceAuthentication(ctx, testSpec.username, &testSpec.password, testSpec.advanceBody)
118+
if testSpec.expectedError != err {
119+
if testSpec.expectedError == nil {
120+
t.Errorf("didn't expect an error but got %v", err)
121+
return
122+
}
123+
124+
if err == nil {
125+
t.Errorf("expected no error but got err=%v", testSpec.expectedError)
126+
return
127+
}
128+
129+
if err.Error() != testSpec.expectedError.Error() {
130+
t.Errorf("expected err=%v\nbut got err=%v", testSpec.expectedError, err)
131+
return
132+
}
133+
}
134+
135+
if testSpec.expectedError != nil {
136+
return
137+
}
138+
139+
val, ok := client.tokenCache[testSpec.username]
140+
141+
if !ok {
142+
t.Errorf("expected token for %s to be set to %q but wasn't found", testSpec.username, mockSuccessfulStartAuthenticationToken)
143+
return
144+
}
145+
146+
if val != mockSuccessfulStartAuthenticationToken {
147+
t.Errorf("expected token for %s to be set to %q but was set to %q", testSpec.username, mockSuccessfulStartAuthenticationToken, val)
148+
}
149+
})
150+
}
151+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"os/signal"
9+
10+
"k8s.io/klog/v2"
11+
12+
"github.com/jetstack/preflight/pkg/internal/cyberark/identity"
13+
"github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery"
14+
)
15+
16+
// This is a trivial CLI application for testing our identity client end-to-end.
17+
// It's not intended for distribution; it simply allows us to run our client and check
18+
// the login is successful.
19+
20+
const (
21+
subdomainFlag = "subdomain"
22+
usernameFlag = "username"
23+
passwordEnv = "TESTIDENTITY_PASSWORD"
24+
)
25+
26+
var (
27+
subdomain string
28+
username string
29+
)
30+
31+
func run(ctx context.Context) error {
32+
if subdomain == "" {
33+
return fmt.Errorf("no %s flag provided", subdomainFlag)
34+
}
35+
36+
if username == "" {
37+
return fmt.Errorf("no %s flag provided", usernameFlag)
38+
}
39+
40+
password := os.Getenv(passwordEnv)
41+
if password == "" {
42+
return fmt.Errorf("no password provided in %s", passwordEnv)
43+
}
44+
sdClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint())
45+
46+
client, err := identity.NewWithDiscoveryClient(ctx, sdClient, subdomain)
47+
if err != nil {
48+
return err
49+
}
50+
51+
err = client.LoginUsernamePassword(ctx, username, []byte(password))
52+
if err != nil {
53+
return err
54+
}
55+
56+
return nil
57+
}
58+
59+
func main() {
60+
defer klog.Flush()
61+
62+
flagSet := flag.NewFlagSet("test", flag.ExitOnError)
63+
klog.InitFlags(flagSet)
64+
_ = flagSet.Parse([]string{"--v", "5"})
65+
66+
logger := klog.Background()
67+
68+
ctx := klog.NewContext(context.Background(), logger)
69+
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
70+
defer cancel()
71+
72+
flag.StringVar(&subdomain, subdomainFlag, "cert-manager", "The subdomain to use for service discovery")
73+
flag.StringVar(&username, usernameFlag, "",
74+
fmt.Sprintf("Username to log in with. Password should be provided via %s envvar", passwordEnv),
75+
)
76+
77+
flag.Parse()
78+
79+
errCode := 0
80+
81+
err := run(ctx)
82+
if err != nil {
83+
logger.Error(err, "execution failed")
84+
errCode = 1
85+
}
86+
87+
klog.FlushAndExit(klog.ExitFlushTimeout, errCode)
88+
}

0 commit comments

Comments
 (0)