Skip to content

Commit

Permalink
Merge branch 'release/v7.0.8-2'
Browse files Browse the repository at this point in the history
  • Loading branch information
meiserloh authored and cesmarvin committed Oct 2, 2024
2 parents fe7cdab + 6f31fea commit c98b509
Show file tree
Hide file tree
Showing 14 changed files with 181 additions and 38 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v7.0.8-2] - 2024-10-02
### Fixed
- Fix a bug where the watch for service accounts in the config `local.yaml` stucks because the events wasn't resetted and polled [#217]

## [v7.0.8-1] - 2024-09-23
### Added
- Add "lang"-attribute to HTML-Pages [#213]
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ RUN apk update && apk add wget && wget -O "apache-tomcat-${TOMCAT_VERSION}.tar.
FROM registry.cloudogu.com/official/java:21.0.4-1

LABEL NAME="official/cas" \
VERSION="7.0.8-1" \
VERSION="7.0.8-2" \
maintainer="[email protected]"

ARG TOMCAT_VERSION
Expand Down
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!groovy
@Library(['github.com/cloudogu/ces-build-lib@2.2.1', 'github.com/cloudogu/dogu-build-lib@v2.3.1'])
@Library(['github.com/cloudogu/ces-build-lib@2.4.0', 'github.com/cloudogu/dogu-build-lib@v2.4.0'])
import com.cloudogu.ces.cesbuildlib.*
import com.cloudogu.ces.dogubuildlib.*

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MAKEFILES_VERSION=9.1.0
MAKEFILES_VERSION=9.2.1

.DEFAULT_GOAL:=dogu-release

Expand Down
85 changes: 64 additions & 21 deletions app/src/main/java/de/triology/cas/services/RegistryLocal.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import java.util.stream.Collectors;

@Slf4j
public class RegistryLocal implements Registry{
public class RegistryLocal implements Registry {

private static final String LOCAL_CONFIG_FILE = "/var/ces/config/local.yaml";
private static final String LOCAL_CONFIG_DIR = "/var/ces/config";
private static final String LOCAL_CONFIG_SUB_PATH = "/var/ces/config";
private static final String LOCAL_CONFIG_FILE_NAME = "local.yaml";
private static final String LOCAL_CONFIG_FILE = LOCAL_CONFIG_SUB_PATH + "/" + LOCAL_CONFIG_FILE_NAME;
private static final String GLOBAL_CONFIG_FILE = "/etc/ces/config/global/config.yaml";

FileSystem fileSystem = FileSystems.getDefault();
Expand All @@ -32,11 +34,13 @@ public class RegistryLocal implements Registry{
protected static class GlobalConfig {
private String fqdn;
}

@Getter
@Setter
protected static class LocalConfig {
private ServiceAccounts service_accounts = new ServiceAccounts();
}

@Getter
@Setter
protected static class ServiceAccountSecret {
Expand All @@ -48,6 +52,7 @@ boolean deepEquals(ServiceAccountSecret other) {
return stringsEqual(secret, other.secret) && stringsEqual(logout_uri, other.logout_uri);
}
}

@Getter
@Setter
protected static class ServiceAccountCas {
Expand Down Expand Up @@ -217,31 +222,69 @@ public URI getCasLogoutUri(String doguname) throws GetCasLogoutUriException {

@Override
public void addDoguChangeListener(DoguChangeListener doguChangeListener) {
Thread t1 = new Thread(() -> {
var path = Paths.get(LOCAL_CONFIG_DIR);
try(WatchService watchService = fileSystem.newWatchService()) {
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);
var previousServiceAccounts = readServiceAccounts();
while (true) {
LOGGER.info("wait for changes under {}", LOCAL_CONFIG_DIR);
watchService.take();
Thread t1 = new Thread(() -> doguChangeListenerThreadHandler(doguChangeListener));
t1.start();
}

private void doguChangeListenerThreadHandler(DoguChangeListener doguChangeListener) {
var path = Paths.get(LOCAL_CONFIG_DIR);
WatchService watchService;
try {
watchService = initializeWatchService(path, null);
var previousServiceAccounts = readServiceAccounts();
WatchKey key;
LOGGER.info("wait for changes under {}", LOCAL_CONFIG_DIR);
while ((key = watchService.take()) != null) { // take does not return null
if (!key.isValid()) {
LOGGER.info("watch key was cancelled or watch service was closed");
watchService = initializeWatchService(path, watchService);
continue;
}

List<WatchEvent<?>> watchEvents = key.pollEvents();
boolean serviceAccountRegistryChanged = false;
for (WatchEvent<?> event : watchEvents) {
if (event.context() != null && event.context().toString().equals(LOCAL_CONFIG_FILE_NAME)) {
serviceAccountRegistryChanged = true;
break;
}
}

if (serviceAccountRegistryChanged) {
var currentServiceAccounts = readServiceAccounts();
if (!previousServiceAccounts.deepEquals(currentServiceAccounts)) {
LOGGER.info("services changed. call doguChangeListener");
doguChangeListener.onChange();
previousServiceAccounts = currentServiceAccounts;
}
}
} catch (IOException e) {
throw new RegistryException("Failed to addDoguChangeListener", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RegistryException("Failed to addDoguChangeListener", e);

// Resetting the watchKey is very important. Otherwise, the key returned from take() (or poll()) will not return any more events.
if (!key.reset()) {
LOGGER.info("watch key is no longer valid");
watchService = initializeWatchService(path, watchService);
}
}
});
t1.start();
} catch (IOException e) {
throw new RegistryException("Failed to addDoguChangeListener", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RegistryException("Failed to addDoguChangeListener", e);
}
}

private static <T> T readYaml(Class<T> tClass, InputStream yamlStream){
private WatchService initializeWatchService(Path path, WatchService oldWatchService) throws IOException {
if (oldWatchService != null) {
LOGGER.info("close old watch service");
oldWatchService.close();
}
LOGGER.info("initialize watch service");
WatchService watchService = fileSystem.newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);

return watchService;
}
private static <T> T readYaml(Class<T> tClass, InputStream yamlStream) {
var yaml = new Yaml(new Constructor(tClass, new LoaderOptions()));
try {
T result = yaml.load(yamlStream);
Expand All @@ -254,9 +297,9 @@ private static <T> T readYaml(Class<T> tClass, InputStream yamlStream){
} catch (YAMLException e) {
throw new RegistryException(String.format("Failed to parse yaml stream to class %s", tClass.getName()), e);
} catch (InstantiationException |
IllegalAccessException |
InvocationTargetException |
NoSuchMethodException e) {
IllegalAccessException |
InvocationTargetException |
NoSuchMethodException e) {
throw new RegistryException(String.format("Failed to construct new instance of %s", tClass.getName()), e);
}
}
Expand Down
93 changes: 84 additions & 9 deletions app/src/test/java/de/triology/cas/services/RegistryLocalTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.WatchService;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -458,30 +457,35 @@ public void getFqdnFailToCloseStream() throws IOException {
}

@Test
public void addDoguChangeListener() throws IOException, InterruptedException {
public void ChangeListener() throws IOException, InterruptedException {
var watchKey = mock(WatchKey.class);
when(watchKey.isValid())
.thenReturn(true);
List<WatchEvent<?>> watchEvents = new ArrayList<>();
watchEvents.add(new EventMock("local.yaml"));
when(watchKey.pollEvents())
.thenReturn(watchEvents);
when(watchKey.reset())
.thenReturn(true);
Class<? extends WatchService> wsClass;
try (var ws = FileSystems.getDefault().newWatchService()) {
wsClass = ws.getClass();
}
var watchService = mock(wsClass);
when(watchService.take())
.thenReturn(null)
.thenReturn(null)
.thenReturn(null)
.thenReturn(watchKey)
.thenThrow(InterruptedException.class); // finish on fourth invocation
var fileSystem = mock(FileSystem.class);
when(fileSystem.newWatchService()).thenReturn(watchService);

var registry = spy(RegistryLocal.class);
registry.fileSystem = fileSystem;
var initialServiceAccounts = new RegistryLocal.ServiceAccounts();
var unchangedServiceAccounts1 = new RegistryLocal.ServiceAccounts();
var changedServiceAccounts = new RegistryLocal.ServiceAccounts();
changedServiceAccounts.setCas(Map.of("usermgt", new RegistryLocal.ServiceAccountCas()));
var unchangedServiceAccounts2 = new RegistryLocal.ServiceAccounts();
unchangedServiceAccounts2.setCas(Map.of("usermgt", new RegistryLocal.ServiceAccountCas()));
doReturn(initialServiceAccounts, // get initial state for comparison
unchangedServiceAccounts1, // no change should be detected
changedServiceAccounts, // detect change
unchangedServiceAccounts2) // no change to previous state
.when(registry).readServiceAccounts();
Expand All @@ -502,6 +506,77 @@ public void addDoguChangeListener() throws IOException, InterruptedException {
assertThat(dogus, hasSize(1));
}

@Test
public void ChangeListenerWithReInitialization() throws IOException, InterruptedException {
var watchKey = mock(WatchKey.class);
when(watchKey.isValid())
.thenReturn(false)
.thenReturn(true)
.thenReturn(true);
List<WatchEvent<?>> watchEvents = new ArrayList<>();
watchEvents.add(new EventMock("local.yaml"));
when(watchKey.pollEvents())
.thenReturn(watchEvents);
when(watchKey.reset())
.thenReturn(false)
.thenReturn(true);
Class<? extends WatchService> wsClass;
try (var ws = FileSystems.getDefault().newWatchService()) {
wsClass = ws.getClass();
}
var watchService = mock(wsClass);
when(watchService.take())
.thenReturn(watchKey)
.thenReturn(watchKey)
.thenReturn(watchKey)
.thenThrow(InterruptedException.class); // finish on fourth invocation
var fileSystem = mock(FileSystem.class);
when(fileSystem.newWatchService())
.thenReturn(watchService)
.thenReturn(watchService)
.thenReturn(watchService);

var registry = spy(RegistryLocal.class);
registry.fileSystem = fileSystem;
var initialServiceAccounts = new RegistryLocal.ServiceAccounts();
var changedServiceAccounts = new RegistryLocal.ServiceAccounts();
changedServiceAccounts.setCas(Map.of("usermgt", new RegistryLocal.ServiceAccountCas()));
var unchangedServiceAccounts = new RegistryLocal.ServiceAccounts();
unchangedServiceAccounts.setCas(Map.of("usermgt", new RegistryLocal.ServiceAccountCas()));
doReturn(initialServiceAccounts, // get initial state for comparison
initialServiceAccounts, // no change
changedServiceAccounts, // detect change
unchangedServiceAccounts) // no change to previous state
.when(registry).readServiceAccounts();

ArrayList<String> dogus = new ArrayList<>();

registry.addDoguChangeListener(() -> {
synchronized (dogus) {
dogus.add("dogu " + dogus.size());
dogus.notify();
}
});

synchronized (dogus) {
dogus.wait();
}

assertThat(dogus, hasSize(1));
}

private record EventMock(String context) implements WatchEvent<String> {
@Override
public Kind<String> kind() {
return null;
}

@Override
public int count() {
return 0;
}
}

@Test
public void deepEquals() {
Map<String, RegistryLocal.ServiceAccountCas> cas = new HashMap<>();
Expand Down
2 changes: 1 addition & 1 deletion build/make/bats.mk
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ BATS_SUPPORT=$(BATS_LIBRARY_DIR)/bats-support
BATS_FILE=$(BATS_LIBRARY_DIR)/bats-file
BATS_BASE_IMAGE?=bats/bats
BATS_CUSTOM_IMAGE?=cloudogu/bats
BATS_TAG?=1.2.1
BATS_TAG?=1.11.0
BATS_DIR=build/make/bats
BATS_WORKDIR="${WORKDIR}"/"${BATS_DIR}"

Expand Down
4 changes: 3 additions & 1 deletion build/make/bats/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
ARG BATS_BASE_IMAGE
ARG BATS_TAG

FROM ${BATS_BASE_IMAGE}:${BATS_TAG}
FROM ${BATS_BASE_IMAGE:-bats/bats}:${BATS_TAG:-1.11.0}

# Make bash more findable by scripts and tests
RUN apk add make git bash
# suppress git "detected dubious ownership" error/warning for repos which are checked out later
RUN git config --global --add safe.directory /workspace
2 changes: 1 addition & 1 deletion build/make/k8s.mk
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ ${K8S_RESOURCE_TEMP_FOLDER}:
##@ K8s - Docker

.PHONY: docker-build
docker-build: check-docker-credentials check-k8s-image-env-var ## Builds the docker image of the K8s app.
docker-build: check-docker-credentials check-k8s-image-env-var ${BINARY_YQ} ## Builds the docker image of the K8s app.
@echo "Building docker image $(IMAGE)..."
@DOCKER_BUILDKIT=1 docker build . -t $(IMAGE)

Expand Down
13 changes: 13 additions & 0 deletions build/make/vulnerability-scan.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
##@ Vulnerability scan

GOVULNCHECK_BIN=${UTILITY_BIN_PATH}/govulncheck
GOVULNCHECK_VERSION?=latest

${GOVULNCHECK_BIN}: ${UTILITY_BIN_PATH}
$(call go-get-tool,$(GOVULNCHECK_BIN),golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION))

.PHONY: govulncheck
govulncheck: ${GOVULNCHECK_BIN} ## This target is used to scan the go repository against known vulnerabilities
@echo "Start vulnerability against repository"
${GOVULNCHECK_BIN} -show verbose ./...
@echo "Finished scan"
3 changes: 3 additions & 0 deletions docs/gui/release_notes_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Im Folgenden finden Sie die Release Notes für das CAS-Dogu.

Technische Details zu einem Release finden Sie im zugehörigen [Changelog](https://docs.cloudogu.com/de/docs/dogus/cas/CHANGELOG/).

## Release 7.0.8-2
Es wurde ein technischer Fehler behoben der in Multinode-Umgebungen verhindert hat, dass Dogus mit Service-Accounts `cas` erreichbar sind.

## Release 7.0.8-1
Das Dogu bietet nun die CAS-Version 7.0.8 an. Die Release Notes von CAS finden Sie [in den CAS-Github-Releases](https://github.com/apereo/cas/releases/tag/v7.0.8).
- Die CAS-HTML-Seiten enthalten nun ein "lang"-Attribut um die Barrierefreiheit zu erhöhen.
Expand Down
3 changes: 3 additions & 0 deletions docs/gui/release_notes_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Below you will find the release notes for CAS-Dogu.

Technical details on a release can be found in the corresponding [Changelog](https://docs.cloudogu.com/de/docs/dogus/cas/CHANGELOG/).

## Release 7.0.8-2
Resolved a technical issue in multinode environment, that caused that dogus with service accounts `cas` are not available.

## Release 7.0.8-1
- The Dogu now offers the CAS version 7.0.8. The release notes of CAS can be found [in the CAS Github releases](https://github.com/apereo/cas/releases/tag/v7.0.8).
- The CAS HTML pages now contain a “lang” attribute to increase accessibility.
Expand Down
2 changes: 1 addition & 1 deletion dogu.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Name": "official/cas",
"Version": "7.0.8-1",
"Version": "7.0.8-2",
"DisplayName": "Central Authentication Service",
"Description": "The Central Authentication Service (CAS) is a single sign-on protocol for the web.",
"Url": "https://apereo.github.io/cas",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ces-style-generator",
"version": "7.0.8-1",
"version": "7.0.8-2",
"description": "Npm project to use ces-theme to generate styling",
"main": "index.js",
"directories": {
Expand Down

0 comments on commit c98b509

Please sign in to comment.