Skip to content

Commit 757be26

Browse files
committed
feat: add CyberArk Identity client
This includes a bunch of testing data obtained experimentally from testing against an integration-environment tenant. I've confirmed that I'm able to log in using this code. Currently, this code stores the login token and requires exactly one username/password challenge to complete successfully. Signed-off-by: Ashley Davis <[email protected]>
1 parent 22d7135 commit 757be26

11 files changed

+1187
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
advanceBody *advanceAuthenticationRequestBody
15+
16+
expectedError error
17+
}{
18+
"success": {
19+
username: successUser,
20+
advanceBody: &advanceAuthenticationRequestBody{
21+
Action: ActionAnswer,
22+
Answer: successPassword,
23+
MechanismID: successMechanismID,
24+
SessionID: successSessionID,
25+
TenantID: "foo",
26+
PersistantLogin: true,
27+
},
28+
29+
expectedError: nil,
30+
},
31+
"incorrect password": {
32+
username: successUser,
33+
advanceBody: &advanceAuthenticationRequestBody{
34+
Action: ActionAnswer,
35+
Answer: "foo",
36+
MechanismID: successMechanismID,
37+
SessionID: successSessionID,
38+
TenantID: "foo",
39+
PersistantLogin: true,
40+
},
41+
42+
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"`),
43+
},
44+
"bad action": {
45+
username: successUser,
46+
advanceBody: &advanceAuthenticationRequestBody{
47+
Action: "foo",
48+
Answer: successPassword,
49+
MechanismID: successMechanismID,
50+
SessionID: successSessionID,
51+
TenantID: "foo",
52+
PersistantLogin: true,
53+
},
54+
55+
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"`),
56+
},
57+
"bad mechanism id": {
58+
username: successUser,
59+
advanceBody: &advanceAuthenticationRequestBody{
60+
Action: ActionAnswer,
61+
Answer: successPassword,
62+
MechanismID: "foo",
63+
SessionID: successSessionID,
64+
TenantID: "foo",
65+
PersistantLogin: true,
66+
},
67+
68+
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"`),
69+
},
70+
"bad session id": {
71+
username: successUser,
72+
advanceBody: &advanceAuthenticationRequestBody{
73+
Action: ActionAnswer,
74+
Answer: successPassword,
75+
MechanismID: successMechanismID,
76+
SessionID: "foo",
77+
TenantID: "foo",
78+
PersistantLogin: true,
79+
},
80+
81+
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"`),
82+
},
83+
"persistant login not set": {
84+
username: successUser,
85+
advanceBody: &advanceAuthenticationRequestBody{
86+
Action: ActionAnswer,
87+
Answer: successPassword,
88+
MechanismID: successMechanismID,
89+
SessionID: successSessionID,
90+
TenantID: "foo",
91+
PersistantLogin: false,
92+
},
93+
94+
expectedError: fmt.Errorf("got unexpected status code 403 Forbidden from request to advance authentication in CyberArk Identity API"),
95+
},
96+
}
97+
98+
for name, testSpec := range tests {
99+
t.Run(name, func(t *testing.T) {
100+
ctx := context.Background()
101+
102+
identityServer := MockIdentityServer()
103+
defer identityServer.Close()
104+
105+
mockDiscoveryServer := servicediscovery.MockDiscoveryServerWithCustomAPIURL(identityServer.Server.URL)
106+
defer mockDiscoveryServer.Close()
107+
108+
discoveryClient := servicediscovery.New(servicediscovery.WithCustomEndpoint(mockDiscoveryServer.Server.URL))
109+
110+
client, err := NewWithDiscoveryClient(ctx, discoveryClient, servicediscovery.MockDiscoverySubdomain)
111+
if err != nil {
112+
t.Errorf("failed to create identity client: %s", err)
113+
return
114+
}
115+
116+
err = client.doAdvanceAuthentication(ctx, testSpec.username, testSpec.advanceBody)
117+
if testSpec.expectedError != err {
118+
if testSpec.expectedError == nil {
119+
t.Errorf("didn't expect an error but got %v", err)
120+
return
121+
}
122+
123+
if err == nil {
124+
t.Errorf("expected no error but got err=%v", testSpec.expectedError)
125+
return
126+
}
127+
128+
if err.Error() != testSpec.expectedError.Error() {
129+
t.Errorf("expected err=%v\nbut got err=%v", testSpec.expectedError, err)
130+
return
131+
}
132+
}
133+
134+
if testSpec.expectedError != nil {
135+
return
136+
}
137+
138+
val, ok := client.tokenCache[testSpec.username]
139+
140+
if !ok {
141+
t.Errorf("expected token for %s to be set to %q but wasn't found", testSpec.username, mockSuccessfulStartAuthenticationToken)
142+
return
143+
}
144+
145+
if val != mockSuccessfulStartAuthenticationToken {
146+
t.Errorf("expected token for %s to be set to %q but was set to %q", testSpec.username, mockSuccessfulStartAuthenticationToken, val)
147+
}
148+
})
149+
}
150+
}
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)