Skip to content

Commit 1f16c9b

Browse files
authored
Merge pull request #8 from FINTLabs/develop
Develop to main
2 parents 723fdc9 + ce9e7e2 commit 1f16c9b

File tree

16 files changed

+269
-60
lines changed

16 files changed

+269
-60
lines changed

.github/workflows/CD.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ jobs:
2222
tags: ${{ steps.meta.outputs.tags }}
2323
steps:
2424
- name: Checkout repository
25-
uses: actions/checkout@v3.3.0
25+
uses: actions/checkout@v4
2626

2727
- name: Login to Docker Hub
28-
uses: docker/login-action@v2.1.0
28+
uses: docker/login-action@v3
2929
with:
3030
registry: ${{ env.REGISTRY }}
3131
username: ${{ github.actor }}
3232
password: ${{ secrets.GITHUB_TOKEN }}
3333

3434
- name: Extract metadata (tags, labels) for Docker
35-
uses: docker/metadata-action@v4.3.0
35+
uses: docker/metadata-action@v5
3636
id: meta
3737
with:
3838
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
@@ -64,9 +64,11 @@ jobs:
6464
cluster: aks-api-fint-2022-02-08
6565
- org: bfk-no
6666
cluster: aks-api-fint-2022-02-08
67+
- org: fridiks-no
68+
cluster: aks-api-fint-2022-02-08
6769
steps:
6870
- name: Checkout repository
69-
uses: actions/checkout@v3.3.0
71+
uses: actions/checkout@v4
7072

7173
- name: Get environment
7274
uses: actions/github-script@v7

.github/workflows/MD.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ on:
2121
- ofk-no
2222
- bfk-no
2323
- afk-no
24+
- fridiks-no
2425

2526
env:
2627
REGISTRY: ghcr.io

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
FROM gradle:8.4-jdk17 as builder
1+
FROM gradle:8.10.1-jdk as builder
22
USER root
33
COPY . .
44
RUN gradle --no-daemon build
55

6-
FROM gcr.io/distroless/java17
6+
FROM gcr.io/distroless/java21
77
ENV JAVA_TOOL_OPTIONS -XX:+ExitOnOutOfMemoryError
88
COPY --from=builder /home/gradle/build/libs/fint-kontroll-azure-ad-gateway-*.jar /data/app.jar
99
CMD ["/data/app.jar"]

build.gradle

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ plugins {
22
id 'org.springframework.boot' version '3.1.4'
33
id 'io.spring.dependency-management' version '1.1.0'
44
id 'java'
5-
id 'groovy'
65
}
76

87
group = 'no.fintlabs'
@@ -58,8 +57,7 @@ dependencies {
5857
testImplementation 'org.springframework.boot:spring-boot-starter-test'
5958
testImplementation 'io.projectreactor:reactor-test'
6059
testImplementation 'cglib:cglib-nodep:3.3.0'
61-
testImplementation 'org.spockframework:spock-spring:2.3-groovy-3.0'
62-
testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0'
60+
6361
}
6462

