Skip to content

Commit 7df9ca2

Browse files
authored
Merge pull request #161 from petrsnd/skills-harmonization
Skills harmonization: rename ci-cd-pipeline, add api-patterns and a2a-workflow
2 parents c414797 + ef4872f commit 7df9ca2

4 files changed

Lines changed: 610 additions & 170 deletions

File tree

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
name: a2a-workflow
3+
description: Use when working with SafeguardJava application-to-application credential retrieval, brokering, or A2A events.
4+
---
5+
6+
# A2A Workflow
7+
8+
Safeguard Application-to-Application (A2A) is the SDK path for non-interactive
9+
automation that needs privileged credentials without a user login prompt. In
10+
SafeguardJava, A2A is certificate-authenticated, API-key-scoped, and exposed
11+
through `Safeguard.A2A.getContext(...)` plus `ISafeguardA2AContext`. Use it for
12+
service accounts, scheduled jobs, brokers, and other integrations that need to
13+
retrieve or rotate credentials directly from Safeguard.
14+
15+
## 1. What A2A is
16+
17+
The main A2A surface is `com.oneidentity.safeguard.safeguardjava.ISafeguardA2AContext`.
18+
`SafeguardA2AContext` maintains two internal REST clients:
19+
20+
- an A2A client rooted at `https://<appliance>/service/a2a/v<apiVersion>`
21+
- a core client rooted at `https://<appliance>/service/core/v<apiVersion>`
22+
23+
The A2A client is used for credential retrieval and brokering (`Credentials`,
24+
`AccessRequests`), while the core client is used to enumerate `A2ARegistrations`
25+
and their `RetrievableAccounts`.
26+
27+
Important design constraints from the SDK:
28+
29+
- A2A uses certificate auth plus an `Authorization: A2A <apiKey>` header
30+
- `Service.A2A` is **not** valid with `ISafeguardConnection`
31+
- API keys are stored as `char[]` in the public interfaces
32+
- context objects must be cleaned up with `dispose()`
33+
34+
## 2. Setup flow (certificate registration, API key creation)
35+
36+
The repository samples document the expected appliance-side setup:
37+
38+
1. **Trust the certificate chain**
39+
- `Samples/CertificateConnect/README.md` calls out uploading the root/intermediate CA chain
40+
- the certificate must be trusted by Safeguard before certificate auth or A2A will work
41+
2. **Register the application / certificate identity**
42+
- create the application user or mapping that represents the calling service
43+
- certificate identity can come from a file, keystore alias, in-memory bytes, or a Windows thumbprint
44+
3. **Create A2A registrations**
45+
- `Samples/A2ARetrievalExample/README.md` calls out configuring retrieval registrations
46+
- each retrievable credential gets an API key that scopes access to that item
47+
4. **Distribute the client certificate and API key securely**
48+
- keep certificate passwords, API keys, and retrieved secrets in `char[]`
49+
- never check PFX/JKS files, passwords, or API keys into the repo
50+
51+
### Choosing a `getContext(...)` overload
52+
53+
`Safeguard.A2A.getContext(...)` supports the same certificate sources the rest of the SDK uses:
54+
55+
- keystore path + alias
56+
- PKCS12 / PFX file path
57+
- in-memory certificate bytes
58+
- Windows certificate thumbprint (Windows only)
59+
- optional `apiVersion`
60+
- either `ignoreSsl` or a custom `HostnameVerifier`
61+
62+
Examples from the actual factories in `Safeguard.java`:
63+
64+
```java
65+
ISafeguardA2AContext fromFile = Safeguard.A2A.getContext(
66+
appliance,
67+
certificatePath,
68+
certificatePassword,
69+
null,
70+
true);
71+
72+
ISafeguardA2AContext fromKeystore = Safeguard.A2A.getContext(
73+
appliance,
74+
keystorePath,
75+
keystorePassword,
76+
certificateAlias,
77+
null,
78+
true);
79+
```
80+
81+
Windows thumbprint overloads require `SunMSCAPI` to be available. The SDK throws a
82+
`SafeguardForJavaException` on non-Windows platforms or when the provider is missing.
83+
84+
## 3. Credential retrieval (programmatic access)
85+
86+
### Enumerate retrievable accounts
87+
88+
`ISafeguardA2AContext.getRetrievableAccounts()` first queries `Core/A2ARegistrations`,
89+
then enumerates each registration's `RetrievableAccounts` endpoint. The returned
90+
`IA2ARetrievableAccount` objects include application name, asset/account details,
91+
and the API key as `char[]`.
92+
93+
```java
94+
List<IA2ARetrievableAccount> accounts = a2aContext.getRetrievableAccounts();
95+
for (IA2ARetrievableAccount account : accounts) {
96+
System.out.println(account.getAssetName() + " -> " + account.getAccountName());
97+
}
98+
```
99+
100+
There is also a filtered form:
101+
102+
```java
103+
List<IA2ARetrievableAccount> accounts =
104+
a2aContext.getRetrievableAccounts("AccountName eq 'admin'");
105+
```
106+
107+
The filter is passed server-side as the `filter` query parameter on each registration lookup.
108+
109+
### Retrieve a password
110+
111+
This is the path shown in `Samples/A2ARetrievalExample/.../A2ARetrievalExample.java`:
112+
113+
```java
114+
char[] password = a2aContext.retrievePassword(apiKey);
115+
try {
116+
usePassword(password);
117+
} finally {
118+
java.util.Arrays.fill(password, '\0');
119+
}
120+
```
121+
122+
Internally, the SDK sends:
123+
124+
- `GET Credentials`
125+
- query parameter `type=Password`
126+
- header `Authorization: A2A <apiKey>`
127+
- client certificate on the TLS connection
128+
129+
### Set a password
130+
131+
The setter method name is capitalized in this SDK:
132+
133+
```java
134+
a2aContext.SetPassword(apiKey, newPassword);
135+
```
136+
137+
That becomes `PUT Credentials/Password` with a JSON string body.
138+
139+
### Retrieve or set an SSH private key
140+
141+
```java
142+
char[] privateKey = a2aContext.retrievePrivateKey(apiKey, KeyFormat.OpenSsh);
143+
a2aContext.SetPrivateKey(apiKey, privateKey, passphrase, KeyFormat.OpenSsh);
144+
```
145+
146+
The getter uses `GET Credentials?type=PrivateKey&keyFormat=<format>`.
147+
The setter uses `PUT Credentials/SshKey` with a serialized `SshKey` payload.
148+
149+
### Retrieve API key secrets
150+
151+
```java
152+
List<IApiKeySecret> secrets = a2aContext.retrieveApiKeySecret(apiKey);
153+
```
154+
155+
This uses `GET Credentials?type=ApiKey` and maps the response into `ApiKeySecret`
156+
objects with `clientId`, `clientSecret`, and related metadata.
157+
158+
## 4. Brokering
159+
160+
SafeguardJava supports brokering through:
161+
162+
- `IBrokeredAccessRequest`
163+
- `BrokeredAccessRequest`
164+
- `BrokeredAccessRequestType`
165+
- `ISafeguardA2AContext.brokerAccessRequest(...)`
166+
167+
The test harness in `tests/safeguardjavaclient/.../SafeguardTests.java` builds a
168+
`BrokeredAccessRequest`, sets `AccountId`, `AssetId`, `ForUserId`, and an access type,
169+
then calls `brokerAccessRequest(...)`.
170+
171+
Minimal pattern:
172+
173+
```java
174+
IBrokeredAccessRequest request = new BrokeredAccessRequest();
175+
request.setForUserId(forUserId);
176+
request.setAssetId(assetId);
177+
request.setAccountId(accountId);
178+
request.setAccessType(BrokeredAccessRequestType.Password);
179+
request.setReasonComment("Created by service broker");
180+
181+
String result = a2aContext.brokerAccessRequest(apiKey, request);
182+
```
183+
184+
What the SDK enforces before sending `POST AccessRequests`:
185+
186+
- either `ForUserId` or `ForUserName` must be set
187+
- either `AssetId` or `AssetName` must be set
188+
- `apiKey` and `accessRequest` cannot be null
189+
- the SDK stamps the request version from the active `apiVersion`
190+
191+
`BrokeredAccessRequest` also exposes optional fields for emergency access, reason
192+
codes, ticket numbers, `RequestedFor`, and day/hour/minute duration values.
193+
194+
## 5. Event listeners / SignalR
195+
196+
A2A supports SignalR listeners through the same `ISafeguardEventListener` interface used
197+
for standard Safeguard events.
198+
199+
### Context-based listeners
200+
201+
From `ISafeguardA2AContext`:
202+
203+
- `getA2AEventListener(char[] apiKey, ISafeguardEventHandler handler)`
204+
- `getA2AEventListener(List<char[]> apiKeys, ISafeguardEventHandler handler)`
205+
- `getPersistentA2AEventListener(char[] apiKey, ISafeguardEventHandler handler)`
206+
- `getPersistentA2AEventListener(List<char[]> apiKeys, ISafeguardEventHandler handler)`
207+
208+
`SafeguardA2AContext` automatically registers these event names on the listener:
209+
210+
- `AssetAccountPasswordUpdated`
211+
- `AssetAccountSshKeyUpdated`
212+
- `AccountApiKeySecretUpdated`
213+
214+
Basic usage:
215+
216+
```java
217+
ISafeguardEventHandler handler = (eventName, eventBody) -> {
218+
System.out.println(eventName);
219+
System.out.println(eventBody);
220+
};
221+
222+
ISafeguardEventListener listener =
223+
a2aContext.getPersistentA2AEventListener(apiKey, handler);
224+
listener.start();
225+
```
226+
227+
### Factory-based listeners
228+
229+
If you do not want to build the context yourself, `Safeguard.A2A.Event` exposes many
230+
`getPersistentA2AEventListener(...)` overloads that accept certificate file, keystore,
231+
thumbprint, or in-memory certificate inputs directly.
232+
233+
### Reconnect behavior
234+
235+
`PersistentSafeguardA2AEventListener` extends `PersistentSafeguardEventListenerBase`.
236+
When the internal SignalR listener disconnects, the base class:
237+
238+
1. disposes the broken listener
239+
2. recreates it from the A2A context
240+
3. re-registers handlers
241+
4. sleeps 5 seconds between failed reconnect attempts
242+
243+
This is the right choice for long-running services.
244+
245+
Non-persistent listeners do **not** recover from long outages.
246+
The interface documentation calls out the 30+ second outage case explicitly.
247+
248+
## 6. Error scenarios and troubleshooting
249+
250+
Common failures are visible directly in the public methods:
251+
252+
- `ObjectDisposedException` if you call the context after `dispose()`
253+
- `ArgumentException` for null `apiKey`, null password/private-key arguments, or an empty API key list
254+
- `SafeguardForJavaException("Unable to connect to web service ...")` when the HTTP client returns null
255+
- `SafeguardForJavaException("Error returned from Safeguard API, Error: <status> <body>")` for non-2xx responses
256+
- `SafeguardForJavaException("You must specify a user...")` or `("You must specify an asset...")` during brokering
257+
- `SafeguardForJavaException("Error parsing JSON response")` or serialization failures when payload conversion breaks
258+
- `SafeguardForJavaException("Missing SunMSCAPI provider...")` for Windows thumbprint usage without the provider
259+
- `SafeguardForJavaException("Not implemented. This function is only available on the Windows platform.")` for thumbprint overloads on non-Windows hosts
260+
261+
Troubleshooting checklist:
262+
263+
1. verify the certificate chain is trusted by Safeguard
264+
2. confirm the certificate maps to the intended application identity
265+
3. confirm the API key belongs to the registration you expect
266+
4. use `getRetrievableAccounts()` to prove what the certificate can actually see
267+
5. switch from a transient listener to a persistent listener if outages matter
268+
6. avoid `ignoreSsl=true` outside lab scenarios
269+
7. clear API keys, passwords, and retrieved secrets from memory when finished

0 commit comments

Comments
 (0)