Skip to content

Commit

Permalink
Merge pull request #16 from swisscom/feature/32591-authenticate
Browse files Browse the repository at this point in the history
Feature/32591 authenticate
  • Loading branch information
Lesrac authored Nov 7, 2024
2 parents d543b9b + 0ccf27b commit 015f61e
Show file tree
Hide file tree
Showing 29 changed files with 668 additions and 42 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ Following environment variables need to be set:
* cdrClient.localFolder=~/Documents/cdr/inflight
* cdrClient.targetFolder=~/Documents/cdr/target
* cdrClient.sourceFolder=~/Documents/cdr/source
* CDR_B2C_TENANT_ID=some-cdr-azure-ad-tenant
* CDR_CLIENT_ID=oauth2-client-id
* CDR_CLIENT_SECRET=oauth2-client-secret

## Application Plugin
To create scripts to run the application locally one needs to run following gradle cmd: ```gradlew installDist```
Expand All @@ -62,10 +65,8 @@ To run the application locally one can call ```./build/install/cdr-client/bin/cd
With a minimum configuration that looks like this:
```
client:
local-folder: /tmp/cdr
endpoint:
host: cdr.health.swisscom.com
base-path: api/documents
customer:
- connector-id: 8000000000000
content-type: application/forumdatenaustausch+xml;charset=UTF-8
Expand Down
86 changes: 69 additions & 17 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import io.gitlab.arturbosch.detekt.Detekt
import org.springframework.boot.gradle.tasks.bundling.BootJar
import java.net.URI
import java.time.Duration

group = "com.swisscom.health.des.cdr"
version = "3.1.3-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

val jvmVersion: String by project
val kotlinCoroutinesVersion: String by project
val springCloudVersion: String by project
val awaitilityVersion: String by project
val jacocoVersion: String by project
val kacheVersion: String by project
val kfsWatchVersion: String by project
val kotlinCoroutinesVersion: String by project
val kotlinLoggingVersion: String by project
val mockkVersion: String by project
val logstashEncoderVersion: String by project
val micrometerTracingVersion: String by project
val kfsWatchVersion: String by project
val kacheVersion: String by project
val mockkVersion: String by project
val msal4jVersion: String by project
val springCloudVersion: String by project
val springMockkVersion: String by project
val awaitilityVersion: String by project
val jvmVersion: String by project

val outputDir: Provider<Directory> = layout.buildDirectory.dir(".")

plugins {
id("com.avast.gradle.docker-compose") version "0.17.8"
id("org.springframework.boot")
id("io.spring.dependency-management")
id("io.gitlab.arturbosch.detekt")
Expand Down Expand Up @@ -62,19 +65,21 @@ dependencyManagement {
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("com.mayakapps.kache:kache:$kacheVersion")
implementation("com.microsoft.azure:msal4j:$msal4jVersion")
implementation("com.squareup.okhttp3:okhttp")
implementation("io.github.irgaly.kfswatch:kfswatch:$kfsWatchVersion")
implementation("io.github.oshai:kotlin-logging:$kotlinLoggingVersion")
implementation("io.micrometer:micrometer-tracing:$micrometerTracingVersion")
implementation("io.micrometer:micrometer-tracing-bridge-otel:$micrometerTracingVersion")
implementation("net.logstash.logback:logstash-logback-encoder:$logstashEncoderVersion")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${kotlinCoroutinesVersion}") // to enable @Scheduled on Kotlin suspending functions
implementation("io.github.oshai:kotlin-logging:${kotlinLoggingVersion}")
implementation("net.logstash.logback:logstash-logback-encoder:${logstashEncoderVersion}")
implementation("io.micrometer:micrometer-tracing:${micrometerTracingVersion}")
implementation("io.micrometer:micrometer-tracing-bridge-otel:${micrometerTracingVersion}")
implementation("io.github.irgaly.kfswatch:kfswatch:$kfsWatchVersion")
implementation("com.mayakapps.kache:kache:$kacheVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$kotlinCoroutinesVersion") // to enable @Scheduled on Kotlin suspending functions
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.retry:spring-retry")

kapt("org.springframework.boot:spring-boot-configuration-processor")

Expand Down Expand Up @@ -112,12 +117,25 @@ kotlin {
}
}

tasks.test {
useJUnitPlatform {
excludeTags(Constants.INTEGRATION_TEST_TAG)
}
}

val jacocoTestCoverageVerification = tasks.named<JacocoCoverageVerification>("jacocoTestCoverageVerification") {
violationRules {
/**
* Ensure tests cover at least 75% of the LoC.
*/
rule {
classDirectories.setFrom(files(classDirectories.files.map {
fileTree(it) {
setExcludes(listOf(
"**/com/swisscom/health/des/cdr/clientvm/msal4j/*.class"
))
}
}))
limit {
minimum = "0.75".toBigDecimal()
}
Expand All @@ -143,6 +161,11 @@ tasks.withType<Test> {
includeEngines("junit-jupiter")
}
finalizedBy(jacocoTestReport)

jvmArgs(
// tests_hosts is used to redirect msal4j, which insists on talking to the Mothership, to our docker compose setup
"-Djdk.net.hosts.file=${layout.projectDirectory.file("src/test/resources/test_hosts").asFile.absolutePath}"
)
}

jacoco {
Expand Down Expand Up @@ -217,3 +240,32 @@ publishing {
}
}
}

/***********************
* Integration Testing *
***********************/
object Constants {
const val TASK_GROUP_VERIFICATION = "verification"
const val INTEGRATION_TEST_TAG = "integration-test"
}

tasks.register<Test>("integrationTest") {
group = Constants.TASK_GROUP_VERIFICATION
useJUnitPlatform {
includeTags(Constants.INTEGRATION_TEST_TAG)
}
shouldRunAfter(tasks.test)
// Ensure latest images get pulled
dependsOn(tasks.composePull)
}

dockerCompose {
dockerComposeWorkingDirectory.set(File("${rootProject.projectDir}/docker-compose"))
dockerComposeStopTimeout.set(Duration.ofSeconds(5)) // time before docker-compose sends SIGTERM to the running containers after the composeDown task has been started
ignorePullFailure.set(true)
isRequiredBy(tasks.getByName("integrationTest"))
}

/***************************
* END Integration Testing *
***************************/
12 changes: 12 additions & 0 deletions docker-compose/caddy/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
http_port 8080
https_port 8443
}
localhost {
reverse_proxy /isalive mock-oauth2-server:8080
}
login.microsoftonline.com {
tls internal
reverse_proxy /common/discovery/* wiremock:8080
reverse_proxy /test-tenant-id/* mock-oauth2-server:8080
}
3 changes: 3 additions & 0 deletions docker-compose/caddy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM caddy:2.8.4
RUN apk update && apk upgrade && apk add curl
COPY ./Caddyfile /etc/caddy/Caddyfile
53 changes: 51 additions & 2 deletions docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
services:
wiremock:
image: local.wiremock.cdrapi
container_name: wiremock-cdr-client
build: ./wiremock
mem_limit: 256m
healthcheck:
# use a mapped URL for health checking; the response is a 200 OK with a string body "OK"
test: curl --fail http://localhost:8080/health || exit 1
interval: 5s
retries: 5
start_period: 5s
timeout: 10s
ports:
- "9090:8080"
- "8443:8443"
- "9090:8080"
environment:
- TZ=Europe/Zurich
command: ["--verbose", "--https-port", "8443", "--global-response-templating"]
networks:
- cdr_client_net

mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
container_name: mock-oauth2-server-cdr-client
environment:
- TZ=Europe/Zurich
- LOG_LEVEL=DEBUG
- JSON_CONFIG_PATH=/app/config.json
- SERVER_PORT=8080
volumes:
- ./mockOAuth2Server/config.json:/app/config.json
- ./mockOAuth2Server/mockoauth2server.p12:/app/mockoauth2server.p12
networks:
- cdr_client_net

caddy:
depends_on:
wiremock:
condition: service_healthy
image: local.caddy.cdrappmgr
container_name: caddy-app-mgr
build: ./caddy
mem_limit: 256m
ports:
- "8443:8443"
healthcheck:
# checks the mock-oauth2-server's health endpoint to prove that both caddy and the mock-oauth2-server are up and running
test: curl --insecure --fail https://localhost:8443/isalive || exit 1
interval: 5s
retries: 5
start_period: 120s
timeout: 10s
networks:
- cdr_client_net

networks:
cdr_client_net:
name: cdr_client_net
ipam:
config:
- subnet: 10.113.0.0/16
44 changes: 44 additions & 0 deletions docker-compose/mockOAuth2Server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# TLS Keystore For MockOAuth2Server

Run your [own CA](https://pki-tutorial.readthedocs.io/en/latest/simple/) and issue yourself a
[key pair](./mockoauth2server.p12).<br>
If you want/need to repeat the process:

1. check out the 2022 branch (or whatever branch appears to be the most up-to-date one) of this
[git repo](https://bitbucket.org/stefanholek/pki-example-1) and
2. change the policy "match_pol" of the signing ca config file to mark all fields as "supplied" so you get to set
the `DC`, `OU`, and `O` parts of the X509 certificate yourself:
```shell
diff work/git/3rd_party/pki-example-1/etc/signing-ca.conf work/software/ca/etc/signing-ca.conf
68,70c68,70
< domainComponent = match # Must match 'simple.org'
< organizationName = match # Must match 'Simple Inc'
< organizationalUnitName = optional # Included if present
---
> domainComponent = supplied # Must match 'simple.org'
> organizationName = supplied # Must match 'Simple Inc'
> organizationalUnitName = supplied # Included if present
```
3. follow the instructions to initialize the root and signing CAs (step 1 and 2)
4. create a sub-folder `certs`
5. then generated a new key pair for the Oauth2 Mock Server like so (note that the `SAN` must match the service name in
the [docker-compose.yml](../docker-compose.yaml)):
```shell
SAN=DNS:mock-oauth2-server,DNS:localhost,DNS:host.docker.internal \
openssl req -new \
-config etc/server.conf \
-out certs/mockoauth2server.csr \
-keyout certs/mockoauth2server.key
openssl ca \
-config etc/signing-ca.conf \
-in certs/mockoauth2server.csr \
-out certs/mockoauth2server.crt \
-extensions server_ext
openssl pkcs12 -export \
-name "OAuth2 Local Development" \
-inkey certs/mockoauth2server.key \
-in certs/mockoauth2server.crt \
-out certs/mockoauth2server.p12
```
30 changes: 30 additions & 0 deletions docker-compose/mockOAuth2Server/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"interactiveLogin": false,
"httpServer": {
"type": "NettyWrapper"
},
"tokenProvider" : {
"keyProvider" : {
"algorithm" : "ES256"
}
},
"tokenCallbacks": [
{
"issuerId": "test-tenant-id/oauth2/v2.0",
"tokenExpiry":360,
"requestMappings": [
{
"requestParam": "client_id",
"match": "*",
"claims": {
"sub": "${clientId}",
"roles": [
"CdrApi.ReadWrite.OwnedBy",
"AppRoleAssignment.ReadWrite.All"
]
}
}
]
}
]
}
Binary file not shown.
Loading

0 comments on commit 015f61e

Please sign in to comment.