6563
test {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

kustomize/overlays/api/afk-no/kustomization.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ patches:
2929
path: "/spec/resources"
3030
value:
3131
limits:
32-
memory: "6Gi"
32+
memory: "8Gi"
3333
requests:
3434
cpu: "500m"
35-
memory: "4Gi"
35+
memory: "6Gi"
3636
- op: add
3737
path: "/spec/env/0"
3838
value:
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
namespace: fridiks-no
4+
resources:
5+
- ../../../base
6+
commonLabels:
7+
app.kubernetes.io/name: fint-kontroll-azure-ad-gateway
8+
app.kubernetes.io/instance: fint-kontroll-azure-ad-gateway_fridiks-no
9+
app.kubernetes.io/version: latest
10+
app.kubernetes.io/component: backend
11+
app.kubernetes.io/part-of: fint-kontroll
12+
fintlabs.no/team: bas
13+
fintlabs.no/org-id: fridiks.no
14+
patches:
15+
- patch: |-
16+
- op: replace
17+
path: "/spec/orgId"
18+
value: "fridiks.no"
19+
- op: replace
20+
path: "/spec/kafka/acls/0/topic"
21+
value: "fridiks-no.kontroll.entity.azure*"
22+
- op: replace
23+
path: "/spec/kafka/acls/1/topic"
24+
value: "fridiks-no.kontroll.entity.azure*"
25+
- op: replace
26+
path: "/spec/kafka/acls/2/topic"
27+
value: "fridiks-no.kontroll.entity.resource-group*"
28+
- op: add
29+
path: "/spec/resources"
30+
value:
31+
limits:
32+
memory: "2Gi"
33+
requests:
34+
cpu: "500m"
35+
memory: "1Gi"
36+
- op: add
37+
path: "/spec/env/0"
38+
value:
39+
name: "JAVA_TOOL_OPTIONS"
40+
value: "-XX:+ExitOnOutOfMemoryError -Xmx2048m"
41+
target:
42+
kind: Application
43+
name: fint-kontroll-azure-ad-gateway
44+
- patch: |-
45+
- op: replace
46+
path: "/spec/itemPath"
47+
value: "vaults/aks-api-vault/items/kontroll-frid-iks-azure-ad-gateway"
48+
target:
49+
kind: OnePasswordItem
50+
name: fint-kontroll-azure-ad-gateway

src/main/java/no/fintlabs/AzureClient.java

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import lombok.RequiredArgsConstructor;
1515
import lombok.extern.log4j.Log4j2;
1616
import no.fintlabs.azure.*;
17+
import no.fintlabs.cache.FintCache;
1718
import no.fintlabs.kafka.ResourceGroup;
1819
import no.fintlabs.kafka.ResourceGroupMembership;
1920
import okhttp3.Request;
@@ -35,6 +36,21 @@ public class AzureClient {
3536
private final AzureUserExternalProducerService azureUserExternalProducerService;
3637
private final AzureGroupProducerService azureGroupProducerService;
3738
private final AzureGroupMembershipProducerService azureGroupMembershipProducerService;
39+
private final FintCache<String, AzureUser> entraIdUserCache;
40+
private final FintCache<String, AzureUserExternal> entraIdExternalUserCache;
41+
//private final FintCache<String, Optional> resourceGroupMembershipCache;
42+
private final FintCache<String, AzureGroup> azureGroupCache;
43+
private final FintCache<String, AzureGroupMembership> azureGroupMembershipCache;
44+
AtomicInteger publishedMembers;
45+
46+
@Scheduled(cron = "${fint.kontroll.azure-ad-gateway.group-scheduler.clear-cache}")
47+
public void clearCaches() {
48+
entraIdUserCache.clear();
49+
entraIdExternalUserCache.clear();
50+
azureGroupCache.clear();
51+
azureGroupMembershipCache.clear();
52+
log.info("Caches for group, members and users has been reset to null due to scheduler. Next call will publish all users and groups from Entra ID to kafka");
53+
}
3854

3955
@Scheduled(
4056
initialDelayString = "${fint.kontroll.azure-ad-gateway.user-scheduler.pull.initial-delay-ms}",
@@ -63,31 +79,73 @@ private void pullAllUsers() {
6379
}
6480

6581
private void pageThroughUsers(UserCollectionPage inPage) {
66-
//inPageFuture.thenAccept(inPage -> {
67-
int users = 0;
82+
AtomicInteger users = new AtomicInteger();
83+
AtomicInteger changedUsers = new AtomicInteger();
84+
AtomicInteger changedExtUsers = new AtomicInteger();
6885

6986
UserCollectionPage page = inPage;
7087
do {
7188
for (User user : page.getCurrentPage()) {
72-
users++;
73-
if (AzureUser.getAttributeValue(user, configUser.getExternaluserattribute()) != null
74-
&& (AzureUser.getAttributeValue(user, configUser.getExternaluserattribute()).equalsIgnoreCase(configUser.getExternaluservalue()))) {
75-
log.debug("Adding external user to Kafka, {}", user.userPrincipalName);
76-
azureUserExternalProducerService.publish(new AzureUserExternal(user, configUser));
89+
users.getAndIncrement();
90+
91+
if (entraIdUserCache != null &&
92+
entraIdUserCache.containsKey(user.id)) {
93+
AzureUser entraIdUserObject = new AzureUser(user, configUser);
94+
if (entraIdUserObject.equals(entraIdUserCache.get(user.id))) {
95+
log.debug("User {} is unchanged. Skipping publishing to Kafka.", user.id);
96+
return;
97+
}
98+
}
99+
100+
String externalUserAttribute = AzureUser.getAttributeValue(user, configUser.getExternaluserattribute());
101+
if (externalUserAttribute != null
102+
&& externalUserAttribute.equalsIgnoreCase(configUser.getExternaluservalue())) {
103+
AzureUserExternal entraUserExtObject = new AzureUserExternal(user, configUser);
104+
if (entraIdExternalUserCache != null &&
105+
entraIdExternalUserCache.containsKey(user.id) && entraUserExtObject.equals(entraIdExternalUserCache.get(user.id))) {
106+
log.debug("External User {} is unchanged. Skipping publishing to Kafka.", user.id);
107+
return;
108+
} else {
109+
log.debug("Publishing external user to Kafka: {}", user.userPrincipalName);
110+
azureUserExternalProducerService.publish(new AzureUserExternal(user, configUser));
111+
changedExtUsers.getAndIncrement();
112+
entraIdExternalUserCache.put(user.id, new AzureUserExternal(user, configUser));
113+
}
77114
} else {
78-
log.debug("Adding user to Kafka, {}", user.userPrincipalName);
79-
azureUserProducerService.publish(new AzureUser(user, configUser));
115+
AzureUser azureuser = new AzureUser(user, configUser);
116+
if ((azureuser.getEmployeeId() != null && !azureuser.getEmployeeId().isEmpty()) ||
117+
(azureuser.getStudentId() != null && !azureuser.getStudentId().isEmpty())) {
118+
log.debug("Publishing user to Kafka: {}", user.userPrincipalName);
119+
azureUserProducerService.publish(azureuser);
120+
log.debug("Updating cache for user: {}", user.id);
121+
changedUsers.getAndIncrement();
122+
entraIdUserCache.put(user.id, azureuser);
123+
} else {
124+
log.debug("UserId: {} does not contain required employeeId or studentId. Not published to kafka", user.id);
125+
}
80126
}
127+
// if (AzureUser.getAttributeValue(user, configUser.getExternaluserattribute()) != null
128+
// && (AzureUser.getAttributeValue(user, configUser.getExternaluserattribute()).equalsIgnoreCase(configUser.getExternaluservalue()))) {
129+
// log.debug("Adding external user to Kafka, {}", user.userPrincipalName);
130+
// azureUserExternalProducerService.publish(new AzureUserExternal(user, configUser));
131+
// } else {
132+
// log.debug("Adding user to Kafka, {}", user.userPrincipalName);
133+
// azureUserProducerService.publish(new AzureUser(user, configUser));
134+
// }
81135
}
82136
if (page.getNextPage() == null) {
83137
break;
84138
} else {
85-
//log.info("Processing user page");
86139
page = page.getNextPage().buildRequest().get();
87140
}
88141
} while (page != null);
89142
log.info("*** <<< {} User objects detected in Microsoft Entra >>> ***", users);
90-
//});
143+
if (changedUsers.get() > 0) {
144+
log.info("*** <<< {} Entra users published to kafka as they where different from user cache >>> ***", changedUsers.get());
145+
}
146+
if (changedExtUsers.get() > 0) {
147+
log.info("*** <<< {} external users published to kafka as they where where different from external user cache >>> ***", changedExtUsers.get());
148+
}
91149
}
92150

93151
@Scheduled(
@@ -97,6 +155,7 @@ private void pageThroughUsers(UserCollectionPage inPage) {
97155
public void pullAllGroups() {
98156
log.info("*** <<< Fetching groups from Microsoft Entra >>> ***");
99157
long startTime = System.currentTimeMillis();
158+
publishedMembers = new AtomicInteger();
100159

101160
try {
102161
CompletableFuture<GroupCollectionPage> initialPageFuture = graphService.groups()
@@ -120,6 +179,7 @@ public void pullAllGroups() {
120179
long memberFetchMinutes = memberFetchElapsedTimeInSeconds / 60;
121180
long memberFetchSeconds = memberFetchElapsedTimeInSeconds % 60;
122181

182+
123183
log.info("*** <<< Done fetching all group memberships from Microsoft Entra ID in {} minutes and {} seconds >>> ***", memberFetchMinutes, memberFetchSeconds);
124184
return groupCount;
125185
});
@@ -130,14 +190,25 @@ public void pullAllGroups() {
130190
} catch (ClientException e) {
131191
log.error("Failed when trying to get groups. ", e);
132192
}
193+
if (publishedMembers.get() > 0) {
194+
log.info("*** <<< {} Entra group members published to kafka as they were not in membership cache >>> ***", publishedMembers.get());
195+
} else {
196+
log.info("*** <<< All entra group members already in cache. No members published to kafka >>> ***");
197+
}
133198
}
134199

200+
135201
private CompletableFuture<List<Group>> fetchAllGroups(GroupCollectionPage initialPage) {
136202
List<Group> allGroups = new ArrayList<>();
137203
AtomicInteger groupCounter = new AtomicInteger(0); // Counter for groups
138204

139205
return fetchAllGroupsRecursive(initialPage, allGroups, groupCounter).thenApply(v -> {
140-
log.info("*** <<< Found {} groups with suffix \"{}\" >>> ***", groupCounter.get(), configGroup.getSuffix());
206+
if(groupCounter.get() > 0) {
207+
log.info("*** <<< Found {} groups with suffix \"{}\" not in cache, that were published to kafka >>> ***", groupCounter.get(), configGroup.getSuffix());
208+
}
209+
else {
210+
log.info("*** <<< All groups already in cache. Not republishing to kafka >>> ***");
211+
}
141212
return allGroups;
142213
});
143214
}
@@ -146,9 +217,18 @@ private CompletableFuture<Void> fetchAllGroupsRecursive(GroupCollectionPage curr
146217
List<Group> currentPageGroups = currentPage.getCurrentPage().stream()
147218
.filter(group -> group.displayName != null && group.displayName.endsWith(configGroup.getSuffix())&& (!group.additionalDataManager().isEmpty() && group.additionalDataManager().containsKey(configGroup.getFintkontrollidattribute())))
148219
.peek(group -> {
149-
groupCounter.incrementAndGet();
150220
AzureGroup newGroup = new AzureGroup(group, configGroup);
151-
azureGroupProducerService.publish(newGroup); // Publish the group as soon as it is found
221+
if(azureGroupCache != null
222+
&& azureGroupCache.containsKey(newGroup.getId())
223+
&& newGroup.equals(azureGroupCache.get(newGroup.getId()))) {
224+
log.debug("{} groupID allready published and in cache. Not replublished to kafka", newGroup.getId());
225+
}
226+
else
227+
{
228+
groupCounter.incrementAndGet();
229+
azureGroupProducerService.publish(newGroup); // Publish the group as soon as it is found
230+
azureGroupCache.put(newGroup.getId(), newGroup);
231+
}
152232
})
153233
.toList();
154234
allGroups.addAll(currentPageGroups);
@@ -185,23 +265,36 @@ private CompletableFuture<Integer> fetchMembersForAllGroups(List<Group> groups)
185265

186266

187267
private CompletableFuture<Void> pageThroughAzureGroupAsync(AzureGroup azureGroup, DirectoryObjectCollectionWithReferencesPage inPage) {
188-
AtomicInteger members = new AtomicInteger(0);
268+
AtomicInteger members = new AtomicInteger();
269+
189270

190-
return processPageAsync(azureGroup, inPage, members)
271+
272+
return processPageAsync(azureGroup, inPage, members, publishedMembers)
191273
.thenRun(() -> log.debug("{} memberships detected in groupName {} with groupId {}",
192274
members.get(), azureGroup.getDisplayName(), azureGroup.getId()));
193275
}
194276

195-
private CompletableFuture<Void> processPageAsync(AzureGroup azureGroup, DirectoryObjectCollectionWithReferencesPage page, AtomicInteger members) {
277+
private CompletableFuture<Void> processPageAsync(AzureGroup azureGroup, DirectoryObjectCollectionWithReferencesPage page, AtomicInteger members, AtomicInteger publishedMembers) {
196278
if (page == null) {
197279
return CompletableFuture.completedFuture(null);
198280
}
199281

200282
List<CompletableFuture<Void>> futures = page.getCurrentPage().stream()
201283
.map(member -> CompletableFuture.runAsync(() -> {
202284
members.incrementAndGet();
203-
azureGroupMembershipProducerService.publishAddedMembership(new AzureGroupMembership(azureGroup.getId(), member));
204-
log.debug("Produced message to Kafka where userId: {} is member of groupId: {}", member.id, azureGroup.getId());
285+
AzureGroupMembership azureGroupMembership = new AzureGroupMembership(azureGroup.getId(), member);
286+
if(azureGroupMembershipCache != null
287+
&& azureGroupMembershipCache.containsKey(azureGroupMembership.getId())
288+
&& azureGroupMembership.equals(azureGroupMembershipCache.get(azureGroupMembership.getId())))
289+
{
290+
log.debug("Skipping message to Kafka, as userId: {} is allready published as member of groupId: {}", member.id, azureGroup.getId());
291+
}
292+
else {
293+
azureGroupMembershipProducerService.publishAddedMembership(azureGroupMembership);
294+
azureGroupMembershipCache.put(azureGroupMembership.getId(), azureGroupMembership);
295+
publishedMembers.getAndIncrement();
296+
log.debug("Produced message to Kafka where userId: {} is member of groupId: {}", member.id, azureGroup.getId());
297+
}
205298
}))
206299
.toList();
207300

@@ -211,7 +304,7 @@ private CompletableFuture<Void> processPageAsync(AzureGroup azureGroup, Director
211304

212305
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
213306
.thenCompose(v -> nextPageFuture)
214-
.thenCompose(nextPage -> processPageAsync(azureGroup, nextPage, members));
307+
.thenCompose(nextPage -> processPageAsync(azureGroup, nextPage, members, publishedMembers));
215308
}
216309
// public void pullAllGroups() {
217310
// log.info("*** <<< Fetching groups from Microsoft Entra >>> ***");

0 commit comments

Comments
 (0